summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java4
-rw-r--r--core/java/android/accounts/AbstractAccountAuthenticator.java94
-rw-r--r--core/java/android/accounts/AccountAuthenticatorCache.java6
-rw-r--r--core/java/android/accounts/AccountAuthenticatorResponse.java14
-rw-r--r--core/java/android/accounts/AccountManager.java1378
-rw-r--r--core/java/android/accounts/AccountManagerService.java373
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java258
-rw-r--r--core/java/android/accounts/AuthenticatorDescription.java18
-rw-r--r--core/java/android/accounts/ChooseAccountActivity.java17
-rw-r--r--core/java/android/accounts/GrantCredentialsPermissionActivity.java148
-rw-r--r--core/java/android/accounts/IAccountManager.aidl3
-rw-r--r--core/java/android/app/Activity.java244
-rw-r--r--core/java/android/app/ActivityManager.java99
-rw-r--r--core/java/android/app/ActivityManagerNative.java249
-rw-r--r--core/java/android/app/ActivityThread.java702
-rw-r--r--core/java/android/app/AlarmManager.java21
-rw-r--r--core/java/android/app/AlertDialog.java6
-rw-r--r--core/java/android/app/AliasActivity.java1
-rw-r--r--core/java/android/app/ApplicationErrorReport.java201
-rw-r--r--core/java/android/app/ApplicationThreadNative.java20
-rw-r--r--core/java/android/app/BackupAgent.java149
-rw-r--r--core/java/android/app/ContextImpl.java (renamed from core/java/android/app/ApplicationContext.java)406
-rw-r--r--core/java/android/app/Dialog.java79
-rw-r--r--core/java/android/app/ExpandableListActivity.java18
-rw-r--r--core/java/android/app/FullBackupAgent.java26
-rw-r--r--core/java/android/app/IActivityController.aidl5
-rw-r--r--core/java/android/app/IActivityManager.java87
-rwxr-xr-xcore/java/android/app/IAlarmManager.aidl1
-rw-r--r--core/java/android/app/IApplicationThread.java4
-rw-r--r--core/java/android/app/IBackupAgent.aidl27
-rw-r--r--core/java/android/app/ISearchManager.aidl28
-rw-r--r--core/java/android/app/IUiModeManager.aidl54
-rw-r--r--core/java/android/app/IntentService.java80
-rw-r--r--core/java/android/app/ListActivity.java78
-rw-r--r--core/java/android/app/Notification.java6
-rw-r--r--core/java/android/app/SearchDialog.java763
-rw-r--r--core/java/android/app/SearchManager.java1637
-rw-r--r--core/java/android/app/SearchableInfo.aidl (renamed from core/java/android/server/search/SearchableInfo.aidl)2
-rw-r--r--core/java/android/app/SearchableInfo.java (renamed from core/java/android/server/search/SearchableInfo.java)350
-rw-r--r--core/java/android/app/Service.java127
-rw-r--r--core/java/android/app/StatusBarManager.java10
-rw-r--r--core/java/android/app/SuggestionsAdapter.java298
-rw-r--r--core/java/android/app/UiModeManager.java223
-rw-r--r--core/java/android/app/WallpaperInfo.java60
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java409
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java281
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java616
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl59
-rw-r--r--core/java/android/app/backup/AbsoluteFileBackupHelper.java (renamed from core/java/android/backup/AbsoluteFileBackupHelper.java)13
-rw-r--r--core/java/android/app/backup/BackupAgent.java251
-rw-r--r--core/java/android/app/backup/BackupAgentHelper.java91
-rw-r--r--core/java/android/app/backup/BackupDataInput.java194
-rw-r--r--core/java/android/app/backup/BackupDataInputStream.java118
-rw-r--r--core/java/android/app/backup/BackupDataOutput.java131
-rw-r--r--core/java/android/app/backup/BackupHelper.java105
-rw-r--r--core/java/android/app/backup/BackupHelperDispatcher.java (renamed from core/java/android/backup/BackupHelperDispatcher.java)10
-rw-r--r--core/java/android/app/backup/BackupManager.java175
-rw-r--r--core/java/android/app/backup/FileBackupHelper.java (renamed from core/java/android/backup/FileBackupHelper.java)43
-rw-r--r--core/java/android/app/backup/FileBackupHelperBase.java (renamed from core/java/android/backup/FileBackupHelperBase.java)23
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl (renamed from core/java/android/backup/IBackupManager.aidl)37
-rw-r--r--core/java/android/app/backup/IRestoreObserver.aidl (renamed from core/java/android/backup/IRestoreObserver.aidl)25
-rw-r--r--core/java/android/app/backup/IRestoreSession.aidl (renamed from core/java/android/backup/IRestoreSession.aidl)37
-rw-r--r--core/java/android/app/backup/RestoreObserver.java76
-rw-r--r--core/java/android/app/backup/RestoreSession.java203
-rw-r--r--core/java/android/app/backup/RestoreSet.aidl (renamed from core/java/android/backup/RestoreSet.aidl)2
-rw-r--r--core/java/android/app/backup/RestoreSet.java (renamed from core/java/android/backup/RestoreSet.java)2
-rw-r--r--core/java/android/app/backup/SharedPreferencesBackupHelper.java126
-rw-r--r--core/java/android/app/backup/package.html24
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java11
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java20
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java10
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java12
-rw-r--r--core/java/android/backup/BackupDataInput.java112
-rw-r--r--core/java/android/backup/BackupDataInputStream.java63
-rw-r--r--core/java/android/backup/BackupDataOutput.java77
-rw-r--r--core/java/android/backup/BackupHelper.java46
-rw-r--r--core/java/android/backup/BackupHelperAgent.java56
-rw-r--r--core/java/android/backup/BackupManager.java133
-rw-r--r--core/java/android/backup/SharedPreferencesBackupHelper.java69
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/bluetooth/BluetoothAudioGateway.java16
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java4
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java32
-rw-r--r--core/java/android/bluetooth/ScoSocket.java6
-rw-r--r--core/java/android/content/AbstractCursorEntityIterator.java121
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java755
-rw-r--r--core/java/android/content/AbstractTableMerger.java599
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java58
-rw-r--r--core/java/android/content/AsyncQueryHandler.java68
-rw-r--r--core/java/android/content/ComponentName.java8
-rw-r--r--core/java/android/content/ContentProvider.java75
-rw-r--r--core/java/android/content/ContentProviderClient.java11
-rw-r--r--core/java/android/content/ContentProviderNative.java198
-rw-r--r--core/java/android/content/ContentProviderOperation.java21
-rw-r--r--core/java/android/content/ContentResolver.java406
-rw-r--r--core/java/android/content/ContentService.java97
-rw-r--r--core/java/android/content/ContentValues.java56
-rw-r--r--core/java/android/content/Context.java243
-rw-r--r--core/java/android/content/ContextWrapper.java10
-rw-r--r--core/java/android/content/CursorEntityIterator.java112
-rw-r--r--core/java/android/content/DialogInterface.java1
-rw-r--r--core/java/android/content/Entity.java44
-rw-r--r--core/java/android/content/EntityIterator.java33
-rw-r--r--core/java/android/content/EventLogTags.logtags7
-rw-r--r--core/java/android/content/IContentProvider.java25
-rw-r--r--core/java/android/content/IContentService.aidl46
-rw-r--r--core/java/android/content/IEntityIterator.java210
-rw-r--r--core/java/android/content/ISyncAdapter.aidl8
-rw-r--r--core/java/android/content/Intent.java323
-rw-r--r--core/java/android/content/IntentFilter.java1
-rw-r--r--core/java/android/content/PeriodicSync.aidl19
-rw-r--r--core/java/android/content/PeriodicSync.java84
-rw-r--r--core/java/android/content/SyncAdapter.java73
-rw-r--r--core/java/android/content/SyncAdaptersCache.java6
-rw-r--r--core/java/android/content/SyncContext.java10
-rw-r--r--core/java/android/content/SyncInfo.aidl (renamed from core/java/android/content/ActiveSyncInfo.aidl)2
-rw-r--r--core/java/android/content/SyncInfo.java (renamed from core/java/android/content/ActiveSyncInfo.java)48
-rw-r--r--core/java/android/content/SyncManager.java1233
-rw-r--r--core/java/android/content/SyncOperation.java120
-rw-r--r--core/java/android/content/SyncQueue.java208
-rw-r--r--core/java/android/content/SyncResult.java141
-rw-r--r--core/java/android/content/SyncStateContentProviderHelper.java243
-rw-r--r--core/java/android/content/SyncStats.java65
-rw-r--r--core/java/android/content/SyncStatusInfo.java70
-rw-r--r--core/java/android/content/SyncStorageEngine.java744
-rw-r--r--core/java/android/content/SyncableContentProvider.java237
-rw-r--r--core/java/android/content/TempProviderSyncAdapter.java585
-rw-r--r--core/java/android/content/package.html14
-rw-r--r--core/java/android/content/pm/ActivityInfo.java22
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java149
-rw-r--r--core/java/android/content/pm/ComponentInfo.java63
-rw-r--r--core/java/android/content/pm/FeatureInfo.java16
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl23
-rw-r--r--core/java/android/content/pm/IPackageMoveObserver.aidl (renamed from core/res/res/drawable/btn_application_selector.xml)19
-rw-r--r--core/java/android/content/pm/InstrumentationInfo.java16
-rw-r--r--core/java/android/content/pm/LabeledIntent.java16
-rw-r--r--core/java/android/content/pm/PackageInfo.java52
-rwxr-xr-x[-rw-r--r--]core/java/android/content/pm/PackageInfoLite.aidl (renamed from core/java/android/content/Entity.aidl)6
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java79
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java60
-rw-r--r--core/java/android/content/pm/PackageManager.java288
-rw-r--r--core/java/android/content/pm/PackageParser.java497
-rwxr-xr-xcore/java/android/content/pm/PackageStats.java16
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java16
-rw-r--r--core/java/android/content/pm/RegisteredServicesCacheListener.java16
-rw-r--r--core/java/android/content/pm/ResolveInfo.java30
-rw-r--r--core/java/android/content/pm/ServiceInfo.java16
-rw-r--r--core/java/android/content/pm/XmlSerializerAndParser.java16
-rw-r--r--core/java/android/content/res/AssetManager.java98
-rw-r--r--core/java/android/content/res/ColorStateList.java2
-rw-r--r--core/java/android/content/res/Configuration.java175
-rw-r--r--core/java/android/content/res/Resources.java6
-rw-r--r--core/java/android/content/res/StringBlock.java1
-rw-r--r--core/java/android/content/res/TypedArray.java55
-rw-r--r--core/java/android/content/res/XmlBlock.java3
-rw-r--r--core/java/android/database/BulkCursorToCursorAdaptor.java45
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java2
-rw-r--r--core/java/android/database/CursorWindow.java2
-rw-r--r--core/java/android/database/DatabaseUtils.java96
-rw-r--r--core/java/android/database/IBulkCursor.java8
-rw-r--r--core/java/android/database/MergeCursor.java2
-rw-r--r--core/java/android/database/sqlite/DatabaseObjectNotClosedException.java33
-rw-r--r--core/java/android/database/sqlite/SQLiteClosable.java40
-rw-r--r--core/java/android/database/sqlite/SQLiteCompiledSql.java166
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java29
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java726
-rw-r--r--core/java/android/database/sqlite/SQLiteDebug.java107
-rw-r--r--core/java/android/database/sqlite/SQLiteDirectCursorDriver.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java202
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java28
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java25
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java73
-rw-r--r--core/java/android/database/sqlite/SQLiteTransactionListener.java16
-rw-r--r--core/java/android/database/sqlite/SqliteWrapper.java108
-rw-r--r--core/java/android/ddm/DdmHandleHeap.java38
-rw-r--r--core/java/android/ddm/DdmHandleHello.java10
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java52
-rwxr-xr-xcore/java/android/gesture/Gesture.java35
-rwxr-xr-xcore/java/android/gesture/GestureOverlayView.java2
-rw-r--r--core/java/android/gesture/GesturePoint.java7
-rw-r--r--core/java/android/gesture/GestureStore.java11
-rw-r--r--core/java/android/gesture/GestureStroke.java39
-rwxr-xr-xcore/java/android/gesture/GestureUtils.java (renamed from core/java/android/gesture/GestureUtilities.java)314
-rwxr-xr-xcore/java/android/gesture/Instance.java12
-rw-r--r--core/java/android/gesture/InstanceLearner.java6
-rwxr-xr-xcore/java/android/gesture/Learner.java5
-rw-r--r--core/java/android/hardware/Camera.java545
-rw-r--r--core/java/android/hardware/Sensor.java6
-rw-r--r--core/java/android/hardware/SensorEvent.java17
-rw-r--r--core/java/android/hardware/SensorManager.java103
-rw-r--r--core/java/android/inputmethodservice/ExtractButton.java18
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java16
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java16
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java16
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java34
-rw-r--r--[-rwxr-xr-x]core/java/android/inputmethodservice/KeyboardView.java59
-rw-r--r--core/java/android/net/ConnectivityManager.java211
-rw-r--r--core/java/android/net/Downloads.java645
-rw-r--r--core/java/android/net/IConnectivityManager.aidl22
-rw-r--r--core/java/android/net/INetworkManagementEventObserver.aidl46
-rw-r--r--core/java/android/net/IThrottleManager.aidl40
-rw-r--r--core/java/android/net/InterfaceConfiguration.aidl (renamed from core/java/com/google/android/net/ParentalControlState.aidl)21
-rw-r--r--core/java/android/net/InterfaceConfiguration.java83
-rw-r--r--core/java/android/net/MobileDataStateTracker.java47
-rw-r--r--core/java/android/net/NetworkConnectivityListener.java220
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java307
-rw-r--r--core/java/android/net/SSLSessionCache.java68
-rw-r--r--core/java/android/net/SntpClient.java27
-rw-r--r--core/java/android/net/ThrottleManager.java214
-rw-r--r--core/java/android/net/TrafficStats.java125
-rw-r--r--core/java/android/net/Uri.java66
-rw-r--r--core/java/android/net/WebAddress.java16
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java118
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java219
-rw-r--r--core/java/android/net/http/Connection.java20
-rw-r--r--core/java/android/net/http/ConnectionThread.java19
-rw-r--r--core/java/android/net/http/EventHandler.java4
-rw-r--r--core/java/android/net/http/Headers.java21
-rw-r--r--core/java/android/net/http/HttpConnection.java3
-rw-r--r--core/java/android/net/http/HttpsConnection.java24
-rw-r--r--core/java/android/net/http/Request.java69
-rw-r--r--core/java/android/net/http/RequestHandle.java47
-rw-r--r--core/java/android/net/http/RequestQueue.java73
-rw-r--r--core/java/android/net/http/SslCertificate.java135
-rw-r--r--core/java/android/net/http/SslError.java2
-rw-r--r--core/java/android/os/AsyncTask.java2
-rw-r--r--core/java/android/os/BatteryStats.java101
-rw-r--r--core/java/android/os/Binder.java17
-rw-r--r--core/java/android/os/Build.java16
-rw-r--r--core/java/android/os/Bundle.java107
-rw-r--r--core/java/android/os/Debug.java89
-rw-r--r--core/java/android/os/DropBoxManager.aidl (renamed from core/java/android/speech/RecognitionResult.aidl)4
-rw-r--r--core/java/android/os/DropBoxManager.java281
-rw-r--r--core/java/android/os/Environment.java209
-rw-r--r--core/java/android/os/FileObserver.java132
-rw-r--r--core/java/android/os/FileUtils.java11
-rw-r--r--core/java/android/os/HandlerStateMachine.java290
-rw-r--r--core/java/android/os/HandlerThread.java2
-rw-r--r--core/java/android/os/IBinder.java3
-rw-r--r--core/java/android/os/ICheckinService.aidl53
-rw-r--r--core/java/android/os/IMountService.aidl83
-rw-r--r--core/java/android/os/INetworkManagementService.aidl210
-rw-r--r--core/java/android/os/IPowerManager.aidl7
-rw-r--r--core/java/android/os/IRemoteCallback.aidl25
-rwxr-xr-xcore/java/android/os/IVibratorService.aidl (renamed from core/java/android/os/IHardwareService.aidl)11
-rw-r--r--core/java/android/os/LocalPowerManager.java5
-rw-r--r--core/java/android/os/MemoryFile.java16
-rw-r--r--core/java/android/os/Message.java76
-rw-r--r--core/java/android/os/MessageQueue.java4
-rw-r--r--core/java/android/os/Messenger.java2
-rw-r--r--core/java/android/os/NetStat.java247
-rw-r--r--core/java/android/os/Parcel.java65
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java12
-rw-r--r--core/java/android/os/Power.java10
-rw-r--r--core/java/android/os/PowerManager.java23
-rw-r--r--core/java/android/os/Process.java21
-rw-r--r--core/java/android/os/RecoverySystem.java407
-rw-r--r--core/java/android/os/RemoteCallback.aidl19
-rw-r--r--core/java/android/os/RemoteCallback.java107
-rw-r--r--core/java/android/os/Vibrator.java6
-rw-r--r--core/java/android/os/storage/IMountService.aidl155
-rw-r--r--core/java/android/os/storage/IMountServiceListener.aidl43
-rw-r--r--core/java/android/os/storage/IMountShutdownObserver.aidl33
-rw-r--r--core/java/android/os/storage/MountServiceListener.java44
-rw-r--r--core/java/android/os/storage/StorageEventListener.java39
-rw-r--r--core/java/android/os/storage/StorageManager.java293
-rw-r--r--core/java/android/os/storage/StorageResultCode.java75
-rw-r--r--core/java/android/pim/EventRecurrence.java63
-rw-r--r--core/java/android/pim/RecurrenceSet.java66
-rw-r--r--core/java/android/pim/vcard/Constants.java94
-rw-r--r--core/java/android/pim/vcard/EntryCommitter.java48
-rw-r--r--core/java/android/pim/vcard/JapaneseUtils.java380
-rw-r--r--core/java/android/pim/vcard/VCardBuilder.java1917
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java1849
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java379
-rw-r--r--core/java/android/pim/vcard/VCardConstants.java152
-rw-r--r--core/java/android/pim/vcard/VCardEntry.java (renamed from core/java/android/pim/vcard/ContactStruct.java)1229
-rw-r--r--core/java/android/pim/vcard/VCardEntryCommitter.java68
-rw-r--r--core/java/android/pim/vcard/VCardEntryConstructor.java (renamed from core/java/android/pim/vcard/VCardDataBuilder.java)168
-rw-r--r--core/java/android/pim/vcard/VCardEntryCounter.java21
-rw-r--r--core/java/android/pim/vcard/VCardEntryHandler.java (renamed from core/java/android/pim/vcard/EntryHandler.java)12
-rw-r--r--core/java/android/pim/vcard/VCardInterpreter.java102
-rw-r--r--core/java/android/pim/vcard/VCardInterpreterCollection.java (renamed from core/java/android/pim/vcard/VCardBuilderCollection.java)57
-rw-r--r--core/java/android/pim/vcard/VCardParser.java51
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java369
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java113
-rw-r--r--core/java/android/pim/vcard/VCardSourceDetector.java46
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java896
-rw-r--r--core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java27
-rw-r--r--core/java/android/preference/EditTextPreference.java3
-rw-r--r--core/java/android/preference/Preference.java27
-rw-r--r--core/java/android/preference/PreferenceActivity.java15
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java83
-rw-r--r--core/java/android/preference/PreferenceManager.java5
-rw-r--r--core/java/android/preference/VolumePreference.java10
-rw-r--r--core/java/android/provider/Applications.java129
-rw-r--r--core/java/android/provider/Browser.java310
-rw-r--r--core/java/android/provider/Calendar.java740
-rw-r--r--core/java/android/provider/CallLog.java54
-rw-r--r--core/java/android/provider/Checkin.java326
-rw-r--r--core/java/android/provider/Contacts.java66
-rw-r--r--core/java/android/provider/ContactsContract.java1127
-rw-r--r--core/java/android/provider/Downloads.java614
-rw-r--r--core/java/android/provider/Gmail.java2467
-rw-r--r--core/java/android/provider/Im.java2352
-rw-r--r--core/java/android/provider/MediaStore.java232
-rw-r--r--core/java/android/provider/Settings.java1682
-rw-r--r--core/java/android/provider/SubscribedFeeds.java209
-rw-r--r--core/java/android/provider/SyncConstValue.java3
-rw-r--r--core/java/android/provider/Telephony.java56
-rw-r--r--core/java/android/server/BluetoothA2dpService.java71
-rw-r--r--core/java/android/server/BluetoothEventLoop.java9
-rw-r--r--core/java/android/server/BluetoothService.java223
-rw-r--r--core/java/android/server/data/BuildData.java89
-rw-r--r--core/java/android/server/data/CrashData.java145
-rw-r--r--core/java/android/server/data/StackTraceElementData.java80
-rw-r--r--core/java/android/server/data/ThrowableData.java138
-rwxr-xr-xcore/java/android/server/data/package.html5
-rw-r--r--core/java/android/server/search/SearchDialogWrapper.java426
-rw-r--r--core/java/android/server/search/SearchManagerService.java222
-rw-r--r--core/java/android/server/search/Searchables.java203
-rw-r--r--core/java/android/service/urlrenderer/IUrlRendererCallback.aidl26
-rw-r--r--core/java/android/service/urlrenderer/IUrlRendererService.aidl27
-rw-r--r--core/java/android/service/urlrenderer/UrlRenderer.java89
-rw-r--r--core/java/android/service/urlrenderer/UrlRendererService.java93
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java99
-rw-r--r--core/java/android/speech/IRecognitionListener.aidl61
-rw-r--r--core/java/android/speech/IRecognitionService.aidl51
-rw-r--r--core/java/android/speech/RecognitionListener.java98
-rw-r--r--core/java/android/speech/RecognitionResult.java220
-rw-r--r--core/java/android/speech/RecognitionService.java333
-rw-r--r--core/java/android/speech/RecognitionServiceUtil.java101
-rw-r--r--core/java/android/speech/RecognizerIntent.java186
-rw-r--r--core/java/android/speech/RecognizerResultsIntent.java147
-rw-r--r--core/java/android/speech/SpeechRecognizer.java480
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl10
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java291
-rw-r--r--core/java/android/test/InstrumentationTestCase.java8
-rw-r--r--core/java/android/text/AndroidBidi.java48
-rw-r--r--core/java/android/text/AndroidCharacter.java44
-rw-r--r--core/java/android/text/AutoText.java2
-rw-r--r--core/java/android/text/Html.java1
-rw-r--r--core/java/android/text/Layout.java235
-rw-r--r--core/java/android/text/LoginFilter.java4
-rw-r--r--core/java/android/text/StaticLayout.java484
-rw-r--r--core/java/android/text/Styled.java255
-rw-r--r--core/java/android/text/TextUtils.java28
-rw-r--r--core/java/android/text/format/DateUtils.java105
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java208
-rw-r--r--core/java/android/text/method/Touch.java20
-rw-r--r--core/java/android/text/style/LeadingMarginSpan.java67
-rw-r--r--core/java/android/text/style/TabStopSpan.java17
-rw-r--r--core/java/android/text/style/UpdateAppearance.java16
-rw-r--r--core/java/android/text/util/Linkify.java10
-rw-r--r--core/java/android/text/util/Regex.java204
-rw-r--r--core/java/android/text/util/Rfc822InputFilter.java58
-rw-r--r--core/java/android/text/util/Rfc822Tokenizer.java7
-rw-r--r--core/java/android/text/util/Rfc822Validator.java132
-rw-r--r--core/java/android/util/Base64.java741
-rw-r--r--core/java/android/util/Base64InputStream.java153
-rw-r--r--core/java/android/util/Base64OutputStream.java155
-rw-r--r--core/java/android/util/EventLog.java294
-rw-r--r--core/java/android/util/EventLogTags.java56
-rw-r--r--core/java/android/util/Log.java88
-rw-r--r--core/java/android/util/LogPrinter.java14
-rw-r--r--core/java/android/util/LongSparseArray.java22
-rw-r--r--core/java/android/util/Patterns.java232
-rw-r--r--core/java/android/util/Slog.java85
-rw-r--r--core/java/android/util/XmlPullAttributes.java1
-rw-r--r--core/java/android/view/Display.java49
-rw-r--r--core/java/android/view/GestureDetector.java94
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java5
-rw-r--r--core/java/android/view/IWindow.aidl3
-rw-r--r--core/java/android/view/IWindowManager.aidl9
-rw-r--r--core/java/android/view/IWindowSession.aidl7
-rw-r--r--core/java/android/view/MotionEvent.java100
-rw-r--r--core/java/android/view/RawInputEvent.java17
-rw-r--r--core/java/android/view/ScaleGestureDetector.java123
-rw-r--r--core/java/android/view/Surface.java43
-rw-r--r--core/java/android/view/SurfaceView.java142
-rw-r--r--core/java/android/view/VelocityTracker.java68
-rw-r--r--core/java/android/view/View.java339
-rw-r--r--core/java/android/view/ViewConfiguration.java21
-rw-r--r--core/java/android/view/ViewDebug.java293
-rw-r--r--core/java/android/view/ViewGroup.java171
-rw-r--r--core/java/android/view/ViewRoot.java201
-rw-r--r--core/java/android/view/ViewStub.java8
-rw-r--r--core/java/android/view/Window.java2
-rw-r--r--core/java/android/view/WindowManager.java114
-rw-r--r--core/java/android/view/WindowManagerPolicy.java73
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java279
-rw-r--r--core/java/android/view/animation/Animation.java1
-rw-r--r--core/java/android/view/animation/DecelerateInterpolator.java4
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java5
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java26
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.java16
-rw-r--r--core/java/android/view/inputmethod/ExtractedTextRequest.java16
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java9
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java9
-rw-r--r--core/java/android/webkit/BrowserFrame.java233
-rw-r--r--core/java/android/webkit/ByteArrayBuilder.java125
-rw-r--r--core/java/android/webkit/CacheLoader.java6
-rw-r--r--core/java/android/webkit/CacheManager.java179
-rw-r--r--core/java/android/webkit/CallbackProxy.java211
-rw-r--r--core/java/android/webkit/ConsoleMessage.java64
-rw-r--r--core/java/android/webkit/ContentLoader.java35
-rw-r--r--core/java/android/webkit/CookieManager.java136
-rw-r--r--core/java/android/webkit/CookieSyncManager.java2
-rw-r--r--core/java/android/webkit/DataLoader.java32
-rw-r--r--core/java/android/webkit/DateSorter.java39
-rw-r--r--core/java/android/webkit/EventLogTags.logtags11
-rw-r--r--core/java/android/webkit/FileLoader.java117
-rw-r--r--core/java/android/webkit/FrameLoader.java80
-rwxr-xr-xcore/java/android/webkit/GeolocationPermissions.java91
-rw-r--r--core/java/android/webkit/GoogleLocationSettingManager.java209
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java95
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java113
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java44
-rw-r--r--core/java/android/webkit/JsResult.java5
-rw-r--r--core/java/android/webkit/LoadListener.java403
-rw-r--r--core/java/android/webkit/MimeTypeMap.java11
-rw-r--r--core/java/android/webkit/Network.java34
-rw-r--r--core/java/android/webkit/PluginActivity.java67
-rw-r--r--core/java/android/webkit/PluginFullScreenHolder.java120
-rw-r--r--core/java/android/webkit/PluginManager.java65
-rw-r--r--core/java/android/webkit/PluginUtil.java59
-rw-r--r--core/java/android/webkit/SslErrorHandler.java35
-rw-r--r--core/java/android/webkit/StreamLoader.java71
-rw-r--r--core/java/android/webkit/URLUtil.java27
-rw-r--r--core/java/android/webkit/ViewManager.java211
-rw-r--r--core/java/android/webkit/WebBackForwardList.java13
-rw-r--r--core/java/android/webkit/WebBackForwardListClient.java40
-rw-r--r--core/java/android/webkit/WebChromeClient.java28
-rw-r--r--core/java/android/webkit/WebHistoryItem.java24
-rw-r--r--core/java/android/webkit/WebIconDatabase.java81
-rw-r--r--core/java/android/webkit/WebSettings.java107
-rw-r--r--core/java/android/webkit/WebStorage.java18
-rw-r--r--core/java/android/webkit/WebTextView.java311
-rw-r--r--core/java/android/webkit/WebView.java3071
-rw-r--r--core/java/android/webkit/WebViewClient.java4
-rw-r--r--core/java/android/webkit/WebViewCore.java542
-rw-r--r--core/java/android/webkit/WebViewDatabase.java380
-rw-r--r--core/java/android/webkit/WebViewWorker.java223
-rw-r--r--core/java/android/widget/AbsListView.java928
-rw-r--r--core/java/android/widget/AbsSeekBar.java38
-rw-r--r--core/java/android/widget/AbsSpinner.java46
-rw-r--r--core/java/android/widget/AbsoluteLayout.java4
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java26
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java315
-rw-r--r--core/java/android/widget/BaseExpandableListAdapter.java38
-rw-r--r--core/java/android/widget/CheckedTextView.java10
-rw-r--r--core/java/android/widget/DatePicker.java16
-rw-r--r--core/java/android/widget/DateTimeView.java263
-rw-r--r--core/java/android/widget/EditText.java8
-rw-r--r--core/java/android/widget/ExpandableListAdapter.java3
-rw-r--r--core/java/android/widget/ExpandableListConnector.java44
-rw-r--r--core/java/android/widget/ExpandableListView.java127
-rw-r--r--core/java/android/widget/FastScroller.java14
-rw-r--r--core/java/android/widget/FrameLayout.java18
-rw-r--r--core/java/android/widget/GridView.java31
-rw-r--r--core/java/android/widget/HeaderViewListAdapter.java105
-rw-r--r--core/java/android/widget/HeterogeneousExpandableList.java109
-rw-r--r--core/java/android/widget/HorizontalScrollView.java243
-rw-r--r--core/java/android/widget/ImageView.java50
-rw-r--r--core/java/android/widget/LinearLayout.java242
-rw-r--r--core/java/android/widget/ListAdapter.java3
-rw-r--r--core/java/android/widget/ListView.java310
-rw-r--r--core/java/android/widget/MediaController.java4
-rw-r--r--core/java/android/widget/NumberPicker.java (renamed from core/java/com/android/internal/widget/NumberPicker.java)259
-rw-r--r--core/java/android/widget/NumberPickerButton.java (renamed from core/java/com/android/internal/widget/NumberPickerButton.java)20
-rw-r--r--core/java/android/widget/PopupWindow.java96
-rw-r--r--core/java/android/widget/ProgressBar.java19
-rw-r--r--core/java/android/widget/QuickContactBadge.java34
-rw-r--r--core/java/android/widget/RelativeLayout.java21
-rw-r--r--core/java/android/widget/RemoteViews.java40
-rw-r--r--core/java/android/widget/ScrollView.java240
-rw-r--r--core/java/android/widget/Scroller.java7
-rw-r--r--core/java/android/widget/SimpleAdapter.java22
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java31
-rw-r--r--core/java/android/widget/SlidingDrawer.java10
-rw-r--r--core/java/android/widget/Spinner.java30
-rw-r--r--core/java/android/widget/TabHost.java17
-rw-r--r--core/java/android/widget/TabWidget.java168
-rw-r--r--core/java/android/widget/TableLayout.java26
-rw-r--r--core/java/android/widget/TableRow.java14
-rw-r--r--core/java/android/widget/TextView.java46
-rw-r--r--core/java/android/widget/TimePicker.java3
-rw-r--r--core/java/android/widget/VideoView.java114
-rw-r--r--core/java/android/widget/ViewFlipper.java2
-rw-r--r--core/java/android/widget/ViewSwitcher.java2
-rw-r--r--core/java/android/widget/ZoomButtonsController.java2
-rw-r--r--core/java/com/android/internal/app/AlertController.java42
-rw-r--r--core/java/com/android/internal/app/DisableCarModeActivity.java43
-rw-r--r--core/java/com/android/internal/app/ExternalMediaFormatActivity.java4
-rwxr-xr-xcore/java/com/android/internal/app/IMediaContainerService.aidl31
-rwxr-xr-xcore/java/com/android/internal/app/NetInitiatedActivity.java1
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java54
-rw-r--r--core/java/com/android/internal/app/RingtonePickerActivity.java1
-rw-r--r--core/java/com/android/internal/app/ShutdownThread.java130
-rw-r--r--core/java/com/android/internal/app/UsbStorageActivity.java124
-rw-r--r--core/java/com/android/internal/app/UsbStorageStopActivity.java123
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl17
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java35
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java192
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java303
-rw-r--r--core/java/com/android/internal/content/SelectionBuilder.java125
-rw-r--r--core/java/com/android/internal/content/SyncStateContentProviderHelper.java13
-rw-r--r--core/java/com/android/internal/database/ArrayListCursor.java171
-rw-r--r--core/java/com/android/internal/http/HttpDateTime.java (renamed from core/java/android/webkit/HttpDateTime.java)15
-rw-r--r--core/java/com/android/internal/logging/AndroidHandler.java65
-rw-r--r--core/java/com/android/internal/net/DNParser.java450
-rw-r--r--core/java/com/android/internal/net/DbSSLSessionCache.java289
-rw-r--r--core/java/com/android/internal/net/DomainNameValidator.java (renamed from core/java/android/net/http/DomainNameChecker.java)88
-rw-r--r--core/java/com/android/internal/os/AtomicFile.java5
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java215
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java7
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java16
-rw-r--r--core/java/com/android/internal/os/IDropBoxManagerService.aidl42
-rw-r--r--core/java/com/android/internal/os/LoggingPrintStream.java81
-rwxr-xr-xcore/java/com/android/internal/os/PkgUsageStats.java16
-rw-r--r--core/java/com/android/internal/os/RecoverySystem.java128
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java217
-rw-r--r--core/java/com/android/internal/os/SamplingProfilerIntegration.java16
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java7
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java16
-rw-r--r--core/java/com/android/internal/service/wallpaper/ImageWallpaper.java18
-rw-r--r--core/java/com/android/internal/util/HanziToPinyin.java495
-rw-r--r--core/java/com/android/internal/util/HierarchicalState.java76
-rw-r--r--core/java/com/android/internal/util/HierarchicalStateMachine.java1307
-rw-r--r--core/java/com/android/internal/util/JournaledFile.java107
-rw-r--r--core/java/com/android/internal/util/ProcessedMessages.java198
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java99
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java19
-rw-r--r--core/java/com/android/internal/view/BaseSurfaceHolder.java16
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java16
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java16
-rw-r--r--core/java/com/android/internal/view/WindowManagerPolicyThread.java41
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java2
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuView.java11
-rw-r--r--core/java/com/android/internal/view/menu/MenuDialogHelper.java18
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java16
-rw-r--r--core/java/com/android/internal/widget/ContactHeaderWidget.java45
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java4
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java413
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java52
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboard.java268
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java265
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardView.java39
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java361
-rw-r--r--core/java/com/android/internal/widget/SlidingTab.java12
-rw-r--r--core/java/com/android/internal/widget/VerticalTextSpinner.java467
-rw-r--r--core/java/com/android/internal/widget/WeightedLinearLayout.java84
-rw-r--r--core/java/com/google/android/collect/Sets.java79
-rw-r--r--core/java/com/google/android/gdata/client/AndroidGDataClient.java508
-rw-r--r--core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java31
-rw-r--r--core/java/com/google/android/gdata/client/QueryParamsImpl.java102
-rw-r--r--core/java/com/google/android/gdata2/client/AndroidGDataClient.java603
-rw-r--r--core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java31
-rw-r--r--core/java/com/google/android/gdata2/client/QueryParamsImpl.java99
-rw-r--r--core/java/com/google/android/mms/pdu/PduPersister.java5
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java399
-rw-r--r--core/java/com/google/android/net/NetworkStatsEntity.java85
-rw-r--r--core/java/com/google/android/net/ParentalControl.java73
-rw-r--r--core/java/com/google/android/net/ParentalControlState.java56
-rw-r--r--core/java/com/google/android/net/SSLClientSessionCacheFactory.java62
-rw-r--r--core/java/com/google/android/net/UrlRules.java236
-rw-r--r--core/java/com/google/android/util/AbstractMessageParser.java17
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java281
-rw-r--r--core/java/com/google/android/util/SimplePullParser.java391
-rw-r--r--core/jni/Android.mk15
-rw-r--r--core/jni/AndroidRuntime.cpp50
-rw-r--r--core/jni/CursorWindow.cpp17
-rw-r--r--core/jni/CursorWindow.h5
-rw-r--r--core/jni/android/graphics/Bitmap.cpp50
-rw-r--r--core/jni/android/graphics/BitmapFactory.cpp52
-rw-r--r--core/jni/android/graphics/Canvas.cpp14
-rw-r--r--core/jni/android/graphics/Graphics.cpp12
-rw-r--r--core/jni/android/graphics/GraphicsJNI.h13
-rw-r--r--core/jni/android/graphics/Path.cpp4
-rw-r--r--core/jni/android/graphics/YuvToJpegEncoder.cpp250
-rw-r--r--core/jni/android/graphics/YuvToJpegEncoder.h74
-rw-r--r--core/jni/android/opengl/util.cpp309
-rw-r--r--core/jni/android_backup_BackupDataInput.cpp14
-rw-r--r--core/jni/android_backup_BackupDataOutput.cpp2
-rw-r--r--core/jni/android_backup_BackupHelperDispatcher.cpp16
-rw-r--r--core/jni/android_backup_FileBackupHelperBase.cpp2
-rw-r--r--core/jni/android_bluetooth_HeadsetBase.cpp25
-rw-r--r--core/jni/android_bluetooth_ScoSocket.cpp193
-rw-r--r--core/jni/android_bluetooth_common.h2
-rw-r--r--core/jni/android_database_SQLiteCompiledSql.cpp134
-rw-r--r--core/jni/android_database_SQLiteDatabase.cpp99
-rw-r--r--core/jni/android_database_SQLiteDebug.cpp60
-rw-r--r--core/jni/android_database_SQLiteProgram.cpp65
-rw-r--r--core/jni/android_graphics_PixelFormat.cpp26
-rw-r--r--core/jni/android_hardware_Camera.cpp36
-rw-r--r--core/jni/android_hardware_SensorManager.cpp1
-rwxr-xr-xcore/jni/android_location_GpsLocationProvider.cpp49
-rw-r--r--core/jni/android_media_AudioRecord.cpp30
-rw-r--r--core/jni/android_media_AudioSystem.cpp6
-rw-r--r--core/jni/android_net_LocalSocketImpl.cpp2
-rw-r--r--core/jni/android_net_TrafficStats.cpp173
-rw-r--r--core/jni/android_net_wifi_Wifi.cpp4
-rw-r--r--core/jni/android_opengl_GLES11.cpp41
-rw-r--r--core/jni/android_opengl_GLES11Ext.cpp79
-rw-r--r--core/jni/android_opengl_GLES20.cpp5023
-rw-r--r--core/jni/android_os_Hardware.cpp62
-rw-r--r--core/jni/android_os_MemoryFile.cpp8
-rw-r--r--core/jni/android_server_BluetoothA2dpService.cpp73
-rw-r--r--core/jni/android_server_BluetoothEventLoop.cpp14
-rw-r--r--core/jni/android_server_BluetoothService.cpp21
-rw-r--r--core/jni/android_text_AndroidBidi.cpp81
-rw-r--r--core/jni/android_text_AndroidCharacter.cpp106
-rw-r--r--core/jni/android_text_format_Time.cpp4
-rw-r--r--core/jni/android_util_AssetManager.cpp91
-rw-r--r--core/jni/android_util_Base64.cpp160
-rw-r--r--core/jni/android_util_Binder.cpp139
-rw-r--r--core/jni/android_util_EventLog.cpp312
-rw-r--r--core/jni/android_util_FileObserver.cpp28
-rw-r--r--core/jni/android_util_Log.cpp20
-rw-r--r--core/jni/android_util_Process.cpp49
-rw-r--r--core/jni/android_util_StringBlock.cpp5
-rw-r--r--core/jni/android_view_Display.cpp2
-rw-r--r--core/jni/android_view_Surface.cpp98
-rw-r--r--core/jni/com_google_android_gles_jni_EGLImpl.cpp2
-rw-r--r--core/jni/com_google_android_gles_jni_GLImpl.cpp1133
-rw-r--r--core/res/Android.mk2
-rw-r--r--core/res/AndroidManifest.xml157
-rw-r--r--core/res/assets/images/combobox-disabled.pngbin566 -> 580 bytes
-rw-r--r--core/res/assets/images/combobox-noHighlight.pngbin655 -> 655 bytes
-rw-r--r--core/res/assets/webkit/togglePlugin.pngbin0 -> 1009 bytes
-rw-r--r--core/res/assets/webkit/youtube.html111
-rw-r--r--core/res/res/anim/cycle_interpolator.xml21
-rw-r--r--core/res/res/anim/lock_screen_enter.xml24
-rw-r--r--core/res/res/anim/lock_screen_exit.xml3
-rw-r--r--core/res/res/anim/wallpaper_close_enter.xml5
-rw-r--r--core/res/res/anim/wallpaper_open_enter.xml2
-rw-r--r--core/res/res/anim/wallpaper_open_exit.xml5
-rw-r--r--core/res/res/color/primary_text_dark.xml2
-rw-r--r--core/res/res/color/primary_text_light.xml2
-rw-r--r--core/res/res/color/secondary_text_dark.xml2
-rw-r--r--core/res/res/color/secondary_text_light.xml2
-rw-r--r--core/res/res/color/secondary_text_nofocus.xml20
-rw-r--r--core/res/res/color/tertiary_text_dark.xml2
-rw-r--r--core/res/res/color/tertiary_text_light.xml2
-rwxr-xr-xcore/res/res/drawable-en-hdpi/sym_keyboard_delete.pngbin0 -> 2315 bytes
-rwxr-xr-xcore/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.pngbin0 -> 1278 bytes
-rw-r--r--core/res/res/drawable-en-mdpi/sym_keyboard_delete.pngbin0 -> 1366 bytes
-rw-r--r--core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.pngbin0 -> 524 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_dropdown_disabled.9.pngbin0 -> 1374 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.pngbin0 -> 1775 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.pngbin0 -> 1649 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.pngbin0 -> 1979 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.pngbin0 -> 1986 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.pngbin0 -> 1696 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.pngbin0 -> 2048 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.pngbin0 -> 2033 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_normal.9.pngbin674 -> 715 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_normal_off.9.pngbin968 -> 1001 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_normal_on.9.pngbin1039 -> 1077 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_pressed.9.pngbin638 -> 745 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.pngbin911 -> 1042 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.pngbin979 -> 1105 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.pngbin0 -> 1051 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.pngbin0 -> 1115 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.pngbin0 -> 1101 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.pngbin0 -> 1134 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_default.9.pngbin711 -> 712 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.pngbin1355 -> 2588 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_selected.9.pngbin1380 -> 2596 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.pngbin914 -> 1040 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.pngbin1555 -> 1680 bytes
-rw-r--r--core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.pngbin1607 -> 1712 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_btn_search_go.pngbin0 -> 1338 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_jog_dial_vibrate_on.pngbin0 -> 8351 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_lock_idle_alarm.pngbin1375 -> 1197 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_lock_silent_mode.pngbin2242 -> 1330 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_lock_silent_mode_off.pngbin2548 -> 1765 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_lock_silent_mode_vibrate.pngbin0 -> 2558 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_search_category_default.pngbin3225 -> 3808 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_vibrate.pngbin6311 -> 2996 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_vibrate_small.pngbin0 -> 1560 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_volume.pngbin4875 -> 2279 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_volume_off.pngbin4010 -> 1813 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_volume_off_small.pngbin1455 -> 666 bytes
-rw-r--r--core/res/res/drawable-hdpi/ic_volume_small.pngbin1727 -> 1049 bytes
-rw-r--r--core/res/res/drawable-hdpi/keyboard_key_feedback_background.9.pngbin1853 -> 1372 bytes
-rw-r--r--core/res/res/drawable-hdpi/keyboard_key_feedback_more_background.9.pngbin2066 -> 1637 bytes
-rw-r--r--core/res/res/drawable-hdpi/password_field_default.9.pngbin0 -> 1213 bytes
-rw-r--r--core/res/res/drawable-hdpi/pickerbox_background.pngbin1131 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/pickerbox_selected.9.pngbin2129 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/pickerbox_unselected.9.pngbin1419 -> 0 bytes
-rwxr-xr-x[-rw-r--r--]core/res/res/drawable-hdpi/progressbar_indeterminate1.pngbin779 -> 474 bytes
-rwxr-xr-x[-rw-r--r--]core/res/res/drawable-hdpi/progressbar_indeterminate2.pngbin775 -> 472 bytes
-rwxr-xr-x[-rw-r--r--]core/res/res/drawable-hdpi/progressbar_indeterminate3.pngbin779 -> 469 bytes
-rw-r--r--core/res/res/drawable-hdpi/recent_dialog_background.9.pngbin0 -> 352 bytes
-rw-r--r--core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.pngbin491 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.pngbin489 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.pngbin486 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.pngbin451 -> 0 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_notify_car_mode.pngbin0 -> 2371 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_adb.pngbin0 -> 1008 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_secure.pngbin0 -> 923 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.pngbin0 -> 2503 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_tether_general.pngbin0 -> 2639 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_tether_usb.pngbin0 -> 2508 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_tether_wifi.pngbin0 -> 2543 bytes
-rw-r--r--core/res/res/drawable-hdpi/stat_sys_throttled.pngbin0 -> 1187 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_app_on_sd_unavailable_icon.pngbin0 -> 3342 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_delete.pngbin0 -> 2282 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_delete_dim.pngbin0 -> 2171 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_feedback_delete.pngbin0 -> 1278 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.pngbin0 -> 845 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_feedback_return.pngbin0 -> 838 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_feedback_shift.pngbin0 -> 885 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.pngbin0 -> 700 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_feedback_space.pngbin0 -> 287 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.pngbin0 -> 1438 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num1.pngbin0 -> 809 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num2.pngbin0 -> 3214 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num3.pngbin0 -> 2805 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num4.pngbin0 -> 2647 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num5.pngbin0 -> 2536 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num6.pngbin0 -> 3573 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num7.pngbin0 -> 3684 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num8.pngbin0 -> 2904 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_num9.pngbin0 -> 3860 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_ok.pngbin0 -> 2260 bytes
-rw-r--r--core/res/res/drawable-hdpi/sym_keyboard_ok_dim.pngbin0 -> 2001 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_return.pngbin0 -> 1123 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_shift.pngbin0 -> 1495 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_shift_locked.pngbin0 -> 1119 bytes
-rwxr-xr-xcore/res/res/drawable-hdpi/sym_keyboard_space.pngbin0 -> 371 bytes
-rw-r--r--core/res/res/drawable-hdpi/textfield_search_empty_default.9.png (renamed from core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png)bin3053 -> 3711 bytes
-rw-r--r--core/res/res/drawable-hdpi/textfield_search_empty_pressed.9.pngbin0 -> 3965 bytes
-rw-r--r--core/res/res/drawable-hdpi/textfield_search_empty_selected.9.pngbin0 -> 3733 bytes
-rw-r--r--core/res/res/drawable-hdpi/usb_android.pngbin0 -> 6975 bytes
-rw-r--r--core/res/res/drawable-hdpi/usb_android_connected.pngbin0 -> 6642 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_dropdown_disabled.9.pngbin0 -> 901 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.pngbin0 -> 1118 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.pngbin0 -> 1068 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.pngbin0 -> 1259 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.pngbin0 -> 1286 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.pngbin0 -> 1021 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.pngbin0 -> 1204 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.pngbin0 -> 1224 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.pngbin0 -> 892 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.pngbin0 -> 948 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.pngbin0 -> 871 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.pngbin0 -> 903 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.pngbin3371 -> 741 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.pngbin3722 -> 1143 bytes
-rw-r--r--core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.pngbin3690 -> 1085 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_btn_search_go.pngbin0 -> 1046 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_jog_dial_vibrate_on.pngbin0 -> 4814 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_launcher_android.pngbin3019 -> 3779 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_lock_idle_alarm.pngbin924 -> 786 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_lock_silent_mode.pngbin1362 -> 1072 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_lock_silent_mode_off.pngbin1554 -> 1145 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_lock_silent_mode_vibrate.pngbin0 -> 1646 bytes
-rwxr-xr-xcore/res/res/drawable-mdpi/ic_search_category_default.pngbin1747 -> 5059 bytes
-rw-r--r--[-rwxr-xr-x]core/res/res/drawable-mdpi/ic_vibrate.pngbin4103 -> 2103 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_vibrate_small.pngbin0 -> 1074 bytes
-rw-r--r--[-rwxr-xr-x]core/res/res/drawable-mdpi/ic_volume.pngbin2669 -> 1476 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_volume_off.pngbin2462 -> 1045 bytes
-rw-r--r--[-rwxr-xr-x]core/res/res/drawable-mdpi/ic_volume_off_small.pngbin832 -> 509 bytes
-rw-r--r--[-rwxr-xr-x]core/res/res/drawable-mdpi/ic_volume_small.pngbin866 -> 792 bytes
-rw-r--r--core/res/res/drawable-mdpi/password_field_default.9.pngbin0 -> 784 bytes
-rw-r--r--core/res/res/drawable-mdpi/pickerbox_background.pngbin4226 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/pickerbox_selected.9.pngbin2155 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/pickerbox_unselected.9.pngbin1474 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/progressbar_indeterminate1.pngbin3678 -> 687 bytes
-rw-r--r--core/res/res/drawable-mdpi/progressbar_indeterminate2.pngbin3704 -> 624 bytes
-rw-r--r--core/res/res/drawable-mdpi/progressbar_indeterminate3.pngbin3752 -> 637 bytes
-rw-r--r--core/res/res/drawable-mdpi/recent_dialog_background.9.pngbin0 -> 314 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_notify_car_mode.pngbin0 -> 1304 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_notify_chat.pngbin2197 -> 1166 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_adb.pngbin0 -> 679 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_secure.pngbin0 -> 631 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_tether_bluetooth.pngbin0 -> 1519 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_tether_general.pngbin0 -> 1670 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_tether_usb.pngbin0 -> 1486 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_tether_wifi.pngbin0 -> 1602 bytes
-rw-r--r--core/res/res/drawable-mdpi/stat_sys_throttled.pngbin0 -> 770 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_app_on_sd_unavailable_icon.pngbin0 -> 5433 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_def_app_icon.pngbin4499 -> 2732 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_delete.pngbin0 -> 829 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_delete_dim.pngbin0 -> 1215 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.pngbin0 -> 374 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.pngbin0 -> 530 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_feedback_return.pngbin0 -> 381 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.pngbin0 -> 437 bytes
-rwxr-xr-xcore/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.pngbin0 -> 333 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_feedback_space.pngbin0 -> 223 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.pngbin0 -> 778 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num1.pngbin0 -> 506 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num2.pngbin0 -> 1778 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num3.pngbin0 -> 1676 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num4.pngbin0 -> 1540 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num5.pngbin0 -> 1417 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num6.pngbin0 -> 1952 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num7.pngbin0 -> 2051 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num8.pngbin0 -> 1605 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_num9.pngbin0 -> 2173 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_ok.pngbin0 -> 1367 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_ok_dim.pngbin0 -> 1224 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_return.pngbin0 -> 866 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_shift.pngbin0 -> 1017 bytes
-rwxr-xr-xcore/res/res/drawable-mdpi/sym_keyboard_shift_locked.pngbin0 -> 799 bytes
-rw-r--r--core/res/res/drawable-mdpi/sym_keyboard_space.pngbin0 -> 424 bytes
-rw-r--r--core/res/res/drawable-mdpi/textfield_search_empty_default.9.pngbin0 -> 818 bytes
-rw-r--r--core/res/res/drawable-mdpi/textfield_search_empty_pressed.9.pngbin0 -> 1056 bytes
-rw-r--r--core/res/res/drawable-mdpi/textfield_search_empty_selected.9.pngbin0 -> 837 bytes
-rw-r--r--core/res/res/drawable-mdpi/usb_android.pngbin0 -> 4600 bytes
-rw-r--r--core/res/res/drawable-mdpi/usb_android_connected.pngbin0 -> 4232 bytes
-rw-r--r--core/res/res/drawable/btn_circle.xml2
-rw-r--r--core/res/res/drawable/btn_default.xml4
-rw-r--r--core/res/res/drawable/btn_default_small.xml4
-rw-r--r--core/res/res/drawable/btn_dialog.xml4
-rw-r--r--core/res/res/drawable/btn_dropdown.xml24
-rw-r--r--core/res/res/drawable/btn_global_search.xml4
-rw-r--r--core/res/res/drawable/btn_keyboard_key_fulltrans.xml36
-rw-r--r--core/res/res/drawable/btn_search_dialog.xml2
-rw-r--r--core/res/res/drawable/btn_search_dialog_voice.xml2
-rw-r--r--core/res/res/drawable/edit_text.xml4
-rw-r--r--core/res/res/drawable/textfield_search.xml2
-rw-r--r--core/res/res/drawable/textfield_search_empty.xml31
-rw-r--r--core/res/res/layout-ja/contact_header_name.xml44
-rw-r--r--core/res/res/layout-land/icon_menu_layout.xml4
-rw-r--r--core/res/res/layout-land/usb_storage_activity.xml77
-rw-r--r--core/res/res/layout-port/icon_menu_layout.xml4
-rw-r--r--core/res/res/layout/activity_list.xml12
-rw-r--r--core/res/res/layout/activity_list_item.xml2
-rw-r--r--core/res/res/layout/activity_list_item_2.xml2
-rw-r--r--core/res/res/layout/alert_dialog.xml49
-rw-r--r--core/res/res/layout/alert_dialog_progress.xml4
-rw-r--r--core/res/res/layout/always_use_checkbox.xml2
-rw-r--r--core/res/res/layout/app_permission_item.xml2
-rwxr-xr-xcore/res/res/layout/app_perms_summary.xml18
-rw-r--r--core/res/res/layout/auto_complete_list.xml6
-rw-r--r--core/res/res/layout/battery_low.xml4
-rw-r--r--core/res/res/layout/battery_status.xml10
-rw-r--r--core/res/res/layout/character_picker.xml2
-rw-r--r--core/res/res/layout/contact_header.xml43
-rw-r--r--core/res/res/layout/date_picker.xml6
-rw-r--r--core/res/res/layout/dialog_custom_title.xml8
-rw-r--r--core/res/res/layout/dialog_title.xml8
-rw-r--r--core/res/res/layout/dialog_title_icons.xml8
-rw-r--r--core/res/res/layout/expandable_list_content.xml4
-rw-r--r--core/res/res/layout/global_actions_item.xml8
-rw-r--r--core/res/res/layout/google_web_content_helper_layout.xml60
-rw-r--r--core/res/res/layout/grant_credentials_permission.xml182
-rw-r--r--core/res/res/layout/icon_menu_item_layout.xml4
-rw-r--r--core/res/res/layout/input_method.xml10
-rw-r--r--core/res/res/layout/input_method_extract_view.xml4
-rw-r--r--core/res/res/layout/js_prompt.xml6
-rw-r--r--core/res/res/layout/keyboard_popup_keyboard.xml4
-rw-r--r--core/res/res/layout/keyguard_screen_glogin_unlock.xml22
-rw-r--r--core/res/res/layout/keyguard_screen_lock.xml34
-rw-r--r--core/res/res/layout/keyguard_screen_password_landscape.xml83
-rw-r--r--core/res/res/layout/keyguard_screen_password_portrait.xml93
-rw-r--r--core/res/res/layout/keyguard_screen_rotary_unlock.xml138
-rw-r--r--core/res/res/layout/keyguard_screen_rotary_unlock_land.xml145
-rw-r--r--core/res/res/layout/keyguard_screen_sim_pin_landscape.xml12
-rw-r--r--core/res/res/layout/keyguard_screen_sim_pin_portrait.xml259
-rw-r--r--core/res/res/layout/keyguard_screen_tab_unlock.xml49
-rw-r--r--core/res/res/layout/keyguard_screen_tab_unlock_land.xml61
-rw-r--r--core/res/res/layout/keyguard_screen_unlock_landscape.xml34
-rw-r--r--core/res/res/layout/keyguard_screen_unlock_portrait.xml42
-rw-r--r--core/res/res/layout/list_content.xml4
-rw-r--r--core/res/res/layout/list_gestures_overlay.xml4
-rw-r--r--core/res/res/layout/list_menu_item_layout.xml4
-rw-r--r--core/res/res/layout/media_controller.xml8
-rw-r--r--core/res/res/layout/number_picker.xml10
-rw-r--r--core/res/res/layout/permissions_account_and_authtokentype.xml67
-rw-r--r--core/res/res/layout/permissions_package_list_item.xml45
-rw-r--r--core/res/res/layout/power_dialog.xml12
-rw-r--r--core/res/res/layout/preference.xml6
-rw-r--r--core/res/res/layout/preference_child.xml4
-rw-r--r--core/res/res/layout/preference_dialog_edittext.xml32
-rw-r--r--core/res/res/layout/preference_information.xml4
-rw-r--r--core/res/res/layout/preference_list_content.xml4
-rw-r--r--core/res/res/layout/progress_dialog.xml8
-rw-r--r--core/res/res/layout/recent_apps_dialog.xml52
-rw-r--r--core/res/res/layout/recent_apps_icon.xml11
-rw-r--r--core/res/res/layout/resolve_list_item.xml4
-rw-r--r--core/res/res/layout/screen.xml14
-rw-r--r--core/res/res/layout/screen_custom_title.xml4
-rw-r--r--core/res/res/layout/screen_progress.xml14
-rw-r--r--core/res/res/layout/screen_title.xml8
-rw-r--r--core/res/res/layout/screen_title_icons.xml35
-rw-r--r--core/res/res/layout/search_bar.xml21
-rw-r--r--core/res/res/layout/search_dropdown_item_1line.xml2
-rw-r--r--core/res/res/layout/search_dropdown_item_icons_2line.xml6
-rw-r--r--core/res/res/layout/seekbar_dialog.xml6
-rw-r--r--core/res/res/layout/select_dialog.xml4
-rw-r--r--core/res/res/layout/select_dialog_item.xml2
-rw-r--r--core/res/res/layout/select_dialog_multichoice.xml2
-rw-r--r--core/res/res/layout/select_dialog_singlechoice.xml2
-rw-r--r--core/res/res/layout/simple_dropdown_hint.xml2
-rw-r--r--core/res/res/layout/simple_dropdown_item_1line.xml2
-rw-r--r--core/res/res/layout/simple_dropdown_item_2line.xml6
-rw-r--r--core/res/res/layout/simple_expandable_list_item_1.xml2
-rw-r--r--core/res/res/layout/simple_expandable_list_item_2.xml6
-rw-r--r--core/res/res/layout/simple_list_item_1.xml2
-rw-r--r--core/res/res/layout/simple_list_item_2.xml6
-rw-r--r--core/res/res/layout/simple_list_item_checked.xml2
-rw-r--r--core/res/res/layout/simple_list_item_multiple_choice.xml2
-rw-r--r--core/res/res/layout/simple_list_item_single_choice.xml2
-rw-r--r--core/res/res/layout/simple_spinner_dropdown_item.xml2
-rw-r--r--core/res/res/layout/simple_spinner_item.xml2
-rw-r--r--core/res/res/layout/status_bar.xml20
-rw-r--r--core/res/res/layout/status_bar_expanded.xml31
-rw-r--r--core/res/res/layout/status_bar_icon.xml4
-rw-r--r--core/res/res/layout/status_bar_latest_event.xml6
-rw-r--r--core/res/res/layout/status_bar_latest_event_content.xml12
-rw-r--r--core/res/res/layout/status_bar_tracking.xml6
-rw-r--r--core/res/res/layout/tab_content.xml8
-rw-r--r--core/res/res/layout/test_list_item.xml2
-rw-r--r--core/res/res/layout/textview_hint.xml4
-rw-r--r--core/res/res/layout/time_picker.xml4
-rw-r--r--core/res/res/layout/transient_notification.xml4
-rw-r--r--core/res/res/layout/twelve_key_entry.xml181
-rw-r--r--core/res/res/layout/two_line_list_item.xml6
-rw-r--r--core/res/res/layout/usb_storage_activity.xml68
-rw-r--r--core/res/res/values-cs/donottranslate-cldr.xml2
-rw-r--r--core/res/res/values-cs/strings.xml145
-rw-r--r--core/res/res/values-da/strings.xml151
-rw-r--r--core/res/res/values-de/strings.xml153
-rw-r--r--core/res/res/values-el/strings.xml145
-rw-r--r--core/res/res/values-en-rUS/donottranslate-names.xml143
-rw-r--r--core/res/res/values-es-rUS/strings.xml161
-rw-r--r--core/res/res/values-es/strings.xml153
-rw-r--r--core/res/res/values-fr/strings.xml157
-rw-r--r--core/res/res/values-it/strings.xml149
-rw-r--r--core/res/res/values-ja/strings.xml147
-rw-r--r--core/res/res/values-ko/strings.xml259
-rw-r--r--core/res/res/values-land/dimens.xml24
-rw-r--r--core/res/res/values-mcc204-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc204-da/strings.xml2
-rw-r--r--core/res/res/values-mcc204-de/strings.xml2
-rw-r--r--core/res/res/values-mcc204-el/strings.xml2
-rw-r--r--core/res/res/values-mcc204-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc204-es/strings.xml2
-rw-r--r--core/res/res/values-mcc204-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc204-it/strings.xml2
-rw-r--r--core/res/res/values-mcc204-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc204-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc204-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc204-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc204-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc204-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc204-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc204-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc204-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc204-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc204-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-mcc230-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc230-da/strings.xml2
-rw-r--r--core/res/res/values-mcc230-de/strings.xml2
-rw-r--r--core/res/res/values-mcc230-el/strings.xml2
-rw-r--r--core/res/res/values-mcc230-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc230-es/strings.xml2
-rw-r--r--core/res/res/values-mcc230-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc230-it/strings.xml2
-rw-r--r--core/res/res/values-mcc230-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc230-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc230-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc230-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc230-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc230-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc230-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc230-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc230-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc230-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc230-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-mcc232-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc232-da/strings.xml2
-rw-r--r--core/res/res/values-mcc232-de/strings.xml2
-rw-r--r--core/res/res/values-mcc232-el/strings.xml2
-rw-r--r--core/res/res/values-mcc232-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc232-es/strings.xml2
-rw-r--r--core/res/res/values-mcc232-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc232-it/strings.xml2
-rw-r--r--core/res/res/values-mcc232-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc232-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc232-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc232-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc232-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc232-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc232-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc232-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc232-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc232-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc232-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-mcc234-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc234-da/strings.xml2
-rw-r--r--core/res/res/values-mcc234-de/strings.xml2
-rw-r--r--core/res/res/values-mcc234-el/strings.xml2
-rw-r--r--core/res/res/values-mcc234-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc234-es/strings.xml2
-rw-r--r--core/res/res/values-mcc234-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc234-it/strings.xml2
-rw-r--r--core/res/res/values-mcc234-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc234-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc234-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc234-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc234-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc234-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc234-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc234-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc234-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc234-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc234-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-mcc260-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc260-da/strings.xml2
-rw-r--r--core/res/res/values-mcc260-de/strings.xml2
-rw-r--r--core/res/res/values-mcc260-el/strings.xml2
-rw-r--r--core/res/res/values-mcc260-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc260-es/strings.xml2
-rw-r--r--core/res/res/values-mcc260-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc260-it/strings.xml2
-rw-r--r--core/res/res/values-mcc260-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc260-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc260-nb/strings.xml19
-rw-r--r--core/res/res/values-mcc260-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc260-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc260-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc260-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc260-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc260-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc260-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc260-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc260-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-mcc262-cs/strings.xml2
-rw-r--r--core/res/res/values-mcc262-da/strings.xml2
-rw-r--r--core/res/res/values-mcc262-de/strings.xml2
-rw-r--r--core/res/res/values-mcc262-el/strings.xml2
-rw-r--r--core/res/res/values-mcc262-es-rUS/strings.xml2
-rw-r--r--core/res/res/values-mcc262-es/strings.xml2
-rw-r--r--core/res/res/values-mcc262-fr/strings.xml2
-rw-r--r--core/res/res/values-mcc262-it/strings.xml2
-rw-r--r--core/res/res/values-mcc262-ja/strings.xml2
-rw-r--r--core/res/res/values-mcc262-ko/strings.xml2
-rw-r--r--core/res/res/values-mcc262-nl/strings.xml2
-rw-r--r--core/res/res/values-mcc262-pl/strings.xml2
-rw-r--r--core/res/res/values-mcc262-pt-rPT/strings.xml2
-rw-r--r--core/res/res/values-mcc262-pt/strings.xml2
-rw-r--r--core/res/res/values-mcc262-ru/strings.xml2
-rw-r--r--core/res/res/values-mcc262-sv/strings.xml2
-rw-r--r--core/res/res/values-mcc262-tr/strings.xml2
-rw-r--r--core/res/res/values-mcc262-zh-rCN/strings.xml2
-rw-r--r--core/res/res/values-mcc262-zh-rTW/strings.xml2
-rw-r--r--core/res/res/values-nb/strings.xml147
-rw-r--r--core/res/res/values-nl/strings.xml151
-rw-r--r--core/res/res/values-pl/strings.xml149
-rw-r--r--core/res/res/values-port-mdpi/donottranslate.xml23
-rw-r--r--core/res/res/values-pt-rPT/strings.xml153
-rw-r--r--core/res/res/values-pt/strings.xml161
-rw-r--r--core/res/res/values-ru/strings.xml173
-rw-r--r--core/res/res/values-sv/strings.xml145
-rw-r--r--core/res/res/values-tr/strings.xml145
-rw-r--r--core/res/res/values-zh-rCN/strings.xml153
-rw-r--r--core/res/res/values-zh-rTW/strings.xml153
-rw-r--r--core/res/res/values/arrays.xml24
-rwxr-xr-xcore/res/res/values/attrs.xml103
-rw-r--r--core/res/res/values/attrs_manifest.xml92
-rw-r--r--core/res/res/values/colors.xml8
-rw-r--r--core/res/res/values/config.xml125
-rw-r--r--core/res/res/values/dimens.xml22
-rw-r--r--core/res/res/values/donottranslate.xml2
-rw-r--r--core/res/res/values/ids.xml1
-rw-r--r--core/res/res/values/public.xml26
-rw-r--r--core/res/res/values/strings.xml277
-rw-r--r--core/res/res/values/styles.xml22
-rw-r--r--core/res/res/values/themes.xml8
-rwxr-xr-xcore/res/res/xml-land/password_kbd_qwerty.xml87
-rwxr-xr-xcore/res/res/xml-land/password_kbd_qwerty_shifted.xml88
-rwxr-xr-xcore/res/res/xml-mdpi/password_kbd_qwerty.xml89
-rwxr-xr-xcore/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml88
-rwxr-xr-xcore/res/res/xml/password_kbd_extension.xml54
-rwxr-xr-xcore/res/res/xml/password_kbd_numeric.xml53
-rw-r--r--core/res/res/xml/password_kbd_popup_template.xml27
-rwxr-xr-xcore/res/res/xml/password_kbd_qwerty.xml102
-rwxr-xr-xcore/res/res/xml/password_kbd_qwerty_shifted.xml101
-rwxr-xr-xcore/res/res/xml/password_kbd_symbols.xml90
-rwxr-xr-xcore/res/res/xml/password_kbd_symbols_shift.xml86
-rw-r--r--core/tests/ConnectivityManagerTest/Android.mk30
-rw-r--r--core/tests/ConnectivityManagerTest/AndroidManifest.xml50
-rw-r--r--core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java403
-rw-r--r--core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java59
-rw-r--r--core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java193
-rw-r--r--core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java539
-rw-r--r--core/tests/coretests/Android.mk23
-rw-r--r--core/tests/coretests/AndroidManifest.xml1210
-rw-r--r--core/tests/coretests/DisabledTestApp/Android.mk12
-rw-r--r--core/tests/coretests/DisabledTestApp/AndroidManifest.xml28
-rw-r--r--core/tests/coretests/DisabledTestApp/src/com/android/frameworks/coretests/disabled_app/EnabledActivity.java27
-rw-r--r--core/tests/coretests/EnabledTestApp/Android.mk12
-rw-r--r--core/tests/coretests/EnabledTestApp/AndroidManifest.xml52
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledActivity.java27
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledProvider.java54
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledReceiver.java32
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledService.java32
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledActivity.java27
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledProvider.java54
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledReceiver.java32
-rw-r--r--core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledService.java32
-rw-r--r--core/tests/coretests/apks/Android.mk5
-rw-r--r--core/tests/coretests/apks/install_decl_perm/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml36
-rw-r--r--core/tests/coretests/apks/install_decl_perm/res/values/strings.xml6
-rw-r--r--core/tests/coretests/apks/install_loc_auto/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/apks/install_loc_auto/res/values/strings.xml6
-rw-r--r--core/tests/coretests/apks/install_loc_internal/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/apks/install_loc_internal/res/values/strings.xml6
-rw-r--r--core/tests/coretests/apks/install_loc_sdcard/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml22
-rw-r--r--core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml6
-rw-r--r--core/tests/coretests/apks/install_loc_unspecified/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml21
-rw-r--r--core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml6
-rw-r--r--core/tests/coretests/apks/install_use_perm_good/Android.mk11
-rw-r--r--core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml25
-rw-r--r--core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml6
-rw-r--r--core/tests/coretests/assets/text.txt1
-rw-r--r--core/tests/coretests/res/drawable-hdpi/big_drawable_background.9.pngbin0 -> 430 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/black_square.pngbin0 -> 116 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/black_square_stretchable.9.pngbin0 -> 135 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/drawable_background.9.pngbin0 -> 354 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/sym_now_playing_pause_1.pngbin0 -> 668 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_backward_1.pngbin0 -> 1296 bytes
-rw-r--r--core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_forward_1.pngbin0 -> 1247 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/big_drawable_background.9.pngbin0 -> 330 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/black_square.pngbin0 -> 151 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/black_square_stretchable.9.pngbin0 -> 175 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/drawable_background.9.pngbin0 -> 270 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/sym_now_playing_pause_1.png (renamed from core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png)bin3037 -> 3038 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_backward_1.png (renamed from core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png)bin3020 -> 3262 bytes
-rw-r--r--core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_forward_1.png (renamed from core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png)bin3052 -> 3275 bytes
-rw-r--r--core/tests/coretests/res/drawable/bitmap_drawable.xml19
-rw-r--r--core/tests/coretests/res/drawable/box.xml (renamed from core/res/res/drawable/spinnerbox_arrows.xml)15
-rw-r--r--core/tests/coretests/res/layout/add_column_in_table.xml46
-rw-r--r--core/tests/coretests/res/layout/autocompletetextview_simple.xml34
-rw-r--r--core/tests/coretests/res/layout/baseline_0width_and_weight.xml49
-rw-r--r--core/tests/coretests/res/layout/baseline_buttons.xml68
-rw-r--r--core/tests/coretests/res/layout/baseline_center_gravity.xml39
-rw-r--r--core/tests/coretests/res/layout/brightness_limit.xml29
-rw-r--r--core/tests/coretests/res/layout/descendant_focusability.xml66
-rw-r--r--core/tests/coretests/res/layout/disabled.xml43
-rw-r--r--core/tests/coretests/res/layout/drawable_background_minimum_size.xml93
-rw-r--r--core/tests/coretests/res/layout/fill_in_wrap.xml64
-rw-r--r--core/tests/coretests/res/layout/focus_after_removal.xml60
-rw-r--r--core/tests/coretests/res/layout/focus_listener.xml38
-rw-r--r--core/tests/coretests/res/layout/framelayout_gravity.xml76
-rw-r--r--core/tests/coretests/res/layout/framelayout_margin.xml50
-rw-r--r--core/tests/coretests/res/layout/grid_in_horizontal.xml38
-rw-r--r--core/tests/coretests/res/layout/grid_in_vertical.xml42
-rw-r--r--core/tests/coretests/res/layout/grid_padding.xml24
-rw-r--r--core/tests/coretests/res/layout/grid_scroll_listener.xml39
-rw-r--r--core/tests/coretests/res/layout/grid_thrasher.xml38
-rw-r--r--core/tests/coretests/res/layout/include_button.xml21
-rw-r--r--core/tests/coretests/res/layout/include_button_with_size.xml21
-rw-r--r--core/tests/coretests/res/layout/include_tag.xml45
-rw-r--r--core/tests/coretests/res/layout/inflated_expandablelistview.xml20
-rw-r--r--core/tests/coretests/res/layout/layout_five.xml22
-rw-r--r--core/tests/coretests/res/layout/layout_four.xml20
-rw-r--r--core/tests/coretests/res/layout/layout_one.xml20
-rw-r--r--core/tests/coretests/res/layout/layout_six.xml27
-rw-r--r--core/tests/coretests/res/layout/layout_tag.xml23
-rw-r--r--core/tests/coretests/res/layout/layout_three.xml27
-rw-r--r--core/tests/coretests/res/layout/layout_two.xml23
-rw-r--r--core/tests/coretests/res/layout/linear_layout_buttons.xml48
-rw-r--r--core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml39
-rw-r--r--core/tests/coretests/res/layout/linear_layout_grid.xml101
-rw-r--r--core/tests/coretests/res/layout/linear_layout_listview_height.xml66
-rw-r--r--core/tests/coretests/res/layout/linear_layout_spinner_then_button.xml37
-rw-r--r--core/tests/coretests/res/layout/linear_layout_textviews.xml41
-rw-r--r--core/tests/coretests/res/layout/linear_layout_weight.xml55
-rw-r--r--core/tests/coretests/res/layout/list_dividers.xml32
-rw-r--r--core/tests/coretests/res/layout/list_filter.xml50
-rw-r--r--core/tests/coretests/res/layout/list_in_horizontal.xml29
-rw-r--r--core/tests/coretests/res/layout/list_in_vertical.xml46
-rw-r--r--core/tests/coretests/res/layout/list_recycler_profiling.xml32
-rw-r--r--core/tests/coretests/res/layout/list_scroll_listener.xml33
-rw-r--r--core/tests/coretests/res/layout/list_take_focus_from_side.xml113
-rw-r--r--core/tests/coretests/res/layout/list_thrasher.xml33
-rw-r--r--core/tests/coretests/res/layout/list_with_button_above.xml39
-rw-r--r--core/tests/coretests/res/layout/list_with_disappearing_item_bug_item.xml31
-rw-r--r--core/tests/coretests/res/layout/list_with_empty_view.xml36
-rw-r--r--core/tests/coretests/res/layout/longpress.xml32
-rw-r--r--core/tests/coretests/res/layout/mail_message.xml32
-rw-r--r--core/tests/coretests/res/layout/merge_child.xml34
-rw-r--r--core/tests/coretests/res/layout/merge_tag.xml41
-rw-r--r--core/tests/coretests/res/layout/popup_window_visibility.xml60
-rw-r--r--core/tests/coretests/res/layout/pre_draw_listener.xml41
-rw-r--r--core/tests/coretests/res/layout/radiogroup_checkedchild.xml44
-rw-r--r--core/tests/coretests/res/layout/remote_view_host.xml25
-rw-r--r--core/tests/coretests/res/layout/remote_view_test_bad_1.xml31
-rw-r--r--core/tests/coretests/res/layout/remote_view_test_bad_2.xml31
-rw-r--r--core/tests/coretests/res/layout/remote_view_test_good.xml59
-rw-r--r--core/tests/coretests/res/layout/scroll_to_rect_with_internal_scroll.xml49
-rw-r--r--core/tests/coretests/res/layout/scroll_to_rectangle.xml79
-rw-r--r--core/tests/coretests/res/layout/scrollview_linear_layout.xml32
-rw-r--r--core/tests/coretests/res/layout/scrollview_with_webviews.xml45
-rw-r--r--core/tests/coretests/res/layout/table_layout_cell_span.xml88
-rw-r--r--core/tests/coretests/res/layout/table_layout_fixed_width.xml56
-rw-r--r--core/tests/coretests/res/layout/table_layout_horizontal_gravity.xml82
-rw-r--r--core/tests/coretests/res/layout/table_layout_vertical_gravity.xml70
-rw-r--r--core/tests/coretests/res/layout/table_layout_weight.xml44
-rw-r--r--core/tests/coretests/res/layout/translucent_background.xml (renamed from core/res/res/layout/contact_header_name.xml)27
-rw-r--r--core/tests/coretests/res/layout/viewgroupchildren.xml29
-rw-r--r--core/tests/coretests/res/layout/viewstub.xml42
-rw-r--r--core/tests/coretests/res/layout/visibility.xml75
-rw-r--r--core/tests/coretests/res/layout/visibility_callback.xml80
-rw-r--r--core/tests/coretests/res/layout/weight_sum.xml34
-rw-r--r--core/tests/coretests/res/layout/with_bitmap_background.xml27
-rw-r--r--core/tests/coretests/res/layout/zero_sized.xml48
-rw-r--r--core/tests/coretests/res/raw/alt_ip_only.crt17
-rw-r--r--core/tests/coretests/res/raw/installbin0 -> 3078 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_cert1bin0 -> 4468 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_cert1_cert2bin0 -> 5619 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_cert2bin0 -> 4464 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_cert3bin0 -> 4464 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_cert3_cert4bin0 -> 5615 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app1_unsignedbin0 -> 2919 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app2_cert1bin0 -> 4476 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app2_cert1_cert2bin0 -> 5628 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app2_cert2bin0 -> 4457 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app2_cert3bin0 -> 4474 bytes
-rw-r--r--core/tests/coretests/res/raw/install_app2_unsignedbin0 -> 2925 bytes
-rw-r--r--core/tests/coretests/res/raw/install_decl_permbin0 -> 3378 bytes
-rw-r--r--core/tests/coretests/res/raw/install_loc_autobin0 -> 3102 bytes
-rw-r--r--core/tests/coretests/res/raw/install_loc_internalbin0 -> 3106 bytes
-rw-r--r--core/tests/coretests/res/raw/install_loc_sdcardbin0 -> 3102 bytes
-rw-r--r--core/tests/coretests/res/raw/install_loc_unspecifiedbin0 -> 3078 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared1_cert1bin0 -> 13348 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared1_cert1_cert2bin0 -> 14597 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared1_cert2bin0 -> 13345 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared1_unsignedbin0 -> 11597 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared2_cert1bin0 -> 13347 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared2_cert1_cert2bin0 -> 14598 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared2_cert2bin0 -> 13343 bytes
-rw-r--r--core/tests/coretests/res/raw/install_shared2_unsignedbin0 -> 11598 bytes
-rw-r--r--core/tests/coretests/res/raw/install_use_perm_goodbin0 -> 3222 bytes
-rw-r--r--core/tests/coretests/res/raw/medium.xml27
-rw-r--r--core/tests/coretests/res/raw/small.xml20
-rw-r--r--core/tests/coretests/res/raw/subject_alt_only.crt17
-rw-r--r--core/tests/coretests/res/raw/subject_only.crt18
-rw-r--r--core/tests/coretests/res/raw/subject_with_alt_names.crt19
-rw-r--r--core/tests/coretests/res/raw/subject_with_wild_alt_name.crt18
-rw-r--r--core/tests/coretests/res/raw/text.txt1
-rw-r--r--core/tests/coretests/res/raw/v21_backslash.vcf5
-rw-r--r--core/tests/coretests/res/raw/v21_complicated.vcf106
-rw-r--r--core/tests/coretests/res/raw/v21_invalid_comment_line.vcf10
-rw-r--r--core/tests/coretests/res/raw/v21_japanese_1.vcf6
-rw-r--r--core/tests/coretests/res/raw/v21_japanese_2.vcf10
-rw-r--r--core/tests/coretests/res/raw/v21_multiple_entry.vcf33
-rw-r--r--core/tests/coretests/res/raw/v21_org_before_title.vcf6
-rw-r--r--core/tests/coretests/res/raw/v21_pref_handling.vcf15
-rw-r--r--core/tests/coretests/res/raw/v21_simple_1.vcf3
-rw-r--r--core/tests/coretests/res/raw/v21_simple_2.vcf3
-rw-r--r--core/tests/coretests/res/raw/v21_simple_3.vcf4
-rw-r--r--core/tests/coretests/res/raw/v21_title_before_org.vcf6
-rw-r--r--core/tests/coretests/res/raw/v21_winmo_65.vcf10
-rw-r--r--core/tests/coretests/res/raw/v30_comma_separated.vcf5
-rw-r--r--core/tests/coretests/res/raw/v30_simple.vcf13
-rw-r--r--core/tests/coretests/res/raw/wild_alt_name_only.crt17
-rw-r--r--core/tests/coretests/res/values/arrays.xml34
-rw-r--r--core/tests/coretests/res/values/attrs.xml (renamed from core/res/res/values-mcc262-nb/strings.xml)16
-rw-r--r--core/tests/coretests/res/values/colors.xml26
-rw-r--r--core/tests/coretests/res/values/strings.xml120
-rw-r--r--core/tests/coretests/res/values/styles.xml22
-rw-r--r--core/tests/coretests/res/xml/metadata.xml (renamed from core/res/res/values-mcc232-nb/strings.xml)20
-rw-r--r--core/tests/coretests/res/xml/metadata_app.xml (renamed from core/res/res/drawable/pickerbox.xml)15
-rw-r--r--core/tests/coretests/res/xml/searchable.xml26
-rw-r--r--core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java167
-rw-r--r--core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java178
-rw-r--r--core/tests/coretests/src/android/app/SearchManagerTest.java175
-rw-r--r--core/tests/coretests/src/android/app/SearchablesTest.java449
-rw-r--r--core/tests/coretests/src/android/app/SuggestionProvider.java110
-rw-r--r--core/tests/coretests/src/android/app/TranslucentFancyActivity.java70
-rw-r--r--core/tests/coretests/src/android/app/activity/AbortReceiver.java49
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityManagerTest.java135
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityTests.java40
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityTestsBase.java212
-rw-r--r--core/tests/coretests/src/android/app/activity/BroadcastTest.java536
-rw-r--r--core/tests/coretests/src/android/app/activity/ClearTop.java51
-rw-r--r--core/tests/coretests/src/android/app/activity/IntentSenderTest.java86
-rw-r--r--core/tests/coretests/src/android/app/activity/LaunchTest.java75
-rw-r--r--core/tests/coretests/src/android/app/activity/LaunchpadActivity.java588
-rw-r--r--core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java43
-rw-r--r--core/tests/coretests/src/android/app/activity/LifecycleTest.java109
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalActivity.java33
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java42
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalDeniedService.java (renamed from core/java/android/pim/DateException.java)8
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalDialog.java (renamed from core/java/android/os/Base64Utils.java)22
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java42
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalGrantedService.java22
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalProvider.java163
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalReceiver.java77
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalScreen.java33
-rw-r--r--core/tests/coretests/src/android/app/activity/LocalService.java122
-rw-r--r--core/tests/coretests/src/android/app/activity/MetaDataTest.java166
-rw-r--r--core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java42
-rw-r--r--core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java42
-rw-r--r--core/tests/coretests/src/android/app/activity/RemoteReceiver.java50
-rw-r--r--core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java59
-rw-r--r--core/tests/coretests/src/android/app/activity/ResultReceiver.java43
-rw-r--r--core/tests/coretests/src/android/app/activity/SearchableActivity.java30
-rw-r--r--core/tests/coretests/src/android/app/activity/ServiceTest.java469
-rw-r--r--core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java70
-rw-r--r--core/tests/coretests/src/android/app/activity/SubActivityScreen.java168
-rw-r--r--core/tests/coretests/src/android/app/activity/SubActivityTest.java92
-rw-r--r--core/tests/coretests/src/android/app/activity/TestedActivity.java77
-rw-r--r--core/tests/coretests/src/android/app/activity/TestedScreen.java128
-rw-r--r--core/tests/coretests/src/android/bluetooth/AtParserTest.java348
-rw-r--r--core/tests/coretests/src/android/content/AssetTest.java74
-rw-r--r--core/tests/coretests/src/android/content/BrickDeniedTest.java33
-rw-r--r--core/tests/coretests/src/android/content/ContentProviderOperationTest.java550
-rw-r--r--core/tests/coretests/src/android/content/ContentQueryMapTest.java104
-rw-r--r--core/tests/coretests/src/android/content/ContentTests.java (renamed from core/java/android/os/HandlerState.java)19
-rw-r--r--core/tests/coretests/src/android/content/MemoryFileProvider.java211
-rw-r--r--core/tests/coretests/src/android/content/MemoryFileProviderTest.java82
-rw-r--r--core/tests/coretests/src/android/content/ObserverNodeTest.java91
-rw-r--r--core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java402
-rw-r--r--core/tests/coretests/src/android/content/SyncQueueTest.java164
-rw-r--r--core/tests/coretests/src/android/content/SyncStorageEngineTest.java415
-rwxr-xr-xcore/tests/coretests/src/android/content/pm/AppCacheTest.java745
-rw-r--r--core/tests/coretests/src/android/content/pm/ComponentTest.java738
-rwxr-xr-xcore/tests/coretests/src/android/content/pm/PackageManagerTests.java2738
-rw-r--r--core/tests/coretests/src/android/database/CursorWindowTest.java173
-rw-r--r--core/tests/coretests/src/android/database/DatabaseCursorTest.java641
-rw-r--r--core/tests/coretests/src/android/database/DatabaseGeneralTest.java1112
-rw-r--r--core/tests/coretests/src/android/database/DatabaseLocaleTest.java129
-rw-r--r--core/tests/coretests/src/android/database/DatabaseLockTest.java175
-rw-r--r--core/tests/coretests/src/android/database/DatabasePerformanceTests.java1353
-rw-r--r--core/tests/coretests/src/android/database/DatabaseStatementTest.java324
-rw-r--r--core/tests/coretests/src/android/database/DatabaseStressTest.java99
-rw-r--r--core/tests/coretests/src/android/database/MatrixCursorTest.java152
-rw-r--r--core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java91
-rw-r--r--core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java1234
-rw-r--r--core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java211
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java136
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java137
-rw-r--r--core/tests/coretests/src/android/net/LocalSocketTest.java171
-rw-r--r--core/tests/coretests/src/android/net/SSLTest.java52
-rw-r--r--core/tests/coretests/src/android/net/UriMatcherTest.java95
-rw-r--r--core/tests/coretests/src/android/net/UriTest.java606
-rw-r--r--core/tests/coretests/src/android/net/http/SslCertificateTest.java55
-rw-r--r--core/tests/coretests/src/android/os/AidlTest.aidl20
-rw-r--r--core/tests/coretests/src/android/os/AidlTest.java422
-rw-r--r--core/tests/coretests/src/android/os/BinderThreadPriorityService.java62
-rw-r--r--core/tests/coretests/src/android/os/BinderThreadPriorityTest.java146
-rw-r--r--core/tests/coretests/src/android/os/BrightnessLimit.java60
-rw-r--r--core/tests/coretests/src/android/os/BroadcasterTest.java232
-rw-r--r--core/tests/coretests/src/android/os/BuildTest.java76
-rw-r--r--core/tests/coretests/src/android/os/FileObserverTest.java152
-rw-r--r--core/tests/coretests/src/android/os/FileUtilsTest.java171
-rw-r--r--core/tests/coretests/src/android/os/HandlerTester.java89
-rw-r--r--core/tests/coretests/src/android/os/HandlerThreadTest.java102
-rw-r--r--core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java1640
-rw-r--r--core/tests/coretests/src/android/os/IAidlTest.aidl47
-rw-r--r--core/tests/coretests/src/android/os/IBinderThreadPriorityService.aidl (renamed from core/java/android/os/IParentalControlCallback.aidl)15
-rw-r--r--core/tests/coretests/src/android/os/IdleHandlerTest.java199
-rw-r--r--core/tests/coretests/src/android/os/MemoryFileTest.java431
-rw-r--r--core/tests/coretests/src/android/os/MessageQueueTest.java103
-rw-r--r--core/tests/coretests/src/android/os/MessengerService.java50
-rw-r--r--core/tests/coretests/src/android/os/MessengerTest.java119
-rw-r--r--core/tests/coretests/src/android/os/OsTests.java (renamed from core/java/android/os/Hardware.java)42
-rw-r--r--core/tests/coretests/src/android/os/PerformanceCollectorTest.java528
-rw-r--r--core/tests/coretests/src/android/os/PowerManagerTest.java142
-rw-r--r--core/tests/coretests/src/android/os/SystemPropertiesTest.java51
-rw-r--r--core/tests/coretests/src/android/os/TestHandlerThread.java104
-rw-r--r--core/tests/coretests/src/android/os/TraceTest.java215
-rwxr-xr-xcore/tests/coretests/src/android/os/storage/AsecTests.java612
-rw-r--r--core/tests/coretests/src/android/os/storage/StorageListener.java45
-rw-r--r--core/tests/coretests/src/android/pim/RecurrenceSetTest.java82
-rw-r--r--core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java81
-rw-r--r--core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java101
-rw-r--r--core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java95
-rw-r--r--core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java216
-rw-r--r--core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java299
-rw-r--r--core/tests/coretests/src/android/pim/vcard/LineVerifier.java65
-rw-r--r--core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java105
-rw-r--r--core/tests/coretests/src/android/pim/vcard/PropertyNode.java198
-rw-r--r--core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java386
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java959
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java1011
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java434
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java169
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java85
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VCardVerifier.java306
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VNode.java30
-rw-r--r--core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java314
-rw-r--r--core/tests/coretests/src/android/provider/SettingsProviderTest.java147
-rw-r--r--core/tests/coretests/src/android/provider/SmsProviderTest.java76
-rw-r--r--core/tests/coretests/src/android/text/HtmlTest.java248
-rw-r--r--core/tests/coretests/src/android/text/SpannableStringBuilderTest.java27
-rw-r--r--core/tests/coretests/src/android/text/SpannableStringTest.java27
-rw-r--r--core/tests/coretests/src/android/text/SpannableTest.java50
-rw-r--r--core/tests/coretests/src/android/text/SpannedTest.java187
-rw-r--r--core/tests/coretests/src/android/text/StaticLayoutBidiTest.java137
-rw-r--r--core/tests/coretests/src/android/text/StaticLayoutTest.java324
-rw-r--r--core/tests/coretests/src/android/text/TextLayoutTest.java51
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java359
-rw-r--r--core/tests/coretests/src/android/text/format/TimeTest.java608
-rw-r--r--core/tests/coretests/src/android/text/util/LinkifyTest.java66
-rw-r--r--core/tests/coretests/src/android/util/Base64Test.java520
-rw-r--r--core/tests/coretests/src/android/util/DayOfMonthCursorTest.java157
-rw-r--r--core/tests/coretests/src/android/util/ExpandableListScenario.java386
-rw-r--r--core/tests/coretests/src/android/util/FloatMathTest.java55
-rw-r--r--core/tests/coretests/src/android/util/GridScenario.java370
-rw-r--r--core/tests/coretests/src/android/util/InternalSelectionView.java273
-rw-r--r--core/tests/coretests/src/android/util/KeyUtils.java88
-rw-r--r--core/tests/coretests/src/android/util/ListItemFactory.java291
-rw-r--r--core/tests/coretests/src/android/util/ListScenario.java662
-rw-r--r--core/tests/coretests/src/android/util/ListUtil.java103
-rw-r--r--core/tests/coretests/src/android/util/LogTest.java168
-rw-r--r--core/tests/coretests/src/android/util/MonthDisplayHelperTest.java211
-rw-r--r--core/tests/coretests/src/android/util/PatternsTest.java166
-rw-r--r--core/tests/coretests/src/android/util/ScrollViewScenario.java258
-rw-r--r--core/tests/coretests/src/android/util/StateSetTest.java182
-rw-r--r--core/tests/coretests/src/android/util/TimeUtilsTest.java432
-rw-r--r--core/tests/coretests/src/android/util/TouchModeFlexibleAsserts.java75
-rw-r--r--core/tests/coretests/src/android/view/BigCache.java71
-rw-r--r--core/tests/coretests/src/android/view/BigCacheTest.java84
-rw-r--r--core/tests/coretests/src/android/view/BitmapDrawable.java40
-rw-r--r--core/tests/coretests/src/android/view/CreateViewTest.java123
-rw-r--r--core/tests/coretests/src/android/view/Disabled.java49
-rw-r--r--core/tests/coretests/src/android/view/DisabledLongpressTest.java89
-rw-r--r--core/tests/coretests/src/android/view/DisabledTest.java94
-rw-r--r--core/tests/coretests/src/android/view/DrawableBgMinSize.java91
-rw-r--r--core/tests/coretests/src/android/view/DrawableBgMinSizeTest.java152
-rw-r--r--core/tests/coretests/src/android/view/FocusFinderTest.java576
-rw-r--r--core/tests/coretests/src/android/view/GlobalFocusChange.java46
-rw-r--r--core/tests/coretests/src/android/view/GlobalFocusChangeTest.java87
-rw-r--r--core/tests/coretests/src/android/view/Include.java33
-rw-r--r--core/tests/coretests/src/android/view/IncludeTest.java79
-rw-r--r--core/tests/coretests/src/android/view/InflateTest.java155
-rw-r--r--core/tests/coretests/src/android/view/ListContextMenu.java201
-rw-r--r--core/tests/coretests/src/android/view/Longpress.java (renamed from core/java/android/os/MailboxNotAvailableException.java)27
-rw-r--r--core/tests/coretests/src/android/view/LongpressTest.java85
-rw-r--r--core/tests/coretests/src/android/view/MenuTest.java279
-rw-r--r--core/tests/coretests/src/android/view/Merge.java47
-rw-r--r--core/tests/coretests/src/android/view/MergeTest.java42
-rw-r--r--core/tests/coretests/src/android/view/MutateDrawable.java49
-rw-r--r--core/tests/coretests/src/android/view/MutateDrawableTest.java52
-rw-r--r--core/tests/coretests/src/android/view/PopupWindowVisibility.java117
-rw-r--r--core/tests/coretests/src/android/view/PreDrawListener.java92
-rw-r--r--core/tests/coretests/src/android/view/RemoteViewsActivity.java33
-rw-r--r--core/tests/coretests/src/android/view/RunQueue.java70
-rw-r--r--core/tests/coretests/src/android/view/RunQueueTest.java59
-rw-r--r--core/tests/coretests/src/android/view/SetTagsTest.java114
-rw-r--r--core/tests/coretests/src/android/view/StubbedView.java43
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupAttributesTest.java83
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupChildren.java35
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupChildrenTest.java268
-rw-r--r--core/tests/coretests/src/android/view/ViewStubTest.java85
-rw-r--r--core/tests/coretests/src/android/view/Visibility.java70
-rw-r--r--core/tests/coretests/src/android/view/VisibilityCallback.java111
-rw-r--r--core/tests/coretests/src/android/view/VisibilityCallbackTest.java112
-rw-r--r--core/tests/coretests/src/android/view/VisibilityTest.java179
-rw-r--r--core/tests/coretests/src/android/view/ZeroSized.java34
-rw-r--r--core/tests/coretests/src/android/view/ZeroSizedTest.java102
-rw-r--r--core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java81
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuLayout.java65
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuLayoutLandscape.java24
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java230
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuLayoutPortrait.java24
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java231
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuScenario.java212
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuWith1Item.java44
-rw-r--r--core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java84
-rw-r--r--core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java88
-rw-r--r--core/tests/coretests/src/android/webkit/WebkitTest.java59
-rw-r--r--core/tests/coretests/src/android/widget/AutoCompleteTextViewCallbacks.java141
-rw-r--r--core/tests/coretests/src/android/widget/AutoCompleteTextViewPopup.java260
-rw-r--r--core/tests/coretests/src/android/widget/AutoCompleteTextViewSimple.java122
-rw-r--r--core/tests/coretests/src/android/widget/LabelView.java191
-rw-r--r--core/tests/coretests/src/android/widget/ListViewTest.java141
-rw-r--r--core/tests/coretests/src/android/widget/RadioGroupActivity.java31
-rw-r--r--core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java63
-rw-r--r--core/tests/coretests/src/android/widget/SimpleCursorAdapterTest.java259
-rw-r--r--core/tests/coretests/src/android/widget/TextViewPerformanceTest.java132
-rw-r--r--core/tests/coretests/src/android/widget/TextViewTest.java61
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java144
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java49
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java241
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java67
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java84
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java115
-rw-r--r--core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java111
-rw-r--r--core/tests/coretests/src/android/widget/focus/AdjacentVerticalRectLists.java94
-rw-r--r--core/tests/coretests/src/android/widget/focus/DescendantFocusability.java53
-rw-r--r--core/tests/coretests/src/android/widget/focus/DescendantFocusabilityTest.java138
-rw-r--r--core/tests/coretests/src/android/widget/focus/FocusAfterRemoval.java78
-rw-r--r--core/tests/coretests/src/android/widget/focus/FocusAfterRemovalTest.java131
-rw-r--r--core/tests/coretests/src/android/widget/focus/FocusChangeWithInterestingRectHintTest.java114
-rw-r--r--core/tests/coretests/src/android/widget/focus/GoneParentFocusedChild.java88
-rw-r--r--core/tests/coretests/src/android/widget/focus/GoneParentFocusedChildTest.java56
-rw-r--r--core/tests/coretests/src/android/widget/focus/HorizontalFocusSearch.java134
-rw-r--r--core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java124
-rw-r--r--core/tests/coretests/src/android/widget/focus/LinearLayoutGrid.java65
-rw-r--r--core/tests/coretests/src/android/widget/focus/LinearLayoutGridTest.java81
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListOfButtons.java76
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java170
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java125
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java170
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabels.java111
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabelsTest.java72
-rw-r--r--core/tests/coretests/src/android/widget/focus/ListWithMailMessages.java147
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocus.java47
-rw-r--r--core/tests/coretests/src/android/widget/focus/RequestFocusTest.java97
-rw-r--r--core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java226
-rw-r--r--core/tests/coretests/src/android/widget/focus/VerticalFocusSearch.java151
-rw-r--r--core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java152
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridDelete.java108
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridInHorizontal.java59
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridInHorizontalTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridInVertical.java59
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridInVerticalTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridPadding.java52
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridPaddingTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridScrollListener.java68
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java110
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelection.java (renamed from core/java/android/content/TempProviderSyncResult.java)29
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionBaseTest.java102
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionMany.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionManyTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottom.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomMany.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomManyTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSetSelectionTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSimple.java55
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSingleColumn.java37
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridSingleColumnTest.java47
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridStackFromBottom.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridStackFromBottomMany.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridStackFromBottomManyTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridStackFromBottomTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridThrasher.java135
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridVerticalSpacing.java34
-rw-r--r--core/tests/coretests/src/android/widget/gridview/GridVerticalSpacingStackFromBottom.java35
-rw-r--r--core/tests/coretests/src/android/widget/gridview/touch/GridTouchSetSelectionTest.java82
-rw-r--r--core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java86
-rw-r--r--core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomTest.java119
-rw-r--r--core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingStackFromBottomTest.java160
-rw-r--r--core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingTest.java144
-rw-r--r--core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravity.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravityTest.java131
-rw-r--r--core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMargin.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMarginTest.java85
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravity.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravityTest.java69
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentSpinnerButton.java55
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeight.java37
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeightTest.java62
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineButtons.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/BaselineButtonsTest.java86
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/ExceptionTextView.java65
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/FillInWrap.java32
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/FillInWrapTest.java54
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/HorizontalOrientationVerticalAlignment.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LLEditTextThenButton.java55
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LLOfButtons1.java66
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LLOfButtons2.java26
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java91
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTexts.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTextsTest.java57
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/Weight.java30
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/WeightSum.java31
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/WeightSumTest.java57
-rw-r--r--core/tests/coretests/src/android/widget/layout/linear/WeightTest.java55
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/AddColumn.java54
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/AddColumnTest.java71
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/CellSpan.java33
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/CellSpanTest.java85
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/FixedWidth.java33
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/FixedWidthTest.java68
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/HorizontalGravity.java33
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/HorizontalGravityTest.java74
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/VerticalGravity.java33
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/VerticalGravityTest.java82
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/Weight.java33
-rw-r--r--core/tests/coretests/src/android/widget/layout/table/WeightTest.java63
-rw-r--r--core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java131
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListBottomGravity.java37
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListBottomGravityMany.java36
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListBottomGravityManyTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListBottomGravityTest.java49
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListButtonsDiagonalAcrossItems.java57
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListDividers.java52
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListEmptyViewTest.java128
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListEndingWithMultipleSeparators.java32
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListFilter.java193
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListFocusableTest.java232
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListGetCheckItemIdsTest.java141
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListGetSelectedView.java30
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListHeterogeneous.java83
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListHeterogeneousTest.java74
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListHorizontalFocusWithinItemWins.java64
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListInHorizontal.java55
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListInHorizontalTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListInVertical.java56
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListInVerticalTest.java25
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListInterleaveFocusables.java63
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemFocusableAboveUnfocusable.java48
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemFocusablesClose.java54
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemFocusablesFarApart.java44
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemISVAndButton.java75
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemRequestRectAboveThinFirstItemTest.java102
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListItemsExpandOnSelection.java80
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListLastItemPartiallyVisible.java31
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListManagedCursor.java60
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListManagedCursorTest.java180
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfItemsShorterThanScreen.java28
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfItemsTallerThanScreen.java29
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfShortShortTallShortShort.java33
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfShortTallShort.java32
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfThinItems.java33
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListOfTouchables.java47
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListRecyclerProfiling.java55
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListRetainsFocusAcrossLayoutsTest.java88
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListScrollListener.java72
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java105
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListSetSelection.java72
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListSetSelectionTest.java73
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListSimple.java51
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListTakeFocusFromSide.java84
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListThrasher.java131
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListTopGravity.java37
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListTopGravityMany.java35
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListUnspecifiedMeasure.java55
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListViewHeight.java109
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListViewHeightTest.java86
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithDisappearingItemBug.java80
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithEditTextHeader.java34
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithEmptyView.java104
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithFirstScreenUnSelectable.java33
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithHeaders.java57
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithNoFadingEdge.java28
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithOffScreenNextSelectable.java37
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithOnItemSelectedAction.java44
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithScreenOfNoSelectables.java28
-rw-r--r--core/tests/coretests/src/android/widget/listview/ListWithSeparators.java33
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java109
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusableAboveUnfocusableTest.java55
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesCloseTest.java115
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesFarApartTest.java116
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemsExpandOnSelectionTest.java118
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListLastItemPartiallyVisibleTest.java80
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsShorterThanScreenTest.java162
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java195
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java137
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortTallShortTest.java80
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java115
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java64
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java80
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOffScreenNextSelectableTest.java105
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOnItemSelectedActionTest.java60
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java106
-rw-r--r--core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithSeparatorsTest.java75
-rw-r--r--core/tests/coretests/src/android/widget/listview/focus/AdjacentListsWithAdjacentISVsInsideTest.java90
-rw-r--r--core/tests/coretests/src/android/widget/listview/focus/ListButtonsDiagonalAcrossItemsTest.java105
-rw-r--r--core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java74
-rw-r--r--core/tests/coretests/src/android/widget/listview/focus/ListWithEditTextHeaderTest.java66
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListGetSelectedViewTest.java65
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListOfTouchablesTest.java86
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListSetSelectionTest.java148
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityManyTest.java155
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityTest.java94
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListTouchManyTest.java194
-rw-r--r--core/tests/coretests/src/android/widget/listview/touch/ListTouchTest.java91
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionView.java46
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionViewTest.java66
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ButtonsWithTallTextViewInBetween.java50
-rw-r--r--core/tests/coretests/src/android/widget/scroll/RequestRectangleVisible.java94
-rw-r--r--core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleTest.java237
-rw-r--r--core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScroll.java76
-rw-r--r--core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScrollTest.java85
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabels.java91
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabelsTest.java214
-rw-r--r--core/tests/coretests/src/android/widget/scroll/ShortButtons.java54
-rw-r--r--core/tests/coretests/src/android/widget/scroll/TallTextAboveButton.java31
-rw-r--r--core/tests/coretests/src/android/widget/scroll/arrowscroll/ButtonsWithTallTextViewInBetweenTest.java142
-rw-r--r--core/tests/coretests/src/android/widget/scroll/arrowscroll/ShortButtonsTest.java109
-rw-r--r--core/tests/coretests/src/android/widget/scroll/arrowscroll/TallTextAboveButtonTest.java78
-rw-r--r--core/tests/coretests/src/android/widget/touchmode/ChangeTouchModeTest.java127
-rw-r--r--core/tests/coretests/src/android/widget/touchmode/FocusableInTouchModeClickTest.java64
-rw-r--r--core/tests/coretests/src/android/widget/touchmode/StartInTouchWithViewInFocusTest.java67
-rw-r--r--core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java90
-rw-r--r--core/tests/coretests/src/android/widget/touchmode/TouchModeFocusableTest.java91
-rw-r--r--core/tests/coretests/src/com/android/internal/net/DNParserTest.java51
-rw-r--r--core/tests/coretests/src/com/android/internal/net/DomainNameValidatorTest.java401
-rw-r--r--core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java136
-rw-r--r--core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java58
-rw-r--r--core/tests/coretests/src/com/android/internal/util/HanziToPinyinTest.java74
-rw-r--r--core/tests/coretests/src/com/android/internal/util/PredicatesTest.java74
-rw-r--r--core/tests/hosttests/Android.mk32
-rw-r--r--core/tests/hosttests/README6
-rw-r--r--core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java724
-rw-r--r--core/tests/hosttests/src/android/content/pm/PackageManagerHostTests.java988
-rw-r--r--core/tests/hosttests/src/android/content/pm/PackageManagerStressHostTests.java286
-rw-r--r--core/tests/hosttests/test-apps/Android.mk21
-rw-r--r--core/tests/hosttests/test-apps/AutoLocTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/AutoLocTestApp/AndroidManifest.xml (renamed from core/res/res/values-mcc204-nb/strings.xml)15
-rw-r--r--core/tests/hosttests/test-apps/AutoLocTestApp/src/com/android/framework/autoloctestapp/AutoLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml138
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/src/com/android/framework/externallocallpermstestapp/ExternalLocAllPermsTest.java31
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/src/com/android/framework/externallocpermsfltestapp/ExternalLocPermsFLTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocTestApp/AndroidManifest.xml22
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocTestApp/src/com/android/framework/externalloctestapp/ExternalLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPerms/AndroidManifest.xml32
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPerms/src/com/android/framework/externalsharedpermstestapp/ExternalSharedPermsTest.java54
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml35
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsBT/src/com/android/framework/externalsharedpermsbttestapp/ExternalSharedPermsBTTest.java38
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk29
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/AndroidManifest.xml32
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/src/com/android/framework/externalsharedpermsdiffkeytestapp/ExternalSharedPermsDiffKeyTest.java53
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsFL/AndroidManifest.xml34
-rw-r--r--core/tests/hosttests/test-apps/ExternalSharedPermsFL/src/com/android/framework/externalsharedpermsfltestapp/ExternalSharedPermsFLTest.java44
-rw-r--r--core/tests/hosttests/test-apps/InternalLocTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/InternalLocTestApp/AndroidManifest.xml22
-rw-r--r--core/tests/hosttests/test-apps/InternalLocTestApp/src/com/android/framework/internalloctestapp/InternalLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/NoLocTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/NoLocTestApp/AndroidManifest.xml (renamed from core/res/res/values-mcc234-nb/strings.xml)14
-rw-r--r--core/tests/hosttests/test-apps/NoLocTestApp/src/com/android/framework/noloctestapp/NoLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/AndroidManifest.xml23
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/AndroidManifest.xml23
-rw-r--r--core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/SimpleTestApp/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/SimpleTestApp/AndroidManifest.xml23
-rw-r--r--core/tests/hosttests/test-apps/SimpleTestApp/src/com/android/framework/simpletestapp/SimpleAppActivity.java25
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/AndroidManifest.xml24
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/AndroidManifest.xml22
-rw-r--r--core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Auto/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Auto/AndroidManifest.xml (renamed from core/res/res/values-mcc230-nb/strings.xml)15
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Auto/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_External/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_External/AndroidManifest.xml22
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_External/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Internal/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Internal/AndroidManifest.xml21
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_Internal/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java26
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_None/Android.mk27
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_None/AndroidManifest.xml20
-rw-r--r--core/tests/hosttests/test-apps/VersatileTestApp_None/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java26
1782 files changed, 133317 insertions, 37375 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 4761f98..bf9e07d 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -66,10 +66,10 @@ public class AccessibilityServiceInfo implements Parcelable {
* The event types an {@link AccessibilityService} is interested in.
*
* @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
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
- * @see android.view.accessibility.AccessibilityEvent#TYPE_ACTIVITY_STARTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
* @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
*/
@@ -115,7 +115,7 @@ public class AccessibilityServiceInfo implements Parcelable {
return 0;
}
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(Parcel parcel, int flagz) {
parcel.writeInt(eventTypes);
parcel.writeStringArray(packageNames);
parcel.writeInt(feedbackType);
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index be2bdbe..c0c4c17 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -24,6 +24,10 @@ import android.content.pm.PackageManager;
import android.content.Context;
import android.content.Intent;
import android.Manifest;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
/**
* Abstract base class for creating AccountAuthenticators.
@@ -103,6 +107,8 @@ import android.Manifest;
* writing activities to handle these requests.
*/
public abstract class AbstractAccountAuthenticator {
+ private static final String TAG = "AccountAuthenticator";
+
private final Context mContext;
public AbstractAccountAuthenticator(Context context) {
@@ -111,19 +117,34 @@ public abstract class AbstractAccountAuthenticator {
private class Transport extends IAccountAuthenticator.Stub {
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
- String authTokenType, String[] requiredFeatures, Bundle options)
+ String authTokenType, String[] features, Bundle options)
throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
checkBinderPermission();
try {
final Bundle result = AbstractAccountAuthenticator.this.addAccount(
new AccountAuthenticatorResponse(response),
- accountType, authTokenType, requiredFeatures, options);
+ accountType, authTokenType, features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result));
+ }
if (result != null) {
response.onResult(result);
}
} catch (NetworkErrorException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount", e);
+ }
response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
} catch (UnsupportedOperationException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addAccount", e);
+ }
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"addAccount not supported");
}
@@ -131,16 +152,30 @@ public abstract class AbstractAccountAuthenticator {
public void confirmCredentials(IAccountAuthenticatorResponse response,
Account account, Bundle options) throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials: " + account);
+ }
checkBinderPermission();
try {
final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials(
new AccountAuthenticatorResponse(response), account, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "confirmCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
if (result != null) {
response.onResult(result);
}
} catch (NetworkErrorException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials", e);
+ }
response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
} catch (UnsupportedOperationException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "confirmCredentials", e);
+ }
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"confirmCredentials not supported");
}
@@ -149,16 +184,32 @@ public abstract class AbstractAccountAuthenticator {
public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
String authTokenType)
throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
+ }
checkBinderPermission();
try {
Bundle result = new Bundle();
result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL,
AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
- response.onResult(result);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "getAuthTokenLabel: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
} catch (IllegalArgumentException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthTokenLabel", e);
+ }
response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
"unknown authTokenType");
} catch (UnsupportedOperationException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthTokenLabel", e);
+ }
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"getAuthTokenTypeLabel not supported");
}
@@ -167,35 +218,64 @@ public abstract class AbstractAccountAuthenticator {
public void getAuthToken(IAccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions)
throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken: " + account
+ + ", authTokenType " + authTokenType);
+ }
checkBinderPermission();
try {
final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
new AccountAuthenticatorResponse(response), account,
authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result));
+ }
if (result != null) {
response.onResult(result);
}
} catch (UnsupportedOperationException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken", e);
+ }
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"getAuthToken not supported");
} catch (NetworkErrorException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "getAuthToken", e);
+ }
response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
}
}
public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle loginOptions) throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials: " + account
+ + ", authTokenType " + authTokenType);
+ }
checkBinderPermission();
try {
final Bundle result = AbstractAccountAuthenticator.this.updateCredentials(
new AccountAuthenticatorResponse(response), account,
authTokenType, loginOptions);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "updateCredentials: result "
+ + AccountManager.sanitizeResult(result));
+ }
if (result != null) {
response.onResult(result);
}
} catch (NetworkErrorException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials", e);
+ }
response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
} catch (UnsupportedOperationException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "updateCredentials", e);
+ }
response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
"updateCredentials not supported");
}
@@ -296,8 +376,7 @@ public abstract class AbstractAccountAuthenticator {
* <ul>
* <li> {@link AccountManager#KEY_INTENT}, or
* <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
- * the account that was added, plus {@link AccountManager#KEY_AUTHTOKEN} if an authTokenType
- * was supplied, or
+ * the account that was added, or
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error
* </ul>
@@ -368,8 +447,7 @@ public abstract class AbstractAccountAuthenticator {
* <ul>
* <li> {@link AccountManager#KEY_INTENT}, or
* <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of
- * the account that was added, plus {@link AccountManager#KEY_AUTHTOKEN} if an authTokenType
- * was supplied, or
+ * the account that was added, or
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
* indicate an error
* </ul>
@@ -378,7 +456,7 @@ public abstract class AbstractAccountAuthenticator {
*/
public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options) throws NetworkErrorException;
-
+
/**
* Checks if the account supports all the specified authenticator specific features.
* @param response to send the result back to the AccountManager, will never be null
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
index d6c76a2..d2b3bc7 100644
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ b/core/java/android/accounts/AccountAuthenticatorCache.java
@@ -19,6 +19,7 @@ package android.accounts;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.XmlSerializerAndParser;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.Context;
import android.util.AttributeSet;
@@ -47,8 +48,9 @@ import java.io.IOException;
AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
}
- public AuthenticatorDescription parseServiceAttributes(String packageName, AttributeSet attrs) {
- TypedArray sa = mContext.getResources().obtainAttributes(attrs,
+ public AuthenticatorDescription parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs) {
+ TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AccountAuthenticator);
try {
final String accountType =
diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java
index 7c09fbf..614e139 100644
--- a/core/java/android/accounts/AccountAuthenticatorResponse.java
+++ b/core/java/android/accounts/AccountAuthenticatorResponse.java
@@ -20,11 +20,14 @@ import android.os.Bundle;
import android.os.Parcelable;
import android.os.Parcel;
import android.os.RemoteException;
+import android.util.Log;
/**
* Object used to communicate responses back to the AccountManager
*/
public class AccountAuthenticatorResponse implements Parcelable {
+ private static final String TAG = "AccountAuthenticator";
+
private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
/**
@@ -40,6 +43,11 @@ public class AccountAuthenticatorResponse implements Parcelable {
}
public void onResult(Bundle result) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ result.keySet(); // force it to be unparcelled
+ Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+ + AccountManager.sanitizeResult(result));
+ }
try {
mAccountAuthenticatorResponse.onResult(result);
} catch (RemoteException e) {
@@ -48,6 +56,9 @@ public class AccountAuthenticatorResponse implements Parcelable {
}
public void onRequestContinued() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onRequestContinued");
+ }
try {
mAccountAuthenticatorResponse.onRequestContinued();
} catch (RemoteException e) {
@@ -56,6 +67,9 @@ public class AccountAuthenticatorResponse implements Parcelable {
}
public void onError(int errorCode, String errorMessage) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "AccountAuthenticatorResponse.onError: " + errorCode + ", " + errorMessage);
+ }
try {
mAccountAuthenticatorResponse.onError(errorCode, errorMessage);
} catch (RemoteException e) {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 9765496..b0adaec 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -27,7 +27,9 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.Parcelable;
+import android.os.Build;
import android.util.Log;
+import android.text.TextUtils;
import java.io.IOException;
import java.util.concurrent.Callable;
@@ -42,34 +44,92 @@ import java.util.Map;
import com.google.android.collect.Maps;
/**
- * A class that helps with interactions with the AccountManager Service. It provides
- * methods to allow for account, password, and authtoken management for all accounts on the
- * device. One accesses the {@link AccountManager} by calling:
- * <pre>
- * AccountManager accountManager = AccountManager.get(context);
- * </pre>
+ * This class provides access to a centralized registry of the user's
+ * online accounts. With this service, users only need to enter their
+ * credentials (username and password) once for any account, granting
+ * applications access to online resources with "one-click" approval.
*
- * <p>
- * The AccountManager Service provides storage for the accounts known to the system,
- * provides methods to manage them, and allows the registration of authenticators to
- * which operations such as addAccount and getAuthToken are delegated.
- * <p>
- * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters.
- * These calls return immediately but run asynchronously. If a callback is provided then
- * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully
- * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the
- * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which
- * either returns the result or throws an exception as appropriate.
- * <p>
- * The asynchronous request can be made blocking by not providing a callback and instead
- * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will
- * cause the running thread to block until the result is returned. Keep in mind that one
- * should not block the main thread in this way. Instead one should either use a callback,
- * thus making the call asynchronous, or make the blocking call on a separate thread.
- * <p>
- * If one wants to ensure that the callback is invoked from a specific handler then they should
- * pass the handler to the request. This makes it easier to ensure thread-safety by running
- * all of one's logic from a single handler.
+ * <p>Different online services have different ways of handling accounts and
+ * authentication, so the account manager uses pluggable <em>authenticator</em>
+ * modules for different <em>account types</em>. The authenticators (which
+ * may be written by third parties) handle the actual details of validating
+ * account credentials and storing account information. For example, Google,
+ * Facebook, and Microsoft Exchange each have their own authenticator.
+ *
+ * <p>Many servers support some notion of an <em>authentication token</em>,
+ * which can be used to authenticate a request to the server without sending
+ * the user's actual password. (Auth tokens are normally created with a
+ * separate request which does include the user's credentials.) AccountManager
+ * can generate these auth tokens for applications, so the application doesn't
+ * need to handle passwords directly. Auth tokens are normally reusable, and
+ * cached by AccountManager, but must be refreshed periodically. It's the
+ * responsibility of applications to <em>invalidate</em> auth tokens when they
+ * stop working so the AccountManager knows it needs to regenerate them.
+ *
+ * <p>Applications accessing a server normally go through these steps:
+ *
+ * <ul>
+ * <li>Get an instance of AccountManager using {@link #get(Context)}.
+ *
+ * <li>List the available accounts using {@link #getAccountsByType} or
+ * {@link #getAccountsByTypeAndFeatures}. Normally applications will only
+ * be interested in accounts with one particular <em>type</em>, which
+ * identifies the authenticator. Account <em>features</em> are used to
+ * identify particular account subtypes and capabilities. Both the account
+ * type and features are authenticator-specific strings, and must be known by
+ * the application in coordination with its preferred authenticators.
+ *
+ * <li>Select one or more of the available accounts, possibly by asking the
+ * user for their preference. If no suitable accounts are available,
+ * {@link #addAccount} may be called to prompt the user to create an
+ * account of the appropriate type.
+ *
+ * <li>Request an auth token for the selected account(s) using one of the
+ * {@link #getAuthToken} methods or related helpers. Refer to the description
+ * of each method for exact usage and error handling details.
+ *
+ * <li>Make the request using the auth token. The form of the auth token,
+ * the format of the request, and the protocol used are all specific to the
+ * service you are accessing. The application makes the request itself, using
+ * whatever network and protocol libraries are useful.
+ *
+ * <li><b>Important:</b> If the request fails with an authentication error,
+ * it could be that a cached auth token is stale and no longer honored by
+ * the server. The application must call {@link #invalidateAuthToken} to remove
+ * the token from the cache, otherwise requests will continue failing! After
+ * invalidating the auth token, immediately go back to the "Request an auth
+ * token" step above. If the process fails the second time, then it can be
+ * treated as a "genuine" authentication failure and the user notified or other
+ * appropriate actions taken.
+ * </ul>
+ *
+ * <p>Some AccountManager methods may require interaction with the user to
+ * prompt for credentials, present options, or ask the user to add an account.
+ * The caller may choose whether to allow AccountManager to directly launch the
+ * necessary user interface and wait for the user, or to return an Intent which
+ * the caller may use to launch the interface, or (in some cases) to install a
+ * notification which the user can select at any time to launch the interface.
+ * To have AccountManager launch the interface directly, the caller must supply
+ * the current foreground {@link Activity} context.
+ *
+ * <p>Many AccountManager methods take {@link AccountManagerCallback} and
+ * {@link Handler} as parameters. These methods return immediately but
+ * run asynchronously. If a callback is provided then
+ * {@link AccountManagerCallback#run} will be invoked on the Handler's
+ * thread when the request completes, successfully or not.
+ * An {@link AccountManagerFuture} is returned by these requests and also
+ * supplied to the callback (if any). The result is retrieved by calling
+ * {@link AccountManagerFuture#getResult()} which waits for the operation
+ * to complete (if necessary) and either returns the result or throws an
+ * exception if an error occurred during the operation.
+ * To make the request synchronously, call
+ * {@link AccountManagerFuture#getResult()} immediately on receiving the
+ * future from the method. No callback need be supplied.
+ *
+ * <p>Requests which may block, including
+ * {@link AccountManagerFuture#getResult()}, must never be called on
+ * the application's main event thread. These operations throw
+ * {@link IllegalStateException} if they are used on the main thread.
*/
public class AccountManager {
private static final String TAG = "AccountManager";
@@ -82,34 +142,65 @@ public class AccountManager {
public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final int ERROR_CODE_BAD_REQUEST = 8;
- public static final String KEY_ACCOUNTS = "accounts";
- public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
- public static final String KEY_USERDATA = "userdata";
- public static final String KEY_AUTHTOKEN = "authtoken";
- public static final String KEY_PASSWORD = "password";
+ /**
+ * The Bundle key used for the {@link String} account name in results
+ * from methods which return information about a particular account.
+ */
public static final String KEY_ACCOUNT_NAME = "authAccount";
+
+ /**
+ * The Bundle key used for the {@link String} account type in results
+ * from methods which return information about a particular account.
+ */
public static final String KEY_ACCOUNT_TYPE = "accountType";
- public static final String KEY_ERROR_CODE = "errorCode";
- public static final String KEY_ERROR_MESSAGE = "errorMessage";
+
+ /**
+ * The Bundle key used for the auth token value in results
+ * from {@link #getAuthToken} and friends.
+ */
+ public static final String KEY_AUTHTOKEN = "authtoken";
+
+ /**
+ * The Bundle key used for an {@link Intent} in results from methods that
+ * may require the caller to interact with the user. The Intent can
+ * be used to start the corresponding user interface activity.
+ */
public static final String KEY_INTENT = "intent";
- public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+
+ /**
+ * The Bundle key used to supply the password directly in options to
+ * {@link #confirmCredentials}, rather than prompting the user with
+ * the standard password prompt.
+ */
+ 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";
public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage";
public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey";
+ public static final String KEY_BOOLEAN_RESULT = "booleanResult";
+ public static final String KEY_ERROR_CODE = "errorCode";
+ public static final String KEY_ERROR_MESSAGE = "errorMessage";
+ public static final String KEY_USERDATA = "userdata";
+
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_META_DATA_NAME =
- "android.accounts.AccountAuthenticator";
+ "android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
private final Context mContext;
private final IAccountManager mService;
private final Handler mMainHandler;
+
/**
* Action sent as a broadcast Intent by the AccountsService
- * when accounts are added to and/or removed from the device's
- * database.
+ * when accounts are added, accounts are removed, or an
+ * account's credentials (saved password, etc) are changed.
+ *
+ * @see #addOnAccountsUpdatedListener
*/
public static final String LOGIN_ACCOUNTS_CHANGED_ACTION =
"android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -133,26 +224,55 @@ public class AccountManager {
}
/**
- * Retrieve an AccountManager instance that is associated with the context that is passed in.
- * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally,
- * so the caller must take care to use a {@link Context} whose lifetime is associated with
- * the listener registration.
+ * @hide for internal use only
+ */
+ public static Bundle sanitizeResult(Bundle result) {
+ if (result != null) {
+ if (result.containsKey(KEY_AUTHTOKEN)
+ && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) {
+ final Bundle newResult = new Bundle(result);
+ newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>");
+ return newResult;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets an AccountManager instance associated with a Context.
+ * The {@link Context} will be used as long as the AccountManager is
+ * active, so make sure to use a {@link Context} whose lifetime is
+ * commensurate with any listeners registered to
+ * {@link #addOnAccountsUpdatedListener} or similar methods.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
* @param context The {@link Context} to use when necessary
- * @return an {@link AccountManager} instance that is associated with context
+ * @return An {@link AccountManager} instance
*/
public static AccountManager get(Context context) {
+ if (context == null) throw new IllegalArgumentException("context is null");
return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
}
/**
- * Get the password that is associated with the account. Returns null if the account does
- * not exist.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
+ * Gets the saved password associated with the account.
+ * This is intended for authenticators and related code; applications
+ * should get an auth token instead.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to query for a password
+ * @return The account's password, null if none or if the account doesn't exist
*/
public String getPassword(final Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
try {
return mService.getPassword(account);
} catch (RemoteException e) {
@@ -162,14 +282,23 @@ public class AccountManager {
}
/**
- * Get the user data named by "key" that is associated with the account.
- * Returns null if the account does not exist or if it does not have a value for key.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
+ * Gets the user data named by "key" associated with the account.
+ * This is intended for authenticators and related code to store
+ * arbitrary metadata along with accounts. The meaning of the keys
+ * and values is up to the authenticator for the account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to query for user data
+ * @return The user data, null if the account or key doesn't exist
*/
public String getUserData(final Account account, final String key) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
try {
return mService.getUserData(account, key);
} catch (RemoteException e) {
@@ -179,12 +308,15 @@ public class AccountManager {
}
/**
- * Query the AccountManager Service for an array that contains a
- * {@link AuthenticatorDescription} for each registered authenticator.
- * @return an array that contains all the authenticators known to the AccountManager service.
- * This array will be empty if there are no authenticators and will never return null.
- * <p>
- * No permission is required to make this call.
+ * Lists the currently registered authenticators.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @return An array of {@link AuthenticatorDescription} for every
+ * authenticator known to the AccountManager service. Empty (never
+ * null) if no authenticators are known.
*/
public AuthenticatorDescription[] getAuthenticatorTypes() {
try {
@@ -196,11 +328,16 @@ public class AccountManager {
}
/**
- * Query the AccountManager Service for all accounts.
- * @return an array that contains all the accounts known to the AccountManager service.
- * This array will be empty if there are no accounts and will never return null.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
+ * Lists all accounts of any type registered on the device.
+ * Equivalent to getAccountsByType(null).
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @return An array of {@link Account}, one for each account. Empty
+ * (never null) if no accounts have been added.
*/
public Account[] getAccounts() {
try {
@@ -212,13 +349,20 @@ public class AccountManager {
}
/**
- * Query the AccountManager for the set of accounts that have a given type. If null
- * is passed as the type than all accounts are returned.
- * @param type the account type by which to filter, or null to get all accounts
- * @return an array that contains the accounts that match the specified type. This array
- * will be empty if no accounts match. It will never return null.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}
+ * Lists all accounts of a particular type. The account type is a
+ * string token corresponding to the authenticator and useful domain
+ * of the account. For example, there are types corresponding to Google
+ * and Facebook. The exact string token to use will be published somewhere
+ * associated with the authenticator in question.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @return An array of {@link Account}, one per matching account. Empty
+ * (never null) if no accounts of the specified type have been added.
*/
public Account[] getAccountsByType(String type) {
try {
@@ -230,18 +374,113 @@ public class AccountManager {
}
/**
- * Add an account to the AccountManager's set of known accounts.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account The account to add
- * @param password The password to associate with the account. May be null.
- * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null.
- * @return true if the account was sucessfully added, false otherwise, for example,
- * if the account already exists or if the account is null
+ * Finds out whether a particular account has all the specified features.
+ * Account features are authenticator-specific string tokens identifying
+ * boolean account properties. For example, features are used to tell
+ * whether Google accounts have a particular service (such as Google
+ * Calendar or Google Talk) enabled. The feature names and their meanings
+ * are published somewhere associated with the authenticator in question.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param account The {@link Account} to test
+ * @param features An array of the account features to check
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account exists and has all of the specified features.
+ */
+ public AccountManagerFuture<Boolean> hasFeatures(final Account account,
+ final String[] features,
+ AccountManagerCallback<Boolean> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (features == null) throw new IllegalArgumentException("features is null");
+ return new Future2Task<Boolean>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.hasFeatures(mResponse, account, features);
+ }
+ public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+ }
+ }.start();
+ }
+
+ /**
+ * Lists all accounts of a type which have certain features. The account
+ * type identifies the authenticator (see {@link #getAccountsByType}).
+ * Account features are authenticator-specific string tokens identifying
+ * boolean account properties (see {@link #hasFeatures}).
+ *
+ * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator,
+ * which may contact the server or do other work to check account features,
+ * so the method returns an {@link AccountManagerFuture}.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS}.
+ *
+ * @param type The type of accounts to return, must not be null
+ * @param features An array of the account features to require,
+ * may be null or empty
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to an array of
+ * {@link Account}, one per account of the specified type which
+ * matches the requested features.
+ */
+ public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
+ final String type, final String[] features,
+ AccountManagerCallback<Account[]> callback, Handler handler) {
+ if (type == null) throw new IllegalArgumentException("type is null");
+ return new Future2Task<Account[]>(handler, callback) {
+ public void doWork() throws RemoteException {
+ mService.getAccountsByFeatures(mResponse, type, features);
+ }
+ public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
+ if (!bundle.containsKey(KEY_ACCOUNTS)) {
+ throw new AuthenticatorException("no result in response");
+ }
+ final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
+ Account[] descs = new Account[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ descs[i] = (Account) parcelables[i];
+ }
+ return descs;
+ }
+ }.start();
+ }
+
+ /**
+ * Adds an account directly to the AccountManager. Normally used by sign-up
+ * wizards associated with authenticators, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the added account's authenticator.
+ *
+ * @param account The {@link Account} to add
+ * @param password The password to associate with the account, null for none
+ * @param userdata String values to use for the account's userdata, null for none
+ * @return Whether the account was successfully added. False if the account
+ * already exists, the account is null, or another error occurs.
*/
public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
+ if (account == null) throw new IllegalArgumentException("account is null");
try {
return mService.addAccount(account, password, userdata);
} catch (RemoteException e) {
@@ -251,30 +490,29 @@ public class AccountManager {
}
/**
- * Removes the given account. If this account does not exist then this call has no effect.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ * Removes an account from the AccountManager. Does nothing if the account
+ * does not exist. Does not delete the account from the server.
+ * The authenticator may have its own policies preventing account
+ * deletion, in which case the account will not be deleted.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
*
* @param account The {@link Account} to remove
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Boolean} that is true if the account is successfully removed
- * or false if the authenticator refuses to remove the account.
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Boolean,
+ * true if the account has been successfully removed,
+ * false if the authenticator forbids deleting this account.
*/
public AccountManagerFuture<Boolean> removeAccount(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
return new Future2Task<Boolean>(handler, callback) {
public void doWork() throws RemoteException {
mService.removeAccount(mResponse, account);
@@ -289,16 +527,27 @@ public class AccountManager {
}
/**
- * Removes the given authtoken. If this authtoken does not exist for the given account type
- * then this call has no effect.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- * @param accountType the account type of the authtoken to invalidate
- * @param authToken the authtoken to invalidate
+ * Removes an auth token from the AccountManager's cache. Does nothing if
+ * the auth token is not currently in the cache. Applications must call this
+ * method when the auth token is found to have expired or otherwise become
+ * invalid for authenticating requests. The AccountManager does not validate
+ * or expire cached auth tokens otherwise.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS} or
+ * {@link android.Manifest.permission#USE_CREDENTIALS}
+ *
+ * @param accountType The account type of the auth token to invalidate, must not be null
+ * @param authToken The auth token to invalidate, may be null
*/
public void invalidateAuthToken(final String accountType, final String authToken) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
try {
- mService.invalidateAuthToken(accountType, authToken);
+ if (authToken != null) {
+ mService.invalidateAuthToken(accountType, authToken);
+ }
} catch (RemoteException e) {
// won't ever happen
throw new RuntimeException(e);
@@ -306,27 +555,25 @@ public class AccountManager {
}
/**
- * Gets the authtoken named by "authTokenType" for the specified account if it is cached
- * by the AccountManager. If no authtoken is cached then null is returned rather than
- * asking the authenticaticor to generate one. If the account or the
- * authtoken do not exist then null is returned.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose authtoken is to be retrieved, must not be null
- * @param authTokenType the type of authtoken to retrieve
- * @return an authtoken for the given account and authTokenType, if one is cached by the
- * AccountManager, null otherwise.
+ * Gets an auth token from the AccountManager's cache. If no auth
+ * token is cached for this account, null will be returned -- a new
+ * auth token will not be generated, and the server will not be contacted.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The type of auth token to fetch, see {#getAuthToken}
+ * @return The cached auth token for this account and type, or null if
+ * no auth token is cached or the account does not exist.
*/
public String peekAuthToken(final Account account, final String authTokenType) {
- if (account == null) {
- Log.e(TAG, "peekAuthToken: the account must not be null");
- return null;
- }
- if (authTokenType == null) {
- return null;
- }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
try {
return mService.peekAuthToken(account, authTokenType);
} catch (RemoteException e) {
@@ -336,20 +583,22 @@ public class AccountManager {
}
/**
- * Sets the password for the account. The password may be null. If the account does not exist
- * then this call has no affect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose password is to be set. Must not be null.
- * @param password the password to set for the account. May be null.
+ * Sets or forgets a saved password. This modifies the local copy of the
+ * password used to automatically authenticate the user; it does
+ * not change the user's account password on the server. Intended for use
+ * by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and have the same UID as the account's authenticator.
+ *
+ * @param account The account to set a password for
+ * @param password The password to set, null to clear the password
*/
public void setPassword(final Account account, final String password) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
+ if (account == null) throw new IllegalArgumentException("account is null");
try {
mService.setPassword(account, password);
} catch (RemoteException e) {
@@ -359,17 +608,21 @@ public class AccountManager {
}
/**
- * Sets the password for account to null. If the account does not exist then this call
- * has no effect.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- * @param account the account whose password is to be cleared. Must not be null.
+ * Forgets a saved password. This erases the local copy of the password;
+ * it does not change the user's account password on the server.
+ * Has the same effect as setPassword(account, null) but requires fewer
+ * permissions, and may be used by applications or management interfaces
+ * to "sign out" from an account.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}
+ *
+ * @param account The account whose password to clear
*/
public void clearPassword(final Account account) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
+ if (account == null) throw new IllegalArgumentException("account is null");
try {
mService.clearPassword(account);
} catch (RemoteException e) {
@@ -379,25 +632,23 @@ public class AccountManager {
}
/**
- * Sets account's userdata named "key" to the specified value. If the account does not
- * exist then this call has no effect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose userdata is to be set. Must not be null.
- * @param key the key of the userdata to set. Must not be null.
- * @param value the value to set. May be null.
+ * Sets one userdata key for an account. Intended by use for the
+ * authenticator to stash state for itself, not directly by applications.
+ * The meaning of the keys and values is up to the authenticator.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to set the userdata for
+ * @param key The userdata key to set. Must not be null
+ * @param value The value to set, null to clear this userdata key
*/
public void setUserData(final Account account, final String key, final String value) {
- if (account == null) {
- Log.e(TAG, "the account must not be null");
- return;
- }
- if (key == null) {
- Log.e(TAG, "the key must not be null");
- return;
- }
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
try {
mService.setUserData(account, key, value);
} catch (RemoteException e) {
@@ -407,17 +658,24 @@ public class AccountManager {
}
/**
- * Sets the authtoken named by "authTokenType" to the value specified by authToken.
+ * Adds an auth token to the AccountManager cache for an account.
* If the account does not exist then this call has no effect.
- * <p>
- * Requires that the caller has permission
- * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running
- * with the same UID as the Authenticator for the account.
- * @param account the account whose authtoken is to be set. Must not be null.
- * @param authTokenType the type of the authtoken to set. Must not be null.
- * @param authToken the authToken to set. May be null.
+ * Replaces any previous auth token for this account and auth token type.
+ * Intended for use by the authenticator, not directly by applications.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS}
+ * and to have the same UID as the account's authenticator.
+ *
+ * @param account The account to set an auth token for
+ * @param authTokenType The type of the auth token, see {#getAuthToken}
+ * @param authToken The auth token to add to the cache
*/
public void setAuthToken(Account account, final String authTokenType, final String authToken) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
try {
mService.setAuthToken(account, authTokenType, authToken);
} catch (RemoteException e) {
@@ -427,79 +685,104 @@ public class AccountManager {
}
/**
- * Convenience method that makes a blocking call to
- * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}
- * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
- * @param account the account whose authtoken is to be retrieved, must not be null
- * @param authTokenType the type of authtoken to retrieve
- * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification
- * for the account if no authtoken is cached by the AccountManager and the the authenticator
- * does not have valid credentials to get an authtoken.
- * @return an authtoken for the given account and authTokenType, if one is cached by the
- * AccountManager, null otherwise.
- * @throws AuthenticatorException if the authenticator is not present, unreachable or returns
- * an invalid response.
- * @throws OperationCanceledException if the request is canceled for any reason
- * @throws java.io.IOException if the authenticator experiences an IOException while attempting
- * to communicate with its backend server.
+ * This convenience helper synchronously gets an auth token with
+ * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
+ *
+ * <p>This method may block while a network request completes, and must
+ * never be made from the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, see {#link getAuthToken}
+ * @param notifyAuthFailure If true, display a notification and return null
+ * if authentication fails; if false, prompt and wait for the user to
+ * re-enter correct credentials before returning
+ * @return An auth token of the specified type for this account, or null
+ * if authentication fails or none can be fetched.
+ * @throws AuthenticatorException if the authenticator failed to respond
+ * @throws OperationCanceledException if the request was canceled for any
+ * reason, including the user canceling a credential request
+ * @throws java.io.IOException if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
*/
public String blockingGetAuthToken(Account account, String authTokenType,
boolean notifyAuthFailure)
throws OperationCanceledException, IOException, AuthenticatorException {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
null /* handler */).getResult();
+ if (bundle == null) {
+ // This should never happen, but it does, occasionally. If it does return null to
+ // signify that we were not able to get the authtoken.
+ // TODO: remove this when the bug is found that sometimes causes a null bundle to be
+ // returned
+ Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
+ + account + ", authTokenType " + authTokenType);
+ return null;
+ }
return bundle.getString(KEY_AUTHTOKEN);
}
/**
- * Request that an authtoken of the specified type be returned for an account.
- * If the Account Manager has a cached authtoken of the requested type then it will
- * service the request itself. Otherwise it will pass the request on to the authenticator.
- * The authenticator can try to service this request with information it already has stored
- * in the AccountManager but may need to launch an activity to prompt the
- * user to enter credentials. If it is able to retrieve the authtoken it will be returned
- * in the result.
- * <p>
- * If the authenticator needs to prompt the user for credentials it will return an intent to
- * the activity that will do the prompting. If an activity is supplied then that activity
- * will be used to launch the intent and the result will come from it. Otherwise a result will
- * be returned that contains the intent.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
- *
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains:
+ * Gets an auth token of the specified type for a particular account,
+ * prompting the user for credentials if necessary. This method is
+ * intended for applications running in the foreground where it makes
+ * sense to ask the user directly for a password.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it will be returned. Otherwise, if we have a saved password
+ * the server accepts, it will be used to generate a new auth token.
+ * Otherwise, the user will be asked for a password, which will be sent to
+ * the server to generate a new auth token.
+ *
+ * <p>The value of the auth token type depends on the authenticator.
+ * Some services use different tokens to access different functionality --
+ * for example, Google uses different auth tokens to access Gmail and
+ * Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param options Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user for a password
+ * if necessary; used only to call startActivity(); must not be null.
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
* <ul>
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If an auth token
+ * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> getAuthToken(
final Account account, final String authTokenType, final Bundle options,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (activity == null) throw new IllegalArgumentException("activity is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
@@ -511,46 +794,71 @@ public class AccountManager {
}
/**
- * Request that an authtoken of the specified type be returned for an account.
- * If the Account Manager has a cached authtoken of the requested type then it will
- * service the request itself. Otherwise it will pass the request on to the authenticator.
- * The authenticator can try to service this request with information it already has stored
- * in the AccountManager but may need to launch an activity to prompt the
- * user to enter credentials. If it is able to retrieve the authtoken it will be returned
- * in the result.
- * <p>
- * If the authenticator needs to prompt the user for credentials it will return an intent for
- * an activity that will do the prompting. If an intent is returned and notifyAuthFailure
- * is true then a notification will be created that launches this intent.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}.
- *
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the
- * result then a "sign-on needed" notification will be created that will launch this intent.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * Gets an auth token of the specified type for a particular account,
+ * optionally raising a notification if the user must enter credentials.
+ * This method is intended for background tasks and services where the
+ * user should not be immediately interrupted with a password prompt.
+ *
+ * <p>If a previously generated auth token is cached for this account and
+ * type, then it will be returned. Otherwise, if we have saved credentials
+ * the server accepts, it will be used to generate a new auth token.
+ * Otherwise, an Intent will be returned which, when started, will prompt
+ * the user for a password. If the notifyAuthFailure parameter is set,
+ * the same Intent will be associated with a status bar notification,
+ * alerting the user that they need to enter a password at some point.
+ *
+ * <p>If the intent is left in a notification, you will need to wait until
+ * the user gets around to entering a password before trying again,
+ * which could be hours or days or never. When it does happen, the
+ * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION}
+ * {@link Intent}, which applications can use to trigger another attempt
+ * to fetch an auth token.
+ *
+ * <p>If notifications are not enabled, it is the application's
+ * responsibility to launch the returned intent at some point to let
+ * the user enter credentials. In either case, the result from this
+ * call will not wait for user action.
+ *
+ * <p>The value of the auth token type depends on the authenticator.
+ * Some services use different tokens to access different functionality --
+ * for example, Google uses different auth tokens to access Gmail and
+ * Google Calendar for the same account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#USE_CREDENTIALS}.
+ *
+ * @param account The account to fetch an auth token for
+ * @param authTokenType The auth token type, an authenticator-dependent
+ * string token, must not be null
+ * @param notifyAuthFailure True to add a notification to prompt the
+ * user for a password if necessary, false to leave that to the caller
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields on success:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN}
- * if the authenticator is able to retrieve the auth token
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * (Other authenticator-specific values may be returned.) If the user
+ * must enter credentials, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation is canceled for
+ * any reason, incluidng the user canceling a credential request
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new auth token, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> getAuthToken(
final Account account, final String authTokenType, final boolean notifyAuthFailure,
@@ -566,128 +874,128 @@ public class AccountManager {
}
/**
- * Request that an account be added with the given accountType. This request
- * is processed by the authenticator for the account type. If no authenticator is registered
- * in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- *
- * @param accountType The type of account to add. This must not be null.
- * @param authTokenType The account that is added should be able to service this auth token
- * type. This may be null.
- * @param requiredFeatures The account that is added should support these features.
- * This array may be null or empty.
- * @param addAccountOptions A bundle of authenticator-specific options that is passed on
- * to the authenticator. This may be null.
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * Asks the user to add an account of a specified type. The authenticator
+ * for this account type processes this request with the appropriate user
+ * interface. If the user does elect to create a new account, the account
+ * name is returned.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param addAccountOptions Authenticator-specific options for the request,
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to create an
+ * account; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and an account was created:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
- * <li> {@link #KEY_INTENT}, or
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE}
- * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified).
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the creation process
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * creating a new account, usually because of network trouble
* </ul>
*/
public AccountManagerFuture<Bundle> addAccount(final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
- if (accountType == null) {
- Log.e(TAG, "the account must not be null");
- // to unblock caller waiting on Future.get()
- set(new Bundle());
- return;
- }
mService.addAcount(mResponse, accountType, authTokenType,
requiredFeatures, activity != null, addAccountOptions);
}
}.start();
}
- public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
- final String type, final String[] features,
- AccountManagerCallback<Account[]> callback, Handler handler) {
- return new Future2Task<Account[]>(handler, callback) {
- public void doWork() throws RemoteException {
- if (type == null) {
- Log.e(TAG, "Type is null");
- set(new Account[0]);
- return;
- }
- mService.getAccountsByFeatures(mResponse, type, features);
- }
- public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException {
- if (!bundle.containsKey(KEY_ACCOUNTS)) {
- throw new AuthenticatorException("no result in response");
- }
- final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS);
- Account[] descs = new Account[parcelables.length];
- for (int i = 0; i < parcelables.length; i++) {
- descs[i] = (Account) parcelables[i];
- }
- return descs;
- }
- }.start();
- }
-
/**
- * Requests that the authenticator checks that the user knows the credentials for the account.
- * This is typically done by returning an intent to an activity that prompts the user to
- * enter the credentials. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- *
- * @param account The account whose credentials are to be checked
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * Confirms that the user knows the password for an account to make extra
+ * sure they are the owner of the account. The user-entered password can
+ * be supplied directly, otherwise the authenticator for this account type
+ * prompts the user with the appropriate interface. This method is
+ * intended for applications which want extra assurance; for example, the
+ * phone lock screen uses this to let the user unlock the phone with an
+ * account password if they forget the lock pattern.
+ *
+ * <p>If the user-entered password matches a saved password for this
+ * account, the request is considered valid; otherwise the authenticator
+ * verifies the password (usually by contacting the server).
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The account to confirm password knowledge for
+ * @param options Authenticator-specific options for the request;
+ * if the {@link #KEY_PASSWORD} string field is present, the
+ * authenticator may use it directly rather than prompting the user;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if activity or password was supplied and
+ * the account was successfully verified:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
+ * </ul>
+ *
+ * If no activity or password was specified, the returned Bundle contains
+ * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
- * credentials
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
final Bundle options,
final Activity activity,
final AccountManagerCallback<Bundle> callback,
final Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.confirmCredentials(mResponse, account, options, activity != null);
@@ -696,48 +1004,59 @@ public class AccountManager {
}
/**
- * Requests that the authenticator update the the credentials for a user. This is typically
- * done by returning an intent to an activity that will prompt the user to update the stored
- * credentials for the account. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- *
- * @param account The account whose credentials are to be updated.
- * @param authTokenType the auth token to retrieve as part of updating the credentials.
- * May be null.
- * @param options authenticator specific options for the request
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * Asks the user to enter a new password for an account, updating the
+ * saved credentials for the account. Normally this happens automatically
+ * when the server rejects credentials during an auth token fetch, but this
+ * can be invoked directly to ensure we have the correct credentials stored.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param account The account to update credentials for
+ * @param authTokenType The credentials entered must allow an auth token
+ * of this type to be created (but no actual auth token is returned);
+ * may be null
+ * @param options Authenticator-specific options for the request;
+ * may be null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to enter a
+ * password; used only to call startActivity(); if null, the prompt
+ * will not be launched directly, but the necessary {@link Intent}
+ * will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * with these fields if an activity was supplied and the account
+ * credentials were successfully updated:
+ * <ul>
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * </ul>
+ *
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct
- * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided.
+ * <li> {@link AuthenticatorException} if the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the password prompt
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * verifying the password, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> updateCredentials(final Account account,
final String authTokenType,
final Bundle options, final Activity activity,
final AccountManagerCallback<Bundle> callback,
final Handler handler) {
+ if (account == null) throw new IllegalArgumentException("account is null");
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.updateCredentials(mResponse, account, authTokenType, activity != null,
@@ -747,41 +1066,46 @@ public class AccountManager {
}
/**
- * Request that the properties for an authenticator be updated. This is typically done by
- * returning an intent to an activity that will allow the user to make changes. This request
- * is processed by the authenticator for the account. If no matching authenticator is
- * registered in the system then {@link AuthenticatorException} is thrown.
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- *
- * @param accountType The account type of the authenticator whose properties are to be edited.
- * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then
- * the intent will be started with this activity. If activity is null then the result will
- * be returned as-is.
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * Offers the user an opportunity to change an authenticator's settings.
+ * These properties are for the authenticator in general, not a particular
+ * account. Not all authenticators support this method.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The account type associated with the authenticator
+ * to adjust
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to adjust authenticator settings;
+ * used only to call startActivity(); if null, the settings dialog will
+ * not be launched directly, but the necessary {@link Intent} will be
+ * returned to the caller instead
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle
+ * which is empty if properties were edited successfully, or
+ * if no activity was specified, contains only {@link #KEY_INTENT}
+ * needed to launch the authenticator's settings dialog.
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
* <ul>
- * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials
- * <li> nothing, returned if the edit completes successfully
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling the settings dialog
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> editProperties(final String accountType,
final Activity activity, final AccountManagerCallback<Bundle> callback,
final Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.editProperties(mResponse, accountType, activity != null);
@@ -792,14 +1116,13 @@ public class AccountManager {
private void ensureNotOnMainThread() {
final Looper looper = Looper.myLooper();
if (looper != null && looper == mContext.getMainLooper()) {
- // We really want to throw an exception here, but GTalkService exercises this
- // path quite a bit and needs some serious rewrite in order to work properly.
- //noinspection ThrowableInstanceNeverThrow
-// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
-// new Exception());
- // TODO remove the log and throw this exception when the callers are fixed
-// throw new IllegalStateException(
-// "calling this from your main thread can lead to deadlock");
+ final IllegalStateException exception = new IllegalStateException(
+ "calling this from your main thread can lead to deadlock");
+ Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs",
+ exception);
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ throw exception;
+ }
}
}
@@ -860,11 +1183,23 @@ public class AccountManager {
return this;
}
+ protected void set(Bundle bundle) {
+ // TODO: somehow a null is being set as the result of the Future. Log this
+ // case to help debug where this is occurring. When this bug is fixed this
+ // condition statement should be removed.
+ if (bundle == null) {
+ Log.e(TAG, "the bundle must not be null", new Exception());
+ }
+ super.set(bundle);
+ }
+
public abstract void doWork() throws RemoteException;
private Bundle internalGetResult(Long timeout, TimeUnit unit)
throws OperationCanceledException, IOException, AuthenticatorException {
- ensureNotOnMainThread();
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
try {
if (timeout == null) {
return get();
@@ -1030,7 +1365,9 @@ public class AccountManager {
private T internalGetResult(Long timeout, TimeUnit unit)
throws OperationCanceledException, IOException, AuthenticatorException {
- ensureNotOnMainThread();
+ if (!isDone()) {
+ ensureNotOnMainThread();
+ }
try {
if (timeout == null) {
return get();
@@ -1118,6 +1455,7 @@ public class AccountManager {
final Bundle mAddAccountOptions;
final Bundle mLoginOptions;
final AccountManagerCallback<Bundle> mMyCallback;
+ private volatile int mNumAccounts = 0;
public void doWork() throws RemoteException {
getAccountsByTypeAndFeatures(mAccountType, mFeatures,
@@ -1137,6 +1475,8 @@ public class AccountManager {
return;
}
+ mNumAccounts = accounts.length;
+
if (accounts.length == 0) {
if (mActivity != null) {
// no accounts, add one now. pretend that the user directly
@@ -1209,7 +1549,21 @@ public class AccountManager {
public void run(AccountManagerFuture<Bundle> future) {
try {
- set(future.getResult());
+ final Bundle result = future.getResult();
+ if (mNumAccounts == 0) {
+ final String accountName = result.getString(KEY_ACCOUNT_NAME);
+ final String accountType = result.getString(KEY_ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) {
+ setException(new AuthenticatorException("account not in result"));
+ return;
+ }
+ final Account account = new Account(accountName, accountType);
+ mNumAccounts = 1;
+ getAuthToken(account, mAuthTokenType, null /* options */, mActivity,
+ mMyCallback, mHandler);
+ return;
+ }
+ set(result);
} catch (OperationCanceledException e) {
cancel(true /* mayInterruptIfRUnning */);
} catch (IOException e) {
@@ -1221,57 +1575,68 @@ public class AccountManager {
}
/**
- * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures},
- * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)},
- * and {@link #addAccount}. It first gets the list of accounts that match accountType and the
- * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType
- * feature set, and addAccountOptions. If there is exactly one then
- * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is
- * called with that account. If there are more than one then a chooser activity is launched
- * to prompt the user to select one of them and then the authtoken is retrieved for it,
- * <p>
- * This call returns immediately but runs asynchronously and the result is accessed via the
- * {@link AccountManagerFuture} that is returned. This future is also passed as the sole
- * parameter to the {@link AccountManagerCallback}. If the caller wished to use this
- * method asynchronously then they will generally pass in a callback object that will get
- * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then
- * they will generally pass null for the callback and instead call
- * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value,
- * which will then block until the request completes.
- * <p>
- * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
- *
- * @param accountType the accountType to query; this must be non-null
- * @param authTokenType the type of authtoken to retrieve; this must be non-null
- * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}.
- * @param activityForPrompting The activity used to start any account management
- * activities that are required to fulfill this request. This may be null.
- * @param addAccountOptions authenticator-specific options used if an account needs to be added
- * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken
- * @param callback A callback to invoke when the request completes. If null then
- * no callback is invoked.
- * @param handler The {@link Handler} to use to invoke the callback. If null then the
- * main thread's {@link Handler} is used.
- * @return an {@link AccountManagerFuture} that represents the future result of the call.
- * The future result is a {@link Bundle} that contains either:
+ * This convenience helper combines the functionality of
+ * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and
+ * {@link #addAccount}.
+ *
+ * <p>This method gets a list of the accounts matching the
+ * specified type and feature set; if there is exactly one, it is
+ * used; if there are more than one, the user is prompted to pick one;
+ * if there are none, the user is prompted to add one. Finally,
+ * an auth token is acquired for the chosen account.
+ *
+ * <p>This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#MANAGE_ACCOUNTS}.
+ *
+ * @param accountType The account type required
+ * (see {@link #getAccountsByType}), must not be null
+ * @param authTokenType The desired auth token type
+ * (see {@link #getAuthToken}), must not be null
+ * @param features Required features for the account
+ * (see {@link #getAccountsByTypeAndFeatures}), may be null or empty
+ * @param activity The {@link Activity} context to use for launching new
+ * sub-Activities to prompt to add an account, select an account,
+ * and/or enter a password, as necessary; used only to call
+ * startActivity(); should not be null
+ * @param addAccountOptions Authenticator-specific options to use for
+ * adding new accounts; may be null or empty
+ * @param getAuthTokenOptions Authenticator-specific options to use for
+ * getting auth tokens; may be null or empty
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
+ * @param handler {@link Handler} identifying the callback thread,
+ * null for the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * at least the following fields:
* <ul>
- * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to
- * fulfill the request.
- * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the
- * request completes successfully.
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account
+ * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+ * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+ * </ul>
+ *
+ * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li> {@link AuthenticatorException} if no authenticator was registered for
+ * this account type or the authenticator failed to respond
+ * <li> {@link OperationCanceledException} if the operation was canceled for
+ * any reason, including the user canceling any operation
+ * <li> {@link IOException} if the authenticator experienced an I/O problem
+ * updating settings, usually because of network trouble
* </ul>
- * If the user presses "back" then the request will be canceled.
*/
public AccountManagerFuture<Bundle> getAuthTokenByFeatures(
final String accountType, final String authTokenType, final String[] features,
- final Activity activityForPrompting, final Bundle addAccountOptions,
+ final Activity activity, final Bundle addAccountOptions,
final Bundle getAuthTokenOptions,
final AccountManagerCallback<Bundle> callback, final Handler handler) {
if (accountType == null) throw new IllegalArgumentException("account type is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
final GetAuthTokenByTypeAndFeaturesTask task =
new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
- activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler);
+ activity, addAccountOptions, getAuthTokenOptions, callback, handler);
task.start();
return task;
}
@@ -1298,18 +1663,26 @@ public class AccountManager {
};
/**
- * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}.
- * The listener is guaranteed to be invoked on the thread of the Handler that is passed
- * in or the main thread's Handler if handler is null.
- * <p>
- * You must remove this listener before the context that was used to retrieve this
- * {@link AccountManager} instance goes away. This generally means when the Activity
- * or Service you are running is stopped.
- * @param listener the listener to add
- * @param handler the Handler whose thread will be used to invoke the listener. If null
- * the AccountManager context's main thread will be used.
- * @param updateImmediately if true then the listener will be invoked as a result of this
- * call.
+ * Adds an {@link OnAccountsUpdateListener} to this instance of the
+ * {@link AccountManager}. This listener will be notified whenever the
+ * list of accounts on the device changes.
+ *
+ * <p>As long as this listener is present, the AccountManager instance
+ * will not be garbage-collected, and neither will the {@link Context}
+ * used to retrieve it, which may be a large Activity instance. To avoid
+ * memory leaks, you must remove this listener before then. Normally
+ * listeners are added in an Activity or Service's {@link Activity#onCreate}
+ * and removed in {@link Activity#onDestroy}.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The listener to send notifications to
+ * @param handler {@link Handler} identifying the thread to use
+ * for notifications, null for the main thread
+ * @param updateImmediately If true, the listener will be invoked
+ * (on the handler thread) right away with the current account list
* @throws IllegalArgumentException if listener is null
* @throws IllegalStateException if listener was already added
*/
@@ -1331,7 +1704,7 @@ public class AccountManager {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION);
// To recover from disk-full.
- intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+ intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
}
}
@@ -1342,17 +1715,20 @@ public class AccountManager {
}
/**
- * Remove an {@link OnAccountsUpdateListener} that was previously registered with
- * {@link #addOnAccountsUpdatedListener}.
- * @param listener the listener to remove
+ * Removes an {@link OnAccountsUpdateListener} previously registered with
+ * {@link #addOnAccountsUpdatedListener}. The listener will no longer
+ * receive notifications of account changes.
+ *
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>No permission is required to call this method.
+ *
+ * @param listener The previously added listener to remove
* @throws IllegalArgumentException if listener is null
* @throws IllegalStateException if listener was not already added
*/
public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) {
- if (listener == null) {
- Log.e(TAG, "Missing listener");
- return;
- }
+ if (listener == null) throw new IllegalArgumentException("listener is null");
synchronized (mAccountsUpdatedListeners) {
if (!mAccountsUpdatedListeners.containsKey(listener)) {
Log.e(TAG, "Listener was not previously added");
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 440668f..1cd7aa7 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -16,39 +16,44 @@
package android.accounts;
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.RegisteredServicesCache;
-import android.content.pm.PackageInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.RegisteredServicesCacheListener;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Binder;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
-import android.app.PendingIntent;
-import android.app.NotificationManager;
-import android.app.Notification;
-import android.Manifest;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -58,8 +63,9 @@ import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.R;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyIntents;
/**
* A system service that provides account, password, and authtoken management for all
@@ -90,11 +96,8 @@ public class AccountManagerService
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
- private static final int MESSAGE_CONNECTED = 7;
- private static final int MESSAGE_DISCONNECTED = 8;
private final AccountAuthenticatorCache mAuthenticatorCache;
- private final AuthenticatorBindHelper mBindHelper;
private final DatabaseHelper mOpenHelper;
private final SimWatcher mSimWatcher;
@@ -220,11 +223,38 @@ public class AccountManagerService
mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
mAuthenticatorCache.setListener(this, null /* Handler */);
- mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
- MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
mSimWatcher = new SimWatcher(mContext);
sThis.set(this);
+
+ validateAccounts();
+ }
+
+ private void validateAccounts() {
+ boolean accountDeleted = false;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor cursor = db.query(TABLE_ACCOUNTS,
+ new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
+ null, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ final long accountId = cursor.getLong(0);
+ final String accountType = cursor.getString(1);
+ final String accountName = cursor.getString(2);
+ if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType))
+ == null) {
+ Log.d(TAG, "deleting account " + accountName + " because type "
+ + accountType + " no longer has a registered authenticator");
+ db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+ accountDeleted = true;
+ }
+ }
+ } finally {
+ cursor.close();
+ if (accountDeleted) {
+ sendAccountsChangedBroadcast();
+ }
+ }
}
public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
@@ -252,6 +282,7 @@ public class AccountManagerService
}
public String getPassword(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
@@ -282,6 +313,8 @@ public class AccountManagerService
}
public String getUserData(Account account, String key) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (key == null) throw new IllegalArgumentException("key is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
@@ -352,6 +385,7 @@ public class AccountManagerService
}
public boolean addAccount(Account account, String password, Bundle extras) {
+ if (account == null) throw new IllegalArgumentException("account is null");
checkAuthenticateAccountsPermission(account);
// fails if the account already exists
@@ -419,7 +453,71 @@ public class AccountManagerService
return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
}
+ public void hasFeatures(IAccountManagerResponse response,
+ Account account, String[] features) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (features == null) throw new IllegalArgumentException("features is null");
+ checkReadAccountsPermission();
+ long identityToken = clearCallingIdentity();
+ try {
+ new TestFeaturesSession(response, account, features).bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private class TestFeaturesSession extends Session {
+ private final String[] mFeatures;
+ private final Account mAccount;
+
+ public TestFeaturesSession(IAccountManagerResponse response,
+ Account account, String[] features) {
+ super(response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
+ mFeatures = features;
+ mAccount = account;
+ }
+
+ public void run() throws RemoteException {
+ try {
+ mAuthenticator.hasFeatures(this, mAccount, mFeatures);
+ } catch (RemoteException e) {
+ onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+ }
+ }
+
+ public void onResult(Bundle result) {
+ IAccountManagerResponse response = getResponseAndClose();
+ if (response != null) {
+ try {
+ if (result == null) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+ return;
+ }
+ final Bundle newResult = new Bundle();
+ newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+ result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+ response.onResult(newResult);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+ }
+
+ protected String toDebugString(long now) {
+ return super.toDebugString(now) + ", hasFeatures"
+ + ", " + mAccount
+ + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+ }
+ }
+
public void removeAccount(IAccountManagerResponse response, Account account) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
@@ -432,7 +530,8 @@ public class AccountManagerService
private class RemoveAccountSession extends Session {
final Account mAccount;
public RemoveAccountSession(IAccountManagerResponse response, Account account) {
- super(response, account.type, false /* expectActivityLaunch */);
+ super(response, account.type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mAccount = account;
}
@@ -475,7 +574,9 @@ public class AccountManagerService
}
public void invalidateAuthToken(String accountType, String authToken) {
- checkManageAccountsPermission();
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ if (authToken == null) throw new IllegalArgumentException("authToken is null");
+ checkManageAccountsOrUseCredentialsPermissions();
long identityToken = clearCallingIdentity();
try {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -568,6 +669,8 @@ public class AccountManagerService
}
public String peekAuthToken(Account account, String authTokenType) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
@@ -578,6 +681,8 @@ public class AccountManagerService
}
public void setAuthToken(Account account, String authTokenType, String authToken) {
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
@@ -588,6 +693,7 @@ public class AccountManagerService
}
public void setPassword(Account account, String password) {
+ if (account == null) throw new IllegalArgumentException("account is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
try {
@@ -601,11 +707,21 @@ public class AccountManagerService
if (account == null) {
return;
}
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_PASSWORD, password);
- mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ final ContentValues values = new ContentValues();
+ values.put(ACCOUNTS_PASSWORD, password);
+ final long accountId = getAccountId(db, account);
+ if (accountId >= 0) {
+ final String[] argsAccountId = {String.valueOf(accountId)};
+ db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
+ db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
sendAccountsChangedBroadcast();
}
@@ -614,6 +730,7 @@ public class AccountManagerService
}
public void clearPassword(Account account) {
+ if (account == null) throw new IllegalArgumentException("account is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
@@ -624,6 +741,8 @@ public class AccountManagerService
}
public void setUserData(Account account, String key, String value) {
+ if (key == null) throw new IllegalArgumentException("key is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
checkAuthenticateAccountsPermission(account);
long identityToken = clearCallingIdentity();
if (account == null) {
@@ -686,6 +805,9 @@ public class AccountManagerService
public void getAuthToken(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean notifyOnAuthFailure,
final boolean expectActivityLaunch, final Bundle loginOptions) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
final int callerUid = Binder.getCallingUid();
final boolean permissionGranted = permissionIsGranted(account, authTokenType, callerUid);
@@ -706,7 +828,8 @@ public class AccountManagerService
}
}
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ false /* stripAuthTokenFromResult */) {
protected String toDebugString(long now) {
if (loginOptions != null) loginOptions.keySet();
return super.toDebugString(now) + ", getAuthToken"
@@ -854,10 +977,13 @@ public class AccountManagerService
public void addAcount(final IAccountManagerResponse response, final String accountType,
final String authTokenType, final String[] requiredFeatures,
final boolean expectActivityLaunch, final Bundle options) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, accountType, expectActivityLaunch) {
+ new Session(response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
options);
@@ -879,10 +1005,13 @@ public class AccountManagerService
public void confirmCredentials(IAccountManagerResponse response,
final Account account, final Bundle options, final boolean expectActivityLaunch) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.confirmCredentials(this, account, options);
}
@@ -899,10 +1028,14 @@ public class AccountManagerService
public void updateCredentials(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean expectActivityLaunch,
final Bundle loginOptions) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (account == null) throw new IllegalArgumentException("account is null");
+ if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, account.type, expectActivityLaunch) {
+ new Session(response, account.type, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
}
@@ -921,10 +1054,13 @@ public class AccountManagerService
public void editProperties(IAccountManagerResponse response, final String accountType,
final boolean expectActivityLaunch) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
try {
- new Session(response, accountType, expectActivityLaunch) {
+ new Session(response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */) {
public void run() throws RemoteException {
mAuthenticator.editProperties(this, mAccountType);
}
@@ -946,7 +1082,8 @@ public class AccountManagerService
public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
String type, String[] features) {
- super(response, type, false /* expectActivityLaunch */);
+ super(response, type, false /* expectActivityLaunch */,
+ true /* stripAuthTokenFromResult */);
mFeatures = features;
}
@@ -965,8 +1102,20 @@ public class AccountManagerService
return;
}
+ final IAccountAuthenticator accountAuthenticator = mAuthenticator;
+ if (accountAuthenticator == null) {
+ // It is possible that the authenticator has died, which is indicated by
+ // mAuthenticator being set to null. If this happens then just abort.
+ // There is no need to send back a result or error in this case since
+ // that already happened when mAuthenticator was cleared.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "checkAccount: aborting session since we are no longer"
+ + " connected to the authenticator, " + toDebugString());
+ }
+ return;
+ }
try {
- mAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
+ accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
} catch (RemoteException e) {
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
}
@@ -1024,6 +1173,8 @@ public class AccountManagerService
public void getAccountsByFeatures(IAccountManagerResponse response,
String type, String[] features) {
+ if (response == null) throw new IllegalArgumentException("response is null");
+ if (type == null) throw new IllegalArgumentException("accountType is null");
checkReadAccountsPermission();
if (features != null && type == null) {
if (response != null) {
@@ -1038,7 +1189,10 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
if (features == null || features.length == 0) {
- getAccountsByType(type);
+ Account[] accounts = getAccountsByType(type);
+ Bundle result = new Bundle();
+ result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+ onResult(response, result);
return;
}
new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
@@ -1075,7 +1229,7 @@ public class AccountManagerService
}
private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
+ implements IBinder.DeathRecipient, ServiceConnection {
IAccountManagerResponse mResponse;
final String mAccountType;
final boolean mExpectActivityLaunch;
@@ -1088,11 +1242,14 @@ public class AccountManagerService
IAccountAuthenticator mAuthenticator = null;
+ private final boolean mStripAuthTokenFromResult;
+
public Session(IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch) {
+ boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
super();
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ mStripAuthTokenFromResult = stripAuthTokenFromResult;
mResponse = response;
mAccountType = accountType;
mExpectActivityLaunch = expectActivityLaunch;
@@ -1157,7 +1314,7 @@ public class AccountManagerService
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
}
- if (!mBindHelper.bind(mAccountType, this)) {
+ if (!bindToAuthenticator(mAccountType)) {
Log.d(TAG, "bind attempt failed for " + toDebugString());
onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
}
@@ -1166,7 +1323,7 @@ public class AccountManagerService
private void unbind() {
if (mAuthenticator != null) {
mAuthenticator = null;
- mBindHelper.unbind(this);
+ mContext.unbindService(this);
}
}
@@ -1179,7 +1336,7 @@ public class AccountManagerService
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
- public void onConnected(IBinder service) {
+ public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
run();
@@ -1189,9 +1346,7 @@ public class AccountManagerService
}
}
- public abstract void run() throws RemoteException;
-
- public void onDisconnected() {
+ public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1200,6 +1355,8 @@ public class AccountManagerService
}
}
+ public abstract void run() throws RemoteException;
+
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1231,6 +1388,9 @@ public class AccountManagerService
response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
"null bundle returned");
} else {
+ if (mStripAuthTokenFromResult) {
+ result.remove(AccountManager.KEY_AUTHTOKEN);
+ }
response.onResult(result);
}
} catch (RemoteException e) {
@@ -1269,6 +1429,39 @@ public class AccountManagerService
}
}
}
+
+ /**
+ * find the component name for the authenticator and initiate a bind
+ * if no authenticator or the bind fails then return false, otherwise return true
+ */
+ private boolean bindToAuthenticator(String authenticatorType) {
+ AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
+ mAuthenticatorCache.getServiceInfo(
+ AuthenticatorDescription.newKey(authenticatorType));
+ if (authenticatorInfo == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "there is no authenticator for " + authenticatorType
+ + ", bailing out");
+ }
+ return false;
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+ intent.setComponent(authenticatorInfo.componentName);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+ }
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+ }
+ return false;
+ }
+
+
+ return true;
+ }
}
private class MessageHandler extends Handler {
@@ -1277,9 +1470,6 @@ public class AccountManagerService
}
public void handleMessage(Message msg) {
- if (mBindHelper.handleMessage(msg)) {
- return;
- }
switch (msg.what) {
case MESSAGE_TIMED_OUT:
Session session = (Session)msg.obj;
@@ -1292,9 +1482,14 @@ public class AccountManagerService
}
}
+ private static String getDatabaseName() {
+ return DATABASE_NAME;
+ }
+
private class DatabaseHelper extends SQLiteOpenHelper {
+
public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION);
}
@Override
@@ -1419,16 +1614,58 @@ public class AccountManagerService
*/
@Override
public void onReceive(Context context, Intent intent) {
- // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
- String imsi = ((TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE)).getSubscriberId();
+ // Check IMSI on every update; nothing happens if the IMSI
+ // is missing or unchanged.
+ TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (telephonyManager == null) {
+ Log.w(TAG, "failed to get TelephonyManager");
+ return;
+ }
+ String imsi = telephonyManager.getSubscriberId();
+
+ // If the subscriber ID is an empty string, don't do anything.
if (TextUtils.isEmpty(imsi)) return;
+ // If the current IMSI matches what's stored, don't do anything.
String storedImsi = getMetaValue("imsi");
-
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
}
+ if (imsi.equals(storedImsi)) return;
+
+ // If a CDMA phone is unprovisioned, getSubscriberId()
+ // will return a different value, but we *don't* erase the
+ // passwords. We only erase them if it has a different
+ // subscriber ID once it's provisioned.
+ if (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
+ IBinder service = ServiceManager.checkService(Context.TELEPHONY_SERVICE);
+ if (service == null) {
+ Log.w(TAG, "call to checkService(TELEPHONY_SERVICE) failed");
+ return;
+ }
+ ITelephony telephony = ITelephony.Stub.asInterface(service);
+ if (telephony == null) {
+ Log.w(TAG, "failed to get ITelephony interface");
+ return;
+ }
+ boolean needsProvisioning;
+ try {
+ needsProvisioning = telephony.getCdmaNeedsProvisioning();
+ } catch (RemoteException e) {
+ Log.w(TAG, "exception while checking provisioning", e);
+ // default to NOT wiping out the passwords
+ needsProvisioning = true;
+ }
+ if (needsProvisioning) {
+ // if the phone needs re-provisioning, don't do anything.
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "current IMSI=" + imsi + " (needs provisioning); stored IMSI=" +
+ storedImsi);
+ }
+ return;
+ }
+ }
if (!imsi.equals(storedImsi) && !TextUtils.isEmpty(storedImsi)) {
Log.w(TAG, "wiping all passwords and authtokens because IMSI changed ("
@@ -1489,9 +1726,16 @@ public class AccountManagerService
}
}
} else {
+ Account[] accounts = getAccountsByType(null /* type */);
+ fout.println("Accounts: " + accounts.length);
+ for (Account account : accounts) {
+ fout.println(" " + account);
+ }
+
+ fout.println();
synchronized (mSessions) {
final long now = SystemClock.elapsedRealtime();
- fout.println("AccountManagerService: " + mSessions.size() + " sessions");
+ fout.println("Active Sessions: " + mSessions.size());
for (Session session : mSessions.values()) {
fout.println(" " + session.toDebugString(now));
}
@@ -1542,17 +1786,22 @@ public class AccountManagerService
}
}
- private void checkBinderPermission(String permission) {
+ /** Succeeds if any of the specified permissions are granted. */
+ private void checkBinderPermission(String... permissions) {
final int uid = Binder.getCallingUid();
- if (mContext.checkCallingOrSelfPermission(permission) !=
- PackageManager.PERMISSION_GRANTED) {
- String msg = "caller uid " + uid + " lacks " + permission;
- Log.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "caller uid " + uid + " has " + permission);
+
+ for (String perm : permissions) {
+ if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "caller uid " + uid + " has " + perm);
+ }
+ return;
+ }
}
+
+ String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
+ Log.w(TAG, msg);
+ throw new SecurityException(msg);
}
private boolean inSystemImage(int callerUid) {
@@ -1572,6 +1821,7 @@ public class AccountManagerService
}
private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+ final boolean inSystemImage = inSystemImage(callerUid);
final boolean fromAuthenticator = account != null
&& hasAuthenticatorUid(account.type, callerUid);
final boolean hasExplicitGrants = account != null
@@ -1582,7 +1832,7 @@ public class AccountManagerService
+ ": is authenticator? " + fromAuthenticator
+ ", has explicit permission? " + hasExplicitGrants);
}
- return fromAuthenticator || hasExplicitGrants || inSystemImage(callerUid);
+ return fromAuthenticator || hasExplicitGrants || inSystemImage;
}
private boolean hasAuthenticatorUid(String accountType, int callingUid) {
@@ -1609,7 +1859,7 @@ public class AccountManagerService
if (!permissionGranted && isDebuggableMonkeyBuild) {
// TODO: Skip this check when running automated tests. Replace this
// with a more general solution.
- Log.w(TAG, "no credentials permission for usage of " + account + ", "
+ Log.d(TAG, "no credentials permission for usage of " + account + ", "
+ authTokenType + " by uid " + Binder.getCallingUid()
+ " but ignoring since this is a monkey build");
return true;
@@ -1642,6 +1892,11 @@ public class AccountManagerService
checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
}
+ private void checkManageAccountsOrUseCredentialsPermissions() {
+ checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
+ Manifest.permission.USE_CREDENTIALS);
+ }
+
/**
* Allow callers with the given uid permission to get credentials for account/authTokenType.
* <p>
@@ -1650,7 +1905,8 @@ public class AccountManagerService
* @hide
*/
public void grantAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
+ if (account == null || authTokenType == null) {
+ Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
return;
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
@@ -1680,7 +1936,8 @@ public class AccountManagerService
* @hide
*/
public void revokeAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
+ if (account == null || authTokenType == null) {
+ Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
return;
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java
deleted file mode 100644
index 2ca1f0e..0000000
--- a/core/java/android/accounts/AuthenticatorBindHelper.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-/**
- * A helper object that simplifies binding to Account Authenticators. It uses the
- * {@link AccountAuthenticatorCache} to find the component name of the authenticators,
- * allowing the user to bind by account name. It also allows multiple, simultaneous binds
- * to the same authenticator, with each bind call guaranteed to return either
- * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call
- * itself succeeds, even if the authenticator is already bound internally.
- * @hide
- */
-public class AuthenticatorBindHelper {
- private static final String TAG = "Accounts";
- private final Handler mHandler;
- private final Context mContext;
- private final int mMessageWhatConnected;
- private final int mMessageWhatDisconnected;
- private final Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
- private final Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap();
- private final AccountAuthenticatorCache mAuthenticatorCache;
-
- public AuthenticatorBindHelper(Context context,
- AccountAuthenticatorCache authenticatorCache, Handler handler,
- int messageWhatConnected, int messageWhatDisconnected) {
- mContext = context;
- mHandler = handler;
- mAuthenticatorCache = authenticatorCache;
- mMessageWhatConnected = messageWhatConnected;
- mMessageWhatDisconnected = messageWhatDisconnected;
- }
-
- public interface Callback {
- void onConnected(IBinder service);
- void onDisconnected();
- }
-
- public boolean bind(String authenticatorType, Callback callback) {
- // if the authenticator is connecting or connected then return true
- synchronized (mServiceConnections) {
- if (mServiceConnections.containsKey(authenticatorType)) {
- MyServiceConnection connection = mServiceConnections.get(authenticatorType);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "service connection already exists for " + authenticatorType);
- }
- mServiceUsers.get(authenticatorType).add(callback);
- if (connection.mService != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is connected, scheduling a connected message for "
- + authenticatorType);
- }
- connection.scheduleCallbackConnectedMessage(callback);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service is *not* connected, waiting for for "
- + authenticatorType);
- }
- }
- return true;
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no service connection for " + authenticatorType);
- }
-
- // otherwise find the component name for the authenticator and initiate a bind
- // if no authenticator or the bind fails then return false, otherwise return true
- AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
- mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType));
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- MyServiceConnection connection = new MyServiceConnection(authenticatorType);
-
- Intent intent = new Intent();
- intent.setAction("android.accounts.AccountAuthenticator");
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
- mServiceConnections.put(authenticatorType, connection);
- mServiceUsers.put(authenticatorType, Lists.newArrayList(callback));
- return true;
- }
- }
-
- public void unbind(Callback callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbinding callback " + callbackToUnbind);
- }
- synchronized (mServiceConnections) {
- for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
- final String authenticatorType = entry.getKey();
- final ArrayList<Callback> serviceUsers = entry.getValue();
- for (Callback callback : serviceUsers) {
- if (callback == callbackToUnbind) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found callback in service" + authenticatorType);
- }
- serviceUsers.remove(callbackToUnbind);
- if (serviceUsers.isEmpty()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there are no more callbacks for service "
- + authenticatorType + ", unbinding service");
- }
- unbindFromServiceLocked(authenticatorType);
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "leaving service " + authenticatorType
- + " around since there are still callbacks using it");
- }
- }
- return;
- }
- }
- }
- Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services");
- }
- }
-
- /**
- * You must synchronized on mServiceConnections before calling this
- */
- private void unbindFromServiceLocked(String authenticatorType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "unbindService from " + authenticatorType);
- }
- mContext.unbindService(mServiceConnections.get(authenticatorType));
- mServiceUsers.remove(authenticatorType);
- mServiceConnections.remove(authenticatorType);
- }
-
- private class ConnectedMessagePayload {
- public final IBinder mService;
- public final Callback mCallback;
- public ConnectedMessagePayload(IBinder service, Callback callback) {
- mService = service;
- mCallback = callback;
- }
- }
-
- private class MyServiceConnection implements ServiceConnection {
- private final String mAuthenticatorType;
- private IBinder mService = null;
-
- public MyServiceConnection(String authenticatorType) {
- mAuthenticatorType = authenticatorType;
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is connected
- synchronized (mServiceConnections) {
- mService = service;
- for (Callback callback : mServiceUsers.get(mAuthenticatorType)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became connected, scheduling a connected "
- + "message for " + mAuthenticatorType);
- }
- scheduleCallbackConnectedMessage(callback);
- }
- }
- }
-
- private void scheduleCallbackConnectedMessage(Callback callback) {
- final ConnectedMessagePayload payload =
- new ConnectedMessagePayload(mService, callback);
- mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget();
- }
-
- public void onServiceDisconnected(ComponentName name) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType);
- }
- // post a message for each service user to tell them that the service is disconnected,
- // and unbind from the service.
- synchronized (mServiceConnections) {
- final ArrayList<Callback> callbackList = mServiceUsers.get(mAuthenticatorType);
- if (callbackList != null) {
- for (Callback callback : callbackList) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the service became disconnected, scheduling a "
- + "disconnected message for "
- + mAuthenticatorType);
- }
- mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget();
- }
- unbindFromServiceLocked(mAuthenticatorType);
- }
- }
- }
- }
-
- boolean handleMessage(Message message) {
- if (message.what == mMessageWhatConnected) {
- ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected");
- }
- payload.mCallback.onConnected(payload.mService);
- return true;
- } else if (message.what == mMessageWhatDisconnected) {
- Callback callback = (Callback)message.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "notifying callback " + callback + " that it is disconnected");
- }
- callback.onDisconnected();
- return true;
- } else {
- return false;
- }
- }
-}
diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java
index 91c94e6..c651567 100644
--- a/core/java/android/accounts/AuthenticatorDescription.java
+++ b/core/java/android/accounts/AuthenticatorDescription.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.accounts;
import android.os.Parcelable;
@@ -91,7 +107,7 @@ public class AuthenticatorDescription implements Parcelable {
return "AuthenticatorDescription {type=" + type + "}";
}
- /** @inhericDoc */
+ /** @inheritDoc */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type);
dest.writeString(packageName);
diff --git a/core/java/android/accounts/ChooseAccountActivity.java b/core/java/android/accounts/ChooseAccountActivity.java
index 4a0018e..0bbb6fc 100644
--- a/core/java/android/accounts/ChooseAccountActivity.java
+++ b/core/java/android/accounts/ChooseAccountActivity.java
@@ -36,14 +36,15 @@ public class ChooseAccountActivity extends ListActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (savedInstanceState == null) {
- mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
- mAccountManagerResponse =
- getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
- } else {
- mAccounts = savedInstanceState.getParcelableArray(AccountManager.KEY_ACCOUNTS);
- mAccountManagerResponse =
- savedInstanceState.getParcelable(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
+ mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
+ mAccountManagerResponse =
+ getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
+
+ // KEY_ACCOUNTS is a required parameter
+ if (mAccounts == null) {
+ setResult(RESULT_CANCELED);
+ finish();
+ return;
}
String[] mAccountNames = new String[mAccounts.length];
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 4282c1b..fd340cbe 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -18,15 +18,16 @@ package android.accounts;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
+import android.widget.LinearLayout;
+import android.widget.ImageView;
import android.view.View;
import android.view.LayoutInflater;
-import android.view.ViewGroup;
+import android.view.Window;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.text.TextUtils;
+import android.graphics.drawable.Drawable;
import com.android.internal.R;
/**
@@ -44,62 +45,76 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
private String mAuthTokenType;
private int mUid;
private Bundle mResultBundle = null;
+ protected LayoutInflater mInflater;
protected void onCreate(Bundle savedInstanceState) {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
- getWindow().setContentView(R.layout.grant_credentials_permission);
- mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
- mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
- mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
- final String accountTypeLabel =
- getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
- final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
-
- findViewById(R.id.allow).setOnClickListener(this);
- findViewById(R.id.deny).setOnClickListener(this);
-
- TextView messageView = (TextView) getWindow().findViewById(R.id.message);
- String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
- if (TextUtils.isEmpty(authTokenLabel)) {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- mAccount.name, accountTypeLabel));
- } else {
- CharSequence grantCredentialsPermissionFormat = getResources().getText(
- R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
- messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
- authTokenLabel, mAccount.name, accountTypeLabel));
+ setContentView(R.layout.grant_credentials_permission);
+
+ mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final Bundle extras = getIntent().getExtras();
+ mAccount = extras.getParcelable(EXTRAS_ACCOUNT);
+ mAuthTokenType = extras.getString(EXTRAS_AUTH_TOKEN_TYPE);
+
+ if (mAccount == null || mAuthTokenType == null) {
+ // we were somehow started with bad parameters. abort the activity.
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ return;
}
- String[] packageLabels = new String[packages.length];
+ mUid = extras.getInt(EXTRAS_REQUESTING_UID);
+ final String accountTypeLabel = extras.getString(EXTRAS_ACCOUNT_TYPE_LABEL);
+ final String[] packages = extras.getStringArray(EXTRAS_PACKAGES);
+ final String authTokenLabel = extras.getString(EXTRAS_AUTH_TOKEN_LABEL);
+
+ findViewById(R.id.allow_button).setOnClickListener(this);
+ findViewById(R.id.deny_button).setOnClickListener(this);
+
+ LinearLayout packagesListView = (LinearLayout) findViewById(R.id.packages_list);
+
final PackageManager pm = getPackageManager();
- for (int i = 0; i < packages.length; i++) {
+ for (String pkg : packages) {
+ String packageLabel;
try {
- packageLabels[i] =
- pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
+ packageLabel = pm.getApplicationLabel(pm.getApplicationInfo(pkg, 0)).toString();
} catch (PackageManager.NameNotFoundException e) {
- packageLabels[i] = packages[i];
+ packageLabel = pkg;
}
+ packagesListView.addView(newPackageView(packageLabel));
+ }
+
+ ((TextView) findViewById(R.id.account_name)).setText(mAccount.name);
+ ((TextView) findViewById(R.id.account_type)).setText(accountTypeLabel);
+ TextView authTokenTypeView = (TextView) findViewById(R.id.authtoken_type);
+ if (TextUtils.isEmpty(authTokenLabel)) {
+ authTokenTypeView.setVisibility(View.GONE);
+ } else {
+ authTokenTypeView.setText(authTokenLabel);
}
- ((ListView) findViewById(R.id.packages_list)).setAdapter(
- new PackagesArrayAdapter(this, packageLabels));
+ }
+
+ private View newPackageView(String packageLabel) {
+ View view = mInflater.inflate(R.layout.permissions_package_list_item, null);
+ ((TextView) view.findViewById(R.id.package_label)).setText(packageLabel);
+ return view;
}
public void onClick(View v) {
+ final AccountManagerService accountManagerService = AccountManagerService.getSingleton();
switch (v.getId()) {
- case R.id.allow:
- AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.allow_button:
+ accountManagerService.grantAppPermission(mAccount, mAuthTokenType, mUid);
Intent result = new Intent();
result.putExtra("retry", true);
setResult(RESULT_OK, result);
setAccountAuthenticatorResult(result.getExtras());
break;
- case R.id.deny:
- AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
- mUid);
+ case R.id.deny_button:
+ accountManagerService.revokeAppPermission(mAccount, mAuthTokenType, mUid);
setResult(RESULT_CANCELED);
break;
}
@@ -111,63 +126,20 @@ public class GrantCredentialsPermissionActivity extends Activity implements View
}
/**
- * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
+ * Sends the result or a {@link AccountManager#ERROR_CODE_CANCELED} error if a
+ * result isn't present.
*/
public void finish() {
Intent intent = getIntent();
- AccountAuthenticatorResponse accountAuthenticatorResponse =
- intent.getParcelableExtra(EXTRAS_RESPONSE);
- if (accountAuthenticatorResponse != null) {
+ AccountAuthenticatorResponse response = intent.getParcelableExtra(EXTRAS_RESPONSE);
+ if (response != null) {
// send the result bundle back if set, otherwise send an error.
if (mResultBundle != null) {
- accountAuthenticatorResponse.onResult(mResultBundle);
+ response.onResult(mResultBundle);
} else {
- accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
+ response.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
}
}
super.finish();
}
-
- private static class PackagesArrayAdapter extends ArrayAdapter<String> {
- protected LayoutInflater mInflater;
- private static final int mResource = R.layout.simple_list_item_1;
-
- public PackagesArrayAdapter(Context context, String[] items) {
- super(context, mResource, items);
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- static class ViewHolder {
- TextView label;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- // A ViewHolder keeps references to children views to avoid unneccessary calls
- // to findViewById() on each row.
- ViewHolder holder;
-
- // When convertView is not null, we can reuse it directly, there is no need
- // to reinflate it. We only inflate a new View when the convertView supplied
- // by ListView is null.
- if (convertView == null) {
- convertView = mInflater.inflate(mResource, null);
-
- // Creates a ViewHolder and store references to the two children views
- // we want to bind data to.
- holder = new ViewHolder();
- holder.label = (TextView) convertView.findViewById(R.id.text1);
-
- convertView.setTag(holder);
- } else {
- // Get the ViewHolder back to get fast access to the TextView
- // and the ImageView.
- holder = (ViewHolder) convertView.getTag();
- }
-
- holder.label.setText(getItem(position));
-
- return convertView;
- }
- }
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0e318c0..36a5653 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -31,6 +31,7 @@ interface IAccountManager {
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes();
Account[] getAccounts(String accountType);
+ void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
boolean addAccount(in Account account, String password, in Bundle extras);
void removeAccount(in IAccountManagerResponse response, in Account account);
@@ -45,7 +46,7 @@ interface IAccountManager {
String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch,
in Bundle options);
void addAcount(in IAccountManagerResponse response, String accountType,
- String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+ String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
in Bundle options);
void updateCredentials(in IAccountManagerResponse response, in Account account,
String authTokenType, boolean expectActivityLaunch, in Bundle options);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 49ebce3..a962391 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -39,6 +39,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -608,8 +609,13 @@ public class Activity extends ContextThemeWrapper
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
+ private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
- private SparseArray<Dialog> mManagedDialogs;
+ private static class ManagedDialog {
+ Dialog mDialog;
+ Bundle mArgs;
+ }
+ private SparseArray<ManagedDialog> mManagedDialogs;
// set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.
private Instrumentation mInstrumentation;
@@ -672,17 +678,19 @@ public class Activity extends ContextThemeWrapper
private Thread mUiThread;
private final Handler mHandler = new Handler();
+ // Used for debug only
+ /*
public Activity() {
++sInstanceCount;
}
-
@Override
protected void finalize() throws Throwable {
super.finalize();
--sInstanceCount;
}
-
+ */
+
public static long getInstanceCount() {
return sInstanceCount;
}
@@ -811,7 +819,7 @@ public class Activity extends ContextThemeWrapper
/**
* This method is called after {@link #onStart} when the activity is
* being re-initialized from a previously saved state, given here in
- * <var>state</var>. Most implementations will simply use {@link #onCreate}
+ * <var>savedInstanceState</var>. Most implementations will simply use {@link #onCreate}
* to restore their state, but it is sometimes convenient to do it here
* after all of the initialization has been done or to allow subclasses to
* decide whether to use your default implementation. The default
@@ -850,35 +858,41 @@ public class Activity extends ContextThemeWrapper
final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY);
final int numDialogs = ids.length;
- mManagedDialogs = new SparseArray<Dialog>(numDialogs);
+ mManagedDialogs = new SparseArray<ManagedDialog>(numDialogs);
for (int i = 0; i < numDialogs; i++) {
final Integer dialogId = ids[i];
Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
if (dialogState != null) {
// Calling onRestoreInstanceState() below will invoke dispatchOnCreate
// so tell createDialog() not to do it, otherwise we get an exception
- final Dialog dialog = createDialog(dialogId, dialogState);
- mManagedDialogs.put(dialogId, dialog);
- onPrepareDialog(dialogId, dialog);
- dialog.onRestoreInstanceState(dialogState);
+ final ManagedDialog md = new ManagedDialog();
+ md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId));
+ md.mDialog = createDialog(dialogId, dialogState, md.mArgs);
+ if (md.mDialog != null) {
+ mManagedDialogs.put(dialogId, md);
+ onPrepareDialog(dialogId, md.mDialog, md.mArgs);
+ md.mDialog.onRestoreInstanceState(dialogState);
+ }
}
}
}
- private Dialog createDialog(Integer dialogId, Bundle state) {
- final Dialog dialog = onCreateDialog(dialogId);
+ private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) {
+ final Dialog dialog = onCreateDialog(dialogId, args);
if (dialog == null) {
- throw new IllegalArgumentException("Activity#onCreateDialog did "
- + "not create a dialog for id " + dialogId);
+ return null;
}
dialog.dispatchOnCreate(state);
return dialog;
}
- private String savedDialogKeyFor(int key) {
+ private static String savedDialogKeyFor(int key) {
return SAVED_DIALOG_KEY_PREFIX + key;
}
+ private static String savedDialogArgsKeyFor(int key) {
+ return SAVED_DIALOG_ARGS_KEY_PREFIX + key;
+ }
/**
* Called when activity start-up is complete (after {@link #onStart}
@@ -1095,8 +1109,11 @@ public class Activity extends ContextThemeWrapper
for (int i = 0; i < numDialogs; i++) {
final int key = mManagedDialogs.keyAt(i);
ids[i] = key;
- final Dialog dialog = mManagedDialogs.valueAt(i);
- dialogState.putBundle(savedDialogKeyFor(key), dialog.onSaveInstanceState());
+ final ManagedDialog md = mManagedDialogs.valueAt(i);
+ dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState());
+ if (md.mArgs != null) {
+ dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs);
+ }
}
dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids);
@@ -1282,23 +1299,31 @@ public class Activity extends ContextThemeWrapper
// dismiss any dialogs we are managing.
if (mManagedDialogs != null) {
-
final int numDialogs = mManagedDialogs.size();
for (int i = 0; i < numDialogs; i++) {
- final Dialog dialog = mManagedDialogs.valueAt(i);
- if (dialog.isShowing()) {
- dialog.dismiss();
+ final ManagedDialog md = mManagedDialogs.valueAt(i);
+ if (md.mDialog.isShowing()) {
+ md.mDialog.dismiss();
}
}
+ mManagedDialogs = null;
}
// close any cursors we are managing.
- int numCursors = mManagedCursors.size();
- for (int i = 0; i < numCursors; i++) {
- ManagedCursor c = mManagedCursors.get(i);
- if (c != null) {
- c.mCursor.close();
+ synchronized (mManagedCursors) {
+ int numCursors = mManagedCursors.size();
+ for (int i = 0; i < numCursors; i++) {
+ ManagedCursor c = mManagedCursors.get(i);
+ if (c != null) {
+ c.mCursor.close();
+ }
}
+ mManagedCursors.clear();
+ }
+
+ // Close any open search dialog
+ if (mSearchManager != null) {
+ mSearchManager.stopSearch();
}
}
@@ -2087,8 +2112,8 @@ public class Activity extends ContextThemeWrapper
event.setPackageName(getPackageName());
LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
- (params.height == LayoutParams.FILL_PARENT);
+ boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+ (params.height == LayoutParams.MATCH_PARENT);
event.setFullScreen(isFullScreen);
CharSequence title = getTitle();
@@ -2410,36 +2435,57 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}.
+ */
+ @Deprecated
+ protected Dialog onCreateDialog(int id) {
+ return null;
+ }
+
+ /**
* Callback for creating dialogs that are managed (saved and restored) for you
- * by the activity.
+ * by the activity. The default implementation calls through to
+ * {@link #onCreateDialog(int)} for compatibility.
*
- * If you use {@link #showDialog(int)}, the activity will call through to
+ * <p>If you use {@link #showDialog(int)}, the activity will call through to
* this method the first time, and hang onto it thereafter. Any dialog
* that is created by this method will automatically be saved and restored
* for you, including whether it is showing.
*
- * If you would like the activity to manage the saving and restoring dialogs
+ * <p>If you would like the activity to manage saving and restoring dialogs
* for you, you should override this method and handle any ids that are
* passed to {@link #showDialog}.
*
- * If you would like an opportunity to prepare your dialog before it is shown,
- * override {@link #onPrepareDialog(int, Dialog)}.
+ * <p>If you would like an opportunity to prepare your dialog before it is shown,
+ * override {@link #onPrepareDialog(int, Dialog, Bundle)}.
*
* @param id The id of the dialog.
- * @return The dialog
+ * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+ * @return The dialog. If you return null, the dialog will not be created.
*
- * @see #onPrepareDialog(int, Dialog)
- * @see #showDialog(int)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
+ * @see #showDialog(int, Bundle)
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
- protected Dialog onCreateDialog(int id) {
- return null;
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ return onCreateDialog(id);
+ }
+
+ /**
+ * @deprecated Old no-arguments version of
+ * {@link #onPrepareDialog(int, Dialog, Bundle)}.
+ */
+ @Deprecated
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ dialog.setOwnerActivity(this);
}
/**
* Provides an opportunity to prepare a managed dialog before it is being
- * shown.
+ * shown. The default implementation calls through to
+ * {@link #onPrepareDialog(int, Dialog)} for compatibility.
+ *
* <p>
* Override this if you need to update a managed dialog based on the state
* of the application each time it is shown. For example, a time picker
@@ -2449,43 +2495,66 @@ public class Activity extends ContextThemeWrapper
*
* @param id The id of the managed dialog.
* @param dialog The dialog.
- * @see #onCreateDialog(int)
+ * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+ * @see #onCreateDialog(int, Bundle)
* @see #showDialog(int)
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
- protected void onPrepareDialog(int id, Dialog dialog) {
- dialog.setOwnerActivity(this);
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ onPrepareDialog(id, dialog);
+ }
+
+ /**
+ * Simple version of {@link #showDialog(int, Bundle)} that does not
+ * take any arguments. Simply calls {@link #showDialog(int, Bundle)}
+ * with null arguments.
+ */
+ public final void showDialog(int id) {
+ showDialog(id, null);
}
/**
- * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int)}
+ * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)}
* will be made with the same id the first time this is called for a given
* id. From thereafter, the dialog will be automatically saved and restored.
*
- * Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog)} will
+ * <p>Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will
* be made to provide an opportunity to do any timely preparation.
*
* @param id The id of the managed dialog.
- *
+ * @param args Arguments to pass through to the dialog. These will be saved
+ * and restored for you. Note that if the dialog is already created,
+ * {@link #onCreateDialog(int, Bundle)} will not be called with the new
+ * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be.
+ * If you need to rebuild the dialog, call {@link #removeDialog(int)} first.
+ * @return Returns true if the Dialog was created; false is returned if
+ * it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
+ *
* @see Dialog
- * @see #onCreateDialog(int)
- * @see #onPrepareDialog(int, Dialog)
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
- public final void showDialog(int id) {
+ public final boolean showDialog(int id, Bundle args) {
if (mManagedDialogs == null) {
- mManagedDialogs = new SparseArray<Dialog>();
+ mManagedDialogs = new SparseArray<ManagedDialog>();
}
- Dialog dialog = mManagedDialogs.get(id);
- if (dialog == null) {
- dialog = createDialog(id, null);
- mManagedDialogs.put(id, dialog);
+ ManagedDialog md = mManagedDialogs.get(id);
+ if (md == null) {
+ md = new ManagedDialog();
+ md.mDialog = createDialog(id, null, args);
+ if (md.mDialog == null) {
+ return false;
+ }
+ mManagedDialogs.put(id, md);
}
- onPrepareDialog(id, dialog);
- dialog.show();
+ md.mArgs = args;
+ onPrepareDialog(id, md.mDialog, args);
+ md.mDialog.show();
+ return true;
}
/**
@@ -2496,21 +2565,21 @@ public class Activity extends ContextThemeWrapper
* @throws IllegalArgumentException if the id was not previously shown via
* {@link #showDialog(int)}.
*
- * @see #onCreateDialog(int)
- * @see #onPrepareDialog(int, Dialog)
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
* @see #showDialog(int)
* @see #removeDialog(int)
*/
public final void dismissDialog(int id) {
if (mManagedDialogs == null) {
throw missingDialog(id);
-
}
- final Dialog dialog = mManagedDialogs.get(id);
- if (dialog == null) {
+
+ final ManagedDialog md = mManagedDialogs.get(id);
+ if (md == null) {
throw missingDialog(id);
}
- dialog.dismiss();
+ md.mDialog.dismiss();
}
/**
@@ -2526,28 +2595,27 @@ public class Activity extends ContextThemeWrapper
* Removes any internal references to a dialog managed by this Activity.
* If the dialog is showing, it will dismiss it as part of the clean up.
*
- * This can be useful if you know that you will never show a dialog again and
+ * <p>This can be useful if you know that you will never show a dialog again and
* want to avoid the overhead of saving and restoring it in the future.
*
* @param id The id of the managed dialog.
*
- * @see #onCreateDialog(int)
- * @see #onPrepareDialog(int, Dialog)
+ * @see #onCreateDialog(int, Bundle)
+ * @see #onPrepareDialog(int, Dialog, Bundle)
* @see #showDialog(int)
* @see #dismissDialog(int)
*/
public final void removeDialog(int id) {
-
if (mManagedDialogs == null) {
return;
}
- final Dialog dialog = mManagedDialogs.get(id);
- if (dialog == null) {
+ final ManagedDialog md = mManagedDialogs.get(id);
+ if (md == null) {
return;
}
- dialog.dismiss();
+ md.mDialog.dismiss();
mManagedDialogs.remove(id);
}
@@ -3449,17 +3517,7 @@ public class Activity extends ContextThemeWrapper
return;
}
- // uses super.getSystemService() since this.getSystemService() looks at the
- // mSearchManager field.
- mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
- int ident = mIdent;
- if (ident == 0) {
- if (mParent != null) ident = mParent.mIdent;
- if (ident == 0) {
- throw new IllegalArgumentException("no ident");
- }
- }
- mSearchManager.setIdent(ident, getComponentName());
+ mSearchManager = new SearchManager(this, null);
}
@Override
@@ -3729,13 +3787,15 @@ public class Activity extends ContextThemeWrapper
}
final void performRestart() {
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (mc.mReleased || mc.mUpdated) {
- mc.mCursor.requery();
- mc.mReleased = false;
- mc.mUpdated = false;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (mc.mReleased || mc.mUpdated) {
+ mc.mCursor.requery();
+ mc.mReleased = false;
+ mc.mUpdated = false;
+ }
}
}
@@ -3801,12 +3861,14 @@ public class Activity extends ContextThemeWrapper
" did not call through to super.onStop()");
}
- final int N = mManagedCursors.size();
- for (int i=0; i<N; i++) {
- ManagedCursor mc = mManagedCursors.get(i);
- if (!mc.mReleased) {
- mc.mCursor.deactivate();
- mc.mReleased = true;
+ synchronized (mManagedCursors) {
+ final int N = mManagedCursors.size();
+ for (int i=0; i<N; i++) {
+ ManagedCursor mc = mManagedCursors.get(i);
+ if (!mc.mReleased) {
+ mc.mCursor.deactivate();
+ mc.mReleased = true;
+ }
}
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d709deb..c9096cf 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -19,6 +19,7 @@ package android.app;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.graphics.Bitmap;
@@ -144,6 +145,13 @@ public class ActivityManager {
public static final int RECENT_WITH_EXCLUDED = 0x0001;
/**
+ * @hide
+ * TODO: Make this public. Provides a list that does not contain any
+ * recent tasks that currently are not available to the user.
+ */
+ public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
+
+ /**
* Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
@@ -606,7 +614,7 @@ public class ActivityManager {
public int uid;
/**
- * The tag that was provided when the process crashed.
+ * The activity name associated with the error, if known. May be null.
*/
public String tag;
@@ -621,9 +629,14 @@ public class ActivityManager {
public String longMsg;
/**
- * Raw data about the crash (typically a stack trace).
+ * The stack trace where the error originated. May be null.
*/
- public byte[] crashData;
+ public String stackTrace;
+
+ /**
+ * to be deprecated: This value will always be null.
+ */
+ public byte[] crashData = null;
public ProcessErrorStateInfo() {
}
@@ -640,8 +653,7 @@ public class ActivityManager {
dest.writeString(tag);
dest.writeString(shortMsg);
dest.writeString(longMsg);
- dest.writeInt(crashData == null ? -1 : crashData.length);
- dest.writeByteArray(crashData);
+ dest.writeString(stackTrace);
}
public void readFromParcel(Parcel source) {
@@ -652,13 +664,7 @@ public class ActivityManager {
tag = source.readString();
shortMsg = source.readString();
longMsg = source.readString();
- int cdLen = source.readInt();
- if (cdLen == -1) {
- crashData = null;
- } else {
- crashData = new byte[cdLen];
- source.readByteArray(crashData);
- }
+ stackTrace = source.readString();
}
public static final Creator<ProcessErrorStateInfo> CREATOR =
@@ -861,6 +867,22 @@ public class ActivityManager {
}
/**
+ * Returns a list of application processes installed on external media
+ * that are running on the device.
+ *
+ * @return Returns a list of ApplicationInfo records, or null if none
+ * This list ordering is not specified.
+ * @hide
+ */
+ public List<ApplicationInfo> getRunningExternalApplications() {
+ try {
+ return ActivityManagerNative.getDefault().getRunningExternalApplications();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns a list of application processes that are running on the device.
*
* @return Returns a list of RunningAppProcessInfo records, or null if there are no
@@ -892,6 +914,38 @@ public class ActivityManager {
}
/**
+ * @deprecated This is now just a wrapper for
+ * {@link #killBackgroundProcesses(String)}; the previous behavior here
+ * is no longer available to applications because it allows them to
+ * break other applications by removing their alarms, stopping their
+ * services, etc.
+ */
+ @Deprecated
+ public void restartPackage(String packageName) {
+ killBackgroundProcesses(packageName);
+ }
+
+ /**
+ * Have the system immediately kill all background processes associated
+ * with the given package. This is the same as the kernel killing those
+ * processes to reclaim memory; the system will take care of restarting
+ * these processes in the future as needed.
+ *
+ * <p>You must hold the permission
+ * {@link android.Manifest.permission#KILL_BACKGROUND_PROCESSES} to be able to
+ * call this method.
+ *
+ * @param packageName The name of the package whose processes are to
+ * be killed.
+ */
+ public void killBackgroundProcesses(String packageName) {
+ try {
+ ActivityManagerNative.getDefault().killBackgroundProcesses(packageName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Have the system perform a force stop of everything associated with
* the given application package. All processes that share its uid
* will be killed, all services it has running stopped, all activities
@@ -900,14 +954,18 @@ public class ActivityManager {
* be stopped, notifications removed, etc.
*
* <p>You must hold the permission
- * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to
+ * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
* call this method.
*
* @param packageName The name of the package to be stopped.
+ *
+ * @hide This is not available to third party applications due to
+ * it allowing them to break other applications by stopping their
+ * services, removing their alarms, etc.
*/
- public void restartPackage(String packageName) {
+ public void forceStopPackage(String packageName) {
try {
- ActivityManagerNative.getDefault().restartPackage(packageName);
+ ActivityManagerNative.getDefault().forceStopPackage(packageName);
} catch (RemoteException e) {
}
}
@@ -923,4 +981,15 @@ public class ActivityManager {
return null;
}
+ /**
+ * Returns "true" if the user interface is currently being messed with
+ * by a monkey.
+ */
+ public static boolean isUserAMonkey() {
+ try {
+ return ActivityManagerNative.getDefault().isUserAMonkey();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3b8aee9..f694285 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -146,6 +146,51 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case START_ACTIVITY_AND_WAIT_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder b = data.readStrongBinder();
+ IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
+ int grantedMode = data.readInt();
+ IBinder resultTo = data.readStrongBinder();
+ String resultWho = data.readString();
+ int requestCode = data.readInt();
+ boolean onlyIfNeeded = data.readInt() != 0;
+ boolean debug = data.readInt() != 0;
+ WaitResult result = startActivityAndWait(app, intent, resolvedType,
+ grantedUriPermissions, grantedMode, resultTo, resultWho,
+ requestCode, onlyIfNeeded, debug);
+ reply.writeNoException();
+ result.writeToParcel(reply, 0);
+ return true;
+ }
+
+ case START_ACTIVITY_WITH_CONFIG_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder b = data.readStrongBinder();
+ IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
+ int grantedMode = data.readInt();
+ IBinder resultTo = data.readStrongBinder();
+ String resultWho = data.readString();
+ int requestCode = data.readInt();
+ boolean onlyIfNeeded = data.readInt() != 0;
+ boolean debug = data.readInt() != 0;
+ Configuration config = Configuration.CREATOR.createFromParcel(data);
+ int result = startActivityWithConfig(app, intent, resolvedType,
+ grantedUriPermissions, grantedMode, resultTo, resultWho,
+ requestCode, onlyIfNeeded, debug, config);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case START_ACTIVITY_INTENT_SENDER_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -205,6 +250,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ boolean res = willActivityBeVisible(token);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
+
case REGISTER_RECEIVER_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -437,6 +491,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ List<ApplicationInfo> list = getRunningExternalApplications();
+ reply.writeNoException();
+ reply.writeTypedList(list);
+ return true;
+ }
+
case MOVE_TASK_TO_FRONT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int task = data.readInt();
@@ -949,10 +1011,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case KILL_PIDS_FOR_MEMORY_TRANSACTION: {
+ case KILL_PIDS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int[] pids = data.createIntArray();
- boolean res = killPidsForMemory(pids);
+ String reason = data.readString();
+ boolean res = killPids(pids, reason);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -979,21 +1042,26 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case HANDLE_APPLICATION_ERROR_TRANSACTION: {
+ case HANDLE_APPLICATION_CRASH_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder app = data.readStrongBinder();
+ ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
+ handleApplicationCrash(app, ci);
+ reply.writeNoException();
+ return true;
+ }
+
+ case HANDLE_APPLICATION_WTF_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder app = data.readStrongBinder();
- int fl = data.readInt();
String tag = data.readString();
- String shortMsg = data.readString();
- String longMsg = data.readString();
- byte[] crashData = data.createByteArray();
- int res = handleApplicationError(app, fl, tag, shortMsg, longMsg,
- crashData);
+ ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
+ boolean res = handleApplicationWtf(app, tag, ci);
reply.writeNoException();
- reply.writeInt(res);
+ reply.writeInt(res ? 1 : 0);
return true;
}
-
+
case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int sig = data.readInt();
@@ -1002,10 +1070,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case RESTART_PACKAGE_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
+ case KILL_BACKGROUND_PROCESSES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
- restartPackage(packageName);
+ killBackgroundProcesses(packageName);
+ reply.writeNoException();
+ return true;
+ }
+
+ case FORCE_STOP_PACKAGE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ forceStopPackage(packageName);
reply.writeNoException();
return true;
}
@@ -1165,6 +1241,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
int enterAnim = data.readInt();
int exitAnim = data.readInt();
overridePendingTransition(token, packageName, enterAnim, exitAnim);
+ reply.writeNoException();
+ return true;
+ }
+
+ case IS_USER_A_MONKEY_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ reply.writeInt(isUserAMonkey() ? 1 : 0);
+ reply.writeNoException();
return true;
}
}
@@ -1217,6 +1301,57 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
+ public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
+ String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
+ IBinder resultTo, String resultWho,
+ int requestCode, boolean onlyIfNeeded,
+ boolean debug) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeTypedArray(grantedUriPermissions, 0);
+ data.writeInt(grantedMode);
+ data.writeStrongBinder(resultTo);
+ data.writeString(resultWho);
+ data.writeInt(requestCode);
+ data.writeInt(onlyIfNeeded ? 1 : 0);
+ data.writeInt(debug ? 1 : 0);
+ mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ WaitResult result = WaitResult.CREATOR.createFromParcel(reply);
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+ public int startActivityWithConfig(IApplicationThread caller, Intent intent,
+ String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
+ IBinder resultTo, String resultWho,
+ int requestCode, boolean onlyIfNeeded,
+ boolean debug, Configuration config) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeTypedArray(grantedUriPermissions, 0);
+ data.writeInt(grantedMode);
+ data.writeStrongBinder(resultTo);
+ data.writeString(resultWho);
+ data.writeInt(requestCode);
+ data.writeInt(onlyIfNeeded ? 1 : 0);
+ data.writeInt(debug ? 1 : 0);
+ config.writeToParcel(data, 0);
+ mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
@@ -1292,6 +1427,18 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public boolean willActivityBeVisible(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(WILL_ACTIVITY_BE_VISIBLE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean res = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver,
IntentFilter filter, String perm) throws RemoteException
@@ -1577,6 +1724,19 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return list;
}
+ public List<ApplicationInfo> getRunningExternalApplications()
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ ArrayList<ApplicationInfo> list
+ = reply.createTypedArrayList(ApplicationInfo.CREATOR);
+ data.recycle();
+ reply.recycle();
+ return list;
+ }
public void moveTaskToFront(int task) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -2304,12 +2464,13 @@ class ActivityManagerProxy implements IActivityManager
mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0);
data.recycle();
}
- public boolean killPidsForMemory(int[] pids) throws RemoteException {
+ public boolean killPids(int[] pids, String reason) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeIntArray(pids);
- mRemote.transact(KILL_PIDS_FOR_MEMORY_TRANSACTION, data, reply, 0);
+ data.writeString(reason);
+ mRemote.transact(KILL_PIDS_TRANSACTION, data, reply, 0);
boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
@@ -2342,27 +2503,36 @@ class ActivityManagerProxy implements IActivityManager
/* this base class version is never called */
return true;
}
- public int handleApplicationError(IBinder app, int flags,
- String tag, String shortMsg, String longMsg,
- byte[] crashData) throws RemoteException
+ public void handleApplicationCrash(IBinder app,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(app);
+ crashInfo.writeToParcel(data, 0);
+ mRemote.transact(HANDLE_APPLICATION_CRASH_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+ public boolean handleApplicationWtf(IBinder app, String tag,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(app);
- data.writeInt(flags);
data.writeString(tag);
- data.writeString(shortMsg);
- data.writeString(longMsg);
- data.writeByteArray(crashData);
- mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0);
+ crashInfo.writeToParcel(data, 0);
+ mRemote.transact(HANDLE_APPLICATION_WTF_TRANSACTION, data, reply, 0);
reply.readException();
- int res = reply.readInt();
+ boolean res = reply.readInt() != 0;
reply.recycle();
data.recycle();
return res;
}
-
+
public void signalPersistentProcesses(int sig) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2374,12 +2544,23 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public void restartPackage(String packageName) throws RemoteException {
+ public void killBackgroundProcesses(String packageName) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ mRemote.transact(KILL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void forceStopPackage(String packageName) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(packageName);
- mRemote.transact(RESTART_PACKAGE_TRANSACTION, data, reply, 0);
+ mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
@@ -2565,5 +2746,17 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ public boolean isUserAMonkey() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(IS_USER_A_MONKEY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean res = reply.readInt() != 0;
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 909620d..773c344 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -16,6 +16,7 @@
package android.app;
+import android.app.backup.BackupAgent;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
@@ -38,9 +39,9 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.net.http.AndroidHttpClient;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -58,10 +59,12 @@ import android.util.Config;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import android.view.Display;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
+import android.view.ViewRoot;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -77,9 +80,12 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.net.URL;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -144,13 +150,13 @@ public final class ActivityThread {
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
- //Log.v("PackageManager", "returning cur default = " + sPackageManager);
+ //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
- //Log.v("PackageManager", "default service binder = " + b);
+ //Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
- //Log.v("PackageManager", "default service = " + sPackageManager);
+ //Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
@@ -164,7 +170,7 @@ public final class ActivityThread {
}
DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics();
mDisplay.getMetrics(metrics);
- //Log.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
+ //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
// + metrics.heightPixels + " den=" + metrics.density
// + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
return metrics;
@@ -183,14 +189,15 @@ public final class ActivityThread {
synchronized (mPackages) {
// Resources is app scale dependent.
if (false) {
- Log.w(TAG, "getTopLevelResources: " + resDir + " / "
+ Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
+ compInfo.applicationScale);
}
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
+ //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (false) {
- Log.w(TAG, "Returning cached resources " + r + " " + resDir
+ Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale);
}
return r;
@@ -198,7 +205,7 @@ public final class ActivityThread {
}
//if (r != null) {
- // Log.w(TAG, "Throwing away out-of-date resources!!!! "
+ // Slog.w(TAG, "Throwing away out-of-date resources!!!! "
// + r + " " + resDir);
//}
@@ -207,11 +214,11 @@ public final class ActivityThread {
return null;
}
- //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
+ //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics metrics = getDisplayMetricsLocked(false);
r = new Resources(assets, metrics, getConfiguration(), compInfo);
if (false) {
- Log.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale="
+ r.getCompatibilityInfo().applicationScale);
}
@@ -272,6 +279,10 @@ public final class ActivityThread {
int mClientCount = 0;
+ Application getApplication() {
+ return mApplication;
+ }
+
public PackageInfo(ActivityThread activityThread, ApplicationInfo aInfo,
ActivityThread mainThread, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode) {
@@ -292,11 +303,11 @@ public final class ActivityThread {
if (mAppDir == null) {
if (mSystemContext == null) {
mSystemContext =
- ApplicationContext.createSystemContext(mainThread);
+ ContextImpl.createSystemContext(mainThread);
mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
mainThread.getDisplayMetricsLocked(false));
- //Log.i(TAG, "Created system resources "
+ //Slog.i(TAG, "Created system resources "
// + mSystemContext.getResources() + ": "
// + mSystemContext.getResources().getConfiguration());
}
@@ -455,11 +466,12 @@ public final class ActivityThread {
* create the class loader.
*/
- if (localLOGV) Log.v(TAG, "Class path: " + zip);
+ if (localLOGV) Slog.v(TAG, "Class path: " + zip);
mClassLoader =
ApplicationLoaders.getDefault().getClassLoader(
zip, mDataDir, mBaseClassLoader);
+ initializeJavaContextClassLoader();
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
@@ -471,6 +483,120 @@ public final class ActivityThread {
}
}
+ /**
+ * Setup value for Thread.getContextClassLoader(). If the
+ * package will not run in in a VM with other packages, we set
+ * the Java context ClassLoader to the
+ * PackageInfo.getClassLoader value. However, if this VM can
+ * contain multiple packages, we intead set the Java context
+ * ClassLoader to a proxy that will warn about the use of Java
+ * context ClassLoaders and then fall through to use the
+ * system ClassLoader.
+ *
+ * <p> Note that this is similar to but not the same as the
+ * android.content.Context.getClassLoader(). While both
+ * context class loaders are typically set to the
+ * PathClassLoader used to load the package archive in the
+ * single application per VM case, a single Android process
+ * may contain several Contexts executing on one thread with
+ * their own logical ClassLoaders while the Java context
+ * ClassLoader is a thread local. This is why in the case when
+ * we have multiple packages per VM we do not set the Java
+ * context ClassLoader to an arbitrary but instead warn the
+ * user to set their own if we detect that they are using a
+ * Java library that expects it to be set.
+ */
+ private void initializeJavaContextClassLoader() {
+ IPackageManager pm = getPackageManager();
+ android.content.pm.PackageInfo pi;
+ try {
+ pi = pm.getPackageInfo(mPackageName, 0);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ /*
+ * Two possible indications that this package could be
+ * sharing its virtual machine with other packages:
+ *
+ * 1.) the sharedUserId attribute is set in the manifest,
+ * indicating a request to share a VM with other
+ * packages with the same sharedUserId.
+ *
+ * 2.) the application element of the manifest has an
+ * attribute specifying a non-default process name,
+ * indicating the desire to run in another packages VM.
+ */
+ boolean sharedUserIdSet = (pi.sharedUserId != null);
+ boolean processNameNotDefault =
+ (pi.applicationInfo != null &&
+ !mPackageName.equals(pi.applicationInfo.processName));
+ boolean sharable = (sharedUserIdSet || processNameNotDefault);
+ ClassLoader contextClassLoader =
+ (sharable)
+ ? new WarningContextClassLoader()
+ : mClassLoader;
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+
+ private static class WarningContextClassLoader extends ClassLoader {
+
+ private static boolean warned = false;
+
+ private void warn(String methodName) {
+ if (warned) {
+ return;
+ }
+ warned = true;
+ Thread.currentThread().setContextClassLoader(getParent());
+ Slog.w(TAG, "ClassLoader." + methodName + ": " +
+ "The class loader returned by " +
+ "Thread.getContextClassLoader() may fail for processes " +
+ "that host multiple applications. You should explicitly " +
+ "specify a context class loader. For example: " +
+ "Thread.setContextClassLoader(getClass().getClassLoader());");
+ }
+
+ @Override public URL getResource(String resName) {
+ warn("getResource");
+ return getParent().getResource(resName);
+ }
+
+ @Override public Enumeration<URL> getResources(String resName) throws IOException {
+ warn("getResources");
+ return getParent().getResources(resName);
+ }
+
+ @Override public InputStream getResourceAsStream(String resName) {
+ warn("getResourceAsStream");
+ return getParent().getResourceAsStream(resName);
+ }
+
+ @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
+ warn("loadClass");
+ return getParent().loadClass(className);
+ }
+
+ @Override public void setClassAssertionStatus(String cname, boolean enable) {
+ warn("setClassAssertionStatus");
+ getParent().setClassAssertionStatus(cname, enable);
+ }
+
+ @Override public void setPackageAssertionStatus(String pname, boolean enable) {
+ warn("setPackageAssertionStatus");
+ getParent().setPackageAssertionStatus(pname, enable);
+ }
+
+ @Override public void setDefaultAssertionStatus(boolean enable) {
+ warn("setDefaultAssertionStatus");
+ getParent().setDefaultAssertionStatus(enable);
+ }
+
+ @Override public void clearAssertionStatus() {
+ warn("clearAssertionStatus");
+ getParent().clearAssertionStatus();
+ }
+ }
+
public String getAppDir() {
return mAppDir;
}
@@ -513,7 +639,7 @@ public final class ActivityThread {
try {
java.lang.ClassLoader cl = getClassLoader();
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
@@ -527,7 +653,7 @@ public final class ActivityThread {
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
-
+
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
@@ -557,7 +683,7 @@ public final class ActivityThread {
"originally registered here. Are you missing a " +
"call to unregisterReceiver()?");
leak.setStackTrace(rd.getLocation().getStackTrace());
- Log.e(TAG, leak.getMessage(), leak);
+ Slog.e(TAG, leak.getMessage(), leak);
try {
ActivityManagerNative.getDefault().unregisterReceiver(
rd.getIIntentReceiver());
@@ -567,7 +693,7 @@ public final class ActivityThread {
}
}
mUnregisteredReceivers.remove(context);
- //Log.i(TAG, "Receiver registrations: " + mReceivers);
+ //Slog.i(TAG, "Receiver registrations: " + mReceivers);
HashMap<ServiceConnection, ServiceDispatcher> smap =
mServices.remove(context);
if (smap != null) {
@@ -578,7 +704,7 @@ public final class ActivityThread {
what + " " + who + " has leaked ServiceConnection "
+ sd.getServiceConnection() + " that was originally bound here");
leak.setStackTrace(sd.getLocation().getStackTrace());
- Log.e(TAG, leak.getMessage(), leak);
+ Slog.e(TAG, leak.getMessage(), leak);
try {
ActivityManagerNative.getDefault().unbindService(
sd.getIServiceConnection());
@@ -589,7 +715,7 @@ public final class ActivityThread {
}
}
mUnboundServices.remove(context);
- //Log.i(TAG, "Service registrations: " + mServices);
+ //Slog.i(TAG, "Service registrations: " + mServices);
}
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
@@ -685,8 +811,8 @@ public final class ActivityThread {
ReceiverDispatcher rd = mDispatcher.get();
if (DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
- Log.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
- + " to " + rd);
+ Slog.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
+ + " to " + (rd != null ? rd.mReceiver : null));
}
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
@@ -696,14 +822,13 @@ public final class ActivityThread {
// receiver in this process, but before it could be delivered the
// receiver was unregistered. Acknowledge the broadcast on its
// behalf so that the system's broadcast sequence can continue.
- if (DEBUG_BROADCAST) {
- Log.i(TAG, "Broadcast to unregistered receiver");
- }
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing broadcast to unregistered receiver");
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.finishReceiver(this, resultCode, data, extras, false);
} catch (RemoteException e) {
- Log.w(TAG, "Couldn't finish broadcast to unregistered receiver");
+ Slog.w(TAG, "Couldn't finish broadcast to unregistered receiver");
}
}
}
@@ -730,16 +855,29 @@ public final class ActivityThread {
BroadcastReceiver receiver = mReceiver;
if (DEBUG_BROADCAST) {
int seq = mCurIntent.getIntExtra("seq", -1);
- Log.i(TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ Slog.i(TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ " seq=" + seq + " to " + mReceiver);
+ Slog.i(TAG, " mRegistered=" + mRegistered
+ + " mCurOrdered=" + mCurOrdered);
}
+
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ Intent intent = mCurIntent;
+ mCurIntent = null;
+
if (receiver == null) {
+ if (mRegistered && mCurOrdered) {
+ try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing null broadcast to " + mReceiver);
+ mgr.finishReceiver(mIIntentReceiver,
+ mCurCode, mCurData, mCurMap, false);
+ } catch (RemoteException ex) {
+ }
+ }
return;
}
- IActivityManager mgr = ActivityManagerNative.getDefault();
- Intent intent = mCurIntent;
- mCurIntent = null;
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl);
@@ -755,6 +893,8 @@ public final class ActivityThread {
} catch (Exception e) {
if (mRegistered && mCurOrdered) {
try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing failed broadcast to " + mReceiver);
mgr.finishReceiver(mIIntentReceiver,
mCurCode, mCurData, mCurMap, false);
} catch (RemoteException ex) {
@@ -769,6 +909,8 @@ public final class ActivityThread {
}
if (mRegistered && mCurOrdered) {
try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing broadcast to " + mReceiver);
mgr.finishReceiver(mIIntentReceiver,
receiver.getResultCode(),
receiver.getResultData(),
@@ -836,7 +978,7 @@ public final class ActivityThread {
String data, Bundle extras, boolean ordered, boolean sticky) {
if (DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
- Log.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
+ Slog.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
+ " to " + mReceiver);
}
Args args = new Args();
@@ -847,9 +989,11 @@ public final class ActivityThread {
args.mCurOrdered = ordered;
args.mCurSticky = sticky;
if (!mActivityThread.post(args)) {
- if (mRegistered) {
+ if (mRegistered && ordered) {
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing sync broadcast to " + mReceiver);
mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
args.mCurData, args.mCurMap, false);
} catch (RemoteException ex) {
@@ -1145,7 +1289,7 @@ public final class ActivityThread {
}
}
- private static ApplicationContext mSystemContext = null;
+ private static ContextImpl mSystemContext = null;
private static final class ActivityRecord {
IBinder token;
@@ -1308,7 +1452,7 @@ public final class ActivityThread {
}
private static final class ContextCleanupInfo {
- ApplicationContext context;
+ ContextImpl context;
String what;
String who;
}
@@ -1322,6 +1466,7 @@ public final class ActivityThread {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
+ private static final String DB_INFO_FORMAT = " %8d %8d %10d %s";
// Formatting for checkin service - update version if row format changes
private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1392,7 +1537,7 @@ public final class ActivityThread {
r.startsNotResumed = notResumed;
r.createdConfig = config;
- synchronized (mRelaunchingActivities) {
+ synchronized (mPackages) {
mRelaunchingActivities.add(r);
}
@@ -1523,8 +1668,11 @@ public final class ActivityThread {
}
public void scheduleConfigurationChanged(Configuration config) {
- synchronized (mRelaunchingActivities) {
- mPendingConfiguration = config;
+ synchronized (mPackages) {
+ if (mPendingConfiguration == null ||
+ mPendingConfiguration.isOtherSeqNewer(config)) {
+ mPendingConfiguration = config;
+ }
}
queueOrSendMessage(H.CONFIGURATION_CHANGED, config);
}
@@ -1597,7 +1745,7 @@ public final class ActivityThread {
try {
Process.setProcessGroup(Process.myPid(), group);
} catch (Exception e) {
- Log.w(TAG, "Failed setting process group to " + group, e);
+ Slog.w(TAG, "Failed setting process group to " + group, e);
}
}
@@ -1605,6 +1753,10 @@ public final class ActivityThread {
Debug.getMemoryInfo(outInfo);
}
+ public void dispatchPackageBroadcast(int cmd, String[] packages) {
+ queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1629,7 +1781,7 @@ public final class ActivityThread {
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
- long appContextInstanceCount = ApplicationContext.getInstanceCount();
+ long appContextInstanceCount = ContextImpl.getInstanceCount();
long activityInstanceCount = Activity.getInstanceCount();
int globalAssetCount = AssetManager.getGlobalAssetCount();
int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
@@ -1638,8 +1790,7 @@ public final class ActivityThread {
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
- SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
- SQLiteDebug.getPagerStats(stats);
+ SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
// Check to see if we were called by checkin server. If so, print terse format.
boolean doCheckinFormat = false;
@@ -1713,10 +1864,15 @@ public final class ActivityThread {
// SQL
pw.print(sqliteAllocated); pw.print(',');
- pw.print(stats.databaseBytes / 1024); pw.print(',');
- pw.print(stats.numPagers); pw.print(',');
- pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
- pw.print(stats.referencedBytes / 1024); pw.print('\n');
+ pw.print(stats.memoryUsed / 1024); pw.print(',');
+ pw.print(stats.pageCacheOverflo / 1024); pw.print(',');
+ pw.print(stats.largestMemAlloc / 1024); pw.print(',');
+ for (int i = 0; i < stats.dbStats.size(); i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+ dbStats.lookaside, dbStats.dbName);
+ pw.print(',');
+ }
return;
}
@@ -1757,11 +1913,21 @@ public final class ActivityThread {
// SQLite mem info
pw.println(" ");
pw.println(" SQL");
- printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:",
- stats.databaseBytes / 1024);
- printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
- (stats.totalBytes - stats.referencedBytes) / 1024);
- printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "memoryUsed:",
+ stats.memoryUsed / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "pageCacheOverflo:", stats.pageCacheOverflo / 1024,
+ "largestMemAlloc:", stats.largestMemAlloc / 1024);
+ pw.println(" ");
+ int N = stats.dbStats.size();
+ if (N > 0) {
+ pw.println(" DATABASES");
+ printRow(pw, " %8s %8s %10s %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+ for (int i = 0; i < N; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+ dbStats.lookaside, dbStats.dbName);
+ }
+ }
// Asset details.
String assetAlloc = AssetManager.getAssetAllocations();
@@ -1814,6 +1980,8 @@ public final class ActivityThread {
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
public static final int REMOVE_PROVIDER = 131;
+ public static final int ENABLE_JIT = 132;
+ public static final int DISPATCH_PACKAGE_BROADCAST = 133;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1849,6 +2017,8 @@ public final class ActivityThread {
case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
case SUICIDE: return "SUICIDE";
case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
+ case ENABLE_JIT: return "ENABLE_JIT";
+ case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
}
}
return "(unknown)";
@@ -1966,6 +2136,12 @@ public final class ActivityThread {
case REMOVE_PROVIDER:
completeRemoveProvider((IContentProvider)msg.obj);
break;
+ case ENABLE_JIT:
+ ensureJitEnabled();
+ break;
+ case DISPATCH_PACKAGE_BROADCAST:
+ handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
+ break;
}
}
@@ -1985,7 +2161,7 @@ public final class ActivityThread {
IActivityManager am = ActivityManagerNative.getDefault();
ActivityRecord prev;
do {
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null ? a.activity.mFinished : false));
@@ -2001,6 +2177,7 @@ public final class ActivityThread {
prev.nextIdle = null;
} while (a != null);
}
+ ensureJitEnabled();
return false;
}
}
@@ -2054,6 +2231,7 @@ public final class ActivityThread {
= new HashMap<IBinder, Service>();
AppBindData mBoundApplication;
Configuration mConfiguration;
+ Configuration mResConfiguration;
Application mInitialApplication;
final ArrayList<Application> mAllApplications
= new ArrayList<Application>();
@@ -2065,26 +2243,22 @@ public final class ActivityThread {
String mInstrumentationAppPackage = null;
String mInstrumentedAppDir = null;
boolean mSystemThread = false;
-
- /**
- * Activities that are enqueued to be relaunched. This list is accessed
- * by multiple threads, so you must synchronize on it when accessing it.
- */
- final ArrayList<ActivityRecord> mRelaunchingActivities
- = new ArrayList<ActivityRecord>();
- Configuration mPendingConfiguration = null;
+ boolean mJitEnabled = false;
// These can be accessed by multiple threads; mPackages is the lock.
// XXX For now we keep around information about all packages we have
// seen, not removing entries from this map.
final HashMap<String, WeakReference<PackageInfo>> mPackages
- = new HashMap<String, WeakReference<PackageInfo>>();
+ = new HashMap<String, WeakReference<PackageInfo>>();
final HashMap<String, WeakReference<PackageInfo>> mResourcePackages
- = new HashMap<String, WeakReference<PackageInfo>>();
+ = new HashMap<String, WeakReference<PackageInfo>>();
Display mDisplay = null;
DisplayMetrics mDisplayMetrics = null;
- HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
- = new HashMap<ResourcesKey, WeakReference<Resources> >();
+ final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
+ = new HashMap<ResourcesKey, WeakReference<Resources> >();
+ final ArrayList<ActivityRecord> mRelaunchingActivities
+ = new ArrayList<ActivityRecord>();
+ Configuration mPendingConfiguration = null;
// The lock of mProviderMap protects the following variables.
final HashMap<String, ProviderRecord> mProviderMap
@@ -2106,7 +2280,9 @@ public final class ActivityThread {
ref = mResourcePackages.get(packageName);
}
PackageInfo packageInfo = ref != null ? ref.get() : null;
- //Log.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo);
+ //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo);
+ //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir
+ // + ": " + packageInfo.mResources.getAssets().isUpToDate());
if (packageInfo != null && (packageInfo.mResources == null
|| packageInfo.mResources.getAssets().isUpToDate())) {
if (packageInfo.isSecurityViolation()
@@ -2173,7 +2349,7 @@ public final class ActivityThread {
PackageInfo packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
- if (localLOGV) Log.v(TAG, (includeCode ? "Loading code package "
+ if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
@@ -2194,21 +2370,6 @@ public final class ActivityThread {
}
}
- public final boolean hasPackageInfo(String packageName) {
- synchronized (mPackages) {
- WeakReference<PackageInfo> ref;
- ref = mPackages.get(packageName);
- if (ref != null && ref.get() != null) {
- return true;
- }
- ref = mResourcePackages.get(packageName);
- if (ref != null && ref.get() != null) {
- return true;
- }
- return false;
- }
- }
-
ActivityThread() {
}
@@ -2246,17 +2407,17 @@ public final class ActivityThread {
return mBoundApplication.processName;
}
- public ApplicationContext getSystemContext() {
+ public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
- ApplicationContext context =
- ApplicationContext.createSystemContext(this);
+ ContextImpl context =
+ ContextImpl.createSystemContext(this);
PackageInfo info = new PackageInfo(this, "android", context, null);
context.init(info, null, this);
context.getResources().updateConfiguration(
getConfiguration(), getDisplayMetricsLocked(false));
mSystemContext = context;
- //Log.i(TAG, "Created system resources " + context.getResources()
+ //Slog.i(TAG, "Created system resources " + context.getResources()
// + ": " + context.getResources().getConfiguration());
}
}
@@ -2265,11 +2426,18 @@ public final class ActivityThread {
public void installSystemApplicationInfo(ApplicationInfo info) {
synchronized (this) {
- ApplicationContext context = getSystemContext();
+ ContextImpl context = getSystemContext();
context.init(new PackageInfo(this, "android", context, info), null, this);
}
}
+ void ensureJitEnabled() {
+ if (!mJitEnabled) {
+ mJitEnabled = true;
+ dalvik.system.VMRuntime.getRuntime().startJitCompilation();
+ }
+ }
+
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
@@ -2289,10 +2457,10 @@ public final class ActivityThread {
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
- //Log.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
+ //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
// + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
- //Log.i(TAG, "**** WE DO, WE DO WANT TO GC!");
+ //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
@@ -2328,7 +2496,7 @@ public final class ActivityThread {
} else {
name = "(Intent " + intent + ").getComponent() returned null";
}
- Log.v(TAG, "Performing launch: action=" + intent.getAction()
+ Slog.v(TAG, "Performing launch: action=" + intent.getAction()
+ ", comp=" + name
+ ", token=" + token);
}
@@ -2342,7 +2510,7 @@ public final class ActivityThread {
public final void sendActivityResult(
IBinder token, String id, int requestCode,
int resultCode, Intent data) {
- if (DEBUG_RESULTS) Log.v(TAG, "sendActivityResult: id=" + id
+ if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
@@ -2361,7 +2529,7 @@ public final class ActivityThread {
private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
synchronized (this) {
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
@@ -2373,7 +2541,7 @@ public final class ActivityThread {
}
}
- final void scheduleContextCleanup(ApplicationContext context, String who,
+ final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo();
cci.context = context;
@@ -2423,8 +2591,8 @@ public final class ActivityThread {
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
- if (localLOGV) Log.v(TAG, "Performing launch of " + r);
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
+ if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
@@ -2432,12 +2600,12 @@ public final class ActivityThread {
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mConfiguration);
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Launching activity "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
@@ -2482,7 +2650,6 @@ public final class ActivityThread {
" did not call through to super.onPostCreate()");
}
}
- r.state = null;
}
r.paused = true;
@@ -2507,12 +2674,13 @@ public final class ActivityThread {
// we are back active so skip it.
unscheduleGcIdler();
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
+ Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward);
if (!r.activity.mFinished && r.startsNotResumed) {
@@ -2528,6 +2696,9 @@ public final class ActivityThread {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
+ // We need to keep around the original state, in case
+ // we need to be created again.
+ r.state = oldState;
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -2609,6 +2780,8 @@ public final class ActivityThread {
receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
} catch (Exception e) {
try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing failed broadcast to " + data.intent.getComponent());
mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
data.resultData, data.resultExtras, data.resultAbort);
} catch (RemoteException ex) {
@@ -2621,7 +2794,7 @@ public final class ActivityThread {
try {
Application app = packageInfo.makeApplication(false, mInstrumentation);
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Performing receive of " + data.intent
+ ": app=" + app
+ ", appName=" + app.getPackageName()
@@ -2629,7 +2802,7 @@ public final class ActivityThread {
+ ", comp=" + data.intent.getComponent().toShortString()
+ ", dir=" + packageInfo.getAppDir());
- ApplicationContext context = (ApplicationContext)app.getBaseContext();
+ ContextImpl context = (ContextImpl)app.getBaseContext();
receiver.setOrderedHint(true);
receiver.setResult(data.resultCode, data.resultData,
data.resultExtras);
@@ -2638,6 +2811,8 @@ public final class ActivityThread {
data.intent);
} catch (Exception e) {
try {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing failed broadcast to " + data.intent.getComponent());
mgr.finishReceiver(mAppThread.asBinder(), data.resultCode,
data.resultData, data.resultExtras, data.resultAbort);
} catch (RemoteException ex) {
@@ -2651,11 +2826,15 @@ public final class ActivityThread {
try {
if (data.sync) {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing ordered broadcast to " + data.intent.getComponent());
mgr.finishReceiver(
mAppThread.asBinder(), receiver.getResultCode(),
receiver.getResultData(), receiver.getResultExtras(false),
receiver.getAbortBroadcast());
} else {
+ if (DEBUG_BROADCAST) Slog.i(TAG,
+ "Finishing broadcast to " + data.intent.getComponent());
mgr.finishReceiver(mAppThread.asBinder(), 0, null, null, false);
}
} catch (RemoteException ex) {
@@ -2664,7 +2843,7 @@ public final class ActivityThread {
// Instantiate a BackupAgent and tell it that it's alive
private final void handleCreateBackupAgent(CreateBackupAgentData data) {
- if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data);
+ if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);
// no longer idle; we have backup work to do
unscheduleGcIdler();
@@ -2673,7 +2852,7 @@ public final class ActivityThread {
PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
if (mBackupAgents.get(packageName) != null) {
- Log.d(TAG, "BackupAgent " + " for " + packageName
+ Slog.d(TAG, "BackupAgent " + " for " + packageName
+ " already exists");
return;
}
@@ -2682,7 +2861,7 @@ public final class ActivityThread {
String classname = data.appInfo.backupAgentName;
if (classname == null) {
if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
- Log.e(TAG, "Attempted incremental backup but no defined agent for "
+ Slog.e(TAG, "Attempted incremental backup but no defined agent for "
+ packageName);
return;
}
@@ -2695,10 +2874,10 @@ public final class ActivityThread {
agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
// set up the agent's context
- if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ if (DEBUG_BACKUP) Slog.v(TAG, "Initializing BackupAgent "
+ data.appInfo.backupAgentName);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
context.setOuterContext(agent);
agent.attach(context);
@@ -2709,7 +2888,7 @@ public final class ActivityThread {
} catch (Exception e) {
// If this is during restore, fail silently; otherwise go
// ahead and let the user see the crash.
- Log.e(TAG, "Agent threw during creation: " + e);
+ Slog.e(TAG, "Agent threw during creation: " + e);
if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) {
throw e;
}
@@ -2730,7 +2909,7 @@ public final class ActivityThread {
// Tear down a BackupAgent
private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
- if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
+ if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
@@ -2739,12 +2918,12 @@ public final class ActivityThread {
try {
agent.onDestroy();
} catch (Exception e) {
- Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ Slog.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
e.printStackTrace();
}
mBackupAgents.remove(packageName);
} else {
- Log.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ Slog.w(TAG, "Attempt to destroy unknown backup agent " + data);
}
}
@@ -2768,9 +2947,9 @@ public final class ActivityThread {
}
try {
- if (localLOGV) Log.v(TAG, "Creating service " + data.info.name);
+ if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
Application app = packageInfo.makeApplication(false, mInstrumentation);
@@ -2809,6 +2988,7 @@ public final class ActivityThread {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
}
+ ensureJitEnabled();
} catch (RemoteException ex) {
}
} catch (Exception e) {
@@ -2877,6 +3057,7 @@ public final class ActivityThread {
} catch (RemoteException e) {
// nothing to do.
}
+ ensureJitEnabled();
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
@@ -2891,12 +3072,12 @@ public final class ActivityThread {
Service s = mServices.remove(token);
if (s != null) {
try {
- if (localLOGV) Log.v(TAG, "Destroying service " + s);
+ if (localLOGV) Slog.v(TAG, "Destroying service " + s);
s.onDestroy();
Context context = s.getBaseContext();
- if (context instanceof ApplicationContext) {
+ if (context instanceof ContextImpl) {
final String who = s.getClassName();
- ((ApplicationContext) context).scheduleFinalCleanup(who, "Service");
+ ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
}
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
@@ -2912,13 +3093,13 @@ public final class ActivityThread {
}
}
}
- //Log.i(TAG, "Running services: " + mServices);
+ //Slog.i(TAG, "Running services: " + mServices);
}
public final ActivityRecord performResumeActivity(IBinder token,
boolean clearHide) {
ActivityRecord r = mActivities.get(token);
- if (localLOGV) Log.v(TAG, "Performing resume of " + r
+ if (localLOGV) Slog.v(TAG, "Performing resume of " + r
+ " finished=" + r.activity.mFinished);
if (r != null && !r.activity.mFinished) {
if (clearHide) {
@@ -2941,9 +3122,6 @@ public final class ActivityThread {
r.paused = false;
r.stopped = false;
- if (r.activity.mStartedActivity) {
- r.hideForNow = true;
- }
r.state = null;
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
@@ -2967,7 +3145,7 @@ public final class ActivityThread {
if (r != null) {
final Activity a = r.activity;
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
@@ -2978,7 +3156,15 @@ public final class ActivityThread {
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
- if (r.window == null && !a.mFinished && !a.mStartedActivity) {
+ boolean willBeVisible = !a.mStartedActivity;
+ if (!willBeVisible) {
+ try {
+ willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
+ a.getActivityToken());
+ } catch (RemoteException e) {
+ }
+ }
+ if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
@@ -2994,24 +3180,24 @@ public final class ActivityThread {
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
- // window visisble.
- } else if (a.mStartedActivity) {
- if (localLOGV) Log.v(
+ // window visible.
+ } else if (!willBeVisible) {
+ if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
- if (!r.activity.mFinished && !a.mStartedActivity
+ if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Resuming activity "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.newConfig);
performConfigurationChanged(r.activity, r.newConfig);
r.newConfig = null;
}
- if (localLOGV) Log.v(TAG, "Resuming " + r + " with isForward="
+ if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
+ isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
@@ -3035,7 +3221,7 @@ public final class ActivityThread {
r.nextIdle = mNewActivities;
mNewActivities = r;
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
@@ -3093,7 +3279,7 @@ public final class ActivityThread {
boolean userLeaving, int configChanges) {
ActivityRecord r = mActivities.get(token);
if (r != null) {
- //Log.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
+ //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
if (userLeaving) {
performUserLeavingActivity(r);
}
@@ -3131,7 +3317,7 @@ public final class ActivityThread {
RuntimeException e = new RuntimeException(
"Performing pause of activity that is not resumed: "
+ r.intent.getComponent().toShortString());
- Log.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, e.getMessage(), e);
}
Bundle state = null;
if (finished) {
@@ -3188,7 +3374,7 @@ public final class ActivityThread {
private final void performStopActivityInner(ActivityRecord r,
StopInfo info, boolean keepShown) {
- if (localLOGV) Log.v(TAG, "Performing stop of " + r);
+ if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
if (r != null) {
if (!keepShown && r.stopped) {
if (r.activity.mFinished) {
@@ -3200,7 +3386,7 @@ public final class ActivityThread {
RuntimeException e = new RuntimeException(
"Performing stop of activity that is not resumed: "
+ r.intent.getComponent().toShortString());
- Log.e(TAG, e.getMessage(), e);
+ Slog.e(TAG, e.getMessage(), e);
}
if (info != null) {
@@ -3249,7 +3435,7 @@ public final class ActivityThread {
}
}
if (r.newConfig != null) {
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Updating activity vis "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+ r.activityInfo.name + " with new config " + r.newConfig);
performConfigurationChanged(r.activity, r.newConfig);
r.newConfig = null;
@@ -3271,7 +3457,7 @@ public final class ActivityThread {
StopInfo info = new StopInfo();
performStopActivityInner(r, info, show);
- if (localLOGV) Log.v(
+ if (localLOGV) Slog.v(
TAG, "Finishing stop of " + r + ": show=" + show
+ " win=" + r.window);
@@ -3306,7 +3492,7 @@ public final class ActivityThread {
r.stopped = false;
}
if (r.activity.mDecor != null) {
- if (Config.LOGV) Log.v(
+ if (Config.LOGV) Slog.v(
TAG, "Handle window " + r + " visibility: " + show);
updateVisibility(r, show);
}
@@ -3320,7 +3506,7 @@ public final class ActivityThread {
if (ri.mData != null) {
ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
}
- if (DEBUG_RESULTS) Log.v(TAG,
+ if (DEBUG_RESULTS) Slog.v(TAG,
"Delivering result to activity " + r + " : " + ri);
r.activity.dispatchActivityResult(ri.mResultWho,
ri.mRequestCode, ri.mResultCode, ri.mData);
@@ -3337,7 +3523,7 @@ public final class ActivityThread {
private final void handleSendResult(ResultData res) {
ActivityRecord r = mActivities.get(res.token);
- if (DEBUG_RESULTS) Log.v(TAG, "Handling send result to " + r);
+ if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
if (r != null) {
final boolean resumed = !r.paused;
if (!r.activity.mFinished && r.activity.mDecor != null
@@ -3382,7 +3568,7 @@ public final class ActivityThread {
private final ActivityRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityRecord r = mActivities.get(token);
- if (localLOGV) Log.v(TAG, "Performing finish of " + r);
+ if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
if (r != null) {
r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
@@ -3511,8 +3697,8 @@ public final class ActivityThread {
// ApplicationContext we need to have it tear down things
// cleanly.
Context c = r.activity.getBaseContext();
- if (c instanceof ApplicationContext) {
- ((ApplicationContext) c).scheduleFinalCleanup(
+ if (c instanceof ContextImpl) {
+ ((ContextImpl) c).scheduleFinalCleanup(
r.activity.getClass().getName(), "Activity");
}
}
@@ -3532,14 +3718,14 @@ public final class ActivityThread {
Configuration changedConfig = null;
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ tmp.token + " with configChanges=0x"
+ Integer.toHexString(configChanges));
// First: make sure we have the most recent configuration and most
// recent version of the activity, or skip it if some previous call
// had taken a more recent version.
- synchronized (mRelaunchingActivities) {
+ synchronized (mPackages) {
int N = mRelaunchingActivities.size();
IBinder token = tmp.token;
tmp = null;
@@ -3554,7 +3740,7 @@ public final class ActivityThread {
}
if (tmp == null) {
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Abort, activity not relaunching!");
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Abort, activity not relaunching!");
return;
}
@@ -3569,12 +3755,16 @@ public final class ActivityThread {
// assume that is really what we want regardless of what we
// may have pending.
if (mConfiguration == null
- || mConfiguration.diff(tmp.createdConfig) != 0) {
- changedConfig = tmp.createdConfig;
+ || (tmp.createdConfig.isOtherSeqNewer(mConfiguration)
+ && mConfiguration.diff(tmp.createdConfig) != 0)) {
+ if (changedConfig == null
+ || tmp.createdConfig.isOtherSeqNewer(changedConfig)) {
+ changedConfig = tmp.createdConfig;
+ }
}
}
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ tmp.token + ": changedConfig=" + changedConfig);
// If there was a pending configuration change, execute it first.
@@ -3583,7 +3773,7 @@ public final class ActivityThread {
}
ActivityRecord r = mActivities.get(tmp.token);
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Handling relaunch of " + r);
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
if (r == null) {
return;
}
@@ -3669,7 +3859,7 @@ public final class ActivityThread {
// the activity manager may, before then, decide the
// activity needs to be destroyed to handle its new
// configuration.
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Setting activity "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Setting activity "
+ ar.activityInfo.name + " newConfig=" + newConfig);
ar.newConfig = newConfig;
}
@@ -3728,7 +3918,7 @@ public final class ActivityThread {
}
}
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Config callback " + cb
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb
+ ": shouldChangeConfig=" + shouldChangeConfig);
if (shouldChangeConfig) {
cb.onConfigurationChanged(config);
@@ -3745,62 +3935,87 @@ public final class ActivityThread {
}
}
+ final boolean applyConfigurationToResourcesLocked(Configuration config) {
+ if (mResConfiguration == null) {
+ mResConfiguration = new Configuration();
+ }
+ if (!mResConfiguration.isOtherSeqNewer(config)) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ + mResConfiguration.seq + ", newSeq=" + config.seq);
+ return false;
+ }
+ int changes = mResConfiguration.updateFrom(config);
+ DisplayMetrics dm = getDisplayMetricsLocked(true);
+
+ // set it for java, this also affects newly created Resources
+ if (config.locale != null) {
+ Locale.setDefault(config.locale);
+ }
+
+ Resources.updateSystemConfiguration(config, dm);
+
+ ContextImpl.ApplicationPackageManager.configurationChanged();
+ //Slog.i(TAG, "Configuration changed in " + currentPackageName());
+
+ Iterator<WeakReference<Resources>> it =
+ mActiveResources.values().iterator();
+ //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
+ // mActiveResources.entrySet().iterator();
+ while (it.hasNext()) {
+ WeakReference<Resources> v = it.next();
+ Resources r = v.get();
+ if (r != null) {
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ + r + " config to: " + config);
+ r.updateConfiguration(config, dm);
+ //Slog.i(TAG, "Updated app resources " + v.getKey()
+ // + " " + r + ": " + r.getConfiguration());
+ } else {
+ //Slog.i(TAG, "Removing old resources " + v.getKey());
+ it.remove();
+ }
+ }
+
+ return changes != 0;
+ }
+
final void handleConfigurationChanged(Configuration config) {
- synchronized (mRelaunchingActivities) {
+ ArrayList<ComponentCallbacks> callbacks = null;
+
+ synchronized (mPackages) {
if (mPendingConfiguration != null) {
- config = mPendingConfiguration;
+ if (!mPendingConfiguration.isOtherSeqNewer(config)) {
+ config = mPendingConfiguration;
+ }
mPendingConfiguration = null;
}
- }
- ArrayList<ComponentCallbacks> callbacks
- = new ArrayList<ComponentCallbacks>();
-
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle configuration changed: "
- + config);
+ if (config == null) {
+ return;
+ }
+
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ + config);
- synchronized(mPackages) {
+ applyConfigurationToResourcesLocked(config);
+
if (mConfiguration == null) {
mConfiguration = new Configuration();
}
- mConfiguration.updateFrom(config);
- DisplayMetrics dm = getDisplayMetricsLocked(true);
-
- // set it for java, this also affects newly created Resources
- if (config.locale != null) {
- Locale.setDefault(config.locale);
- }
-
- Resources.updateSystemConfiguration(config, dm);
-
- ApplicationContext.ApplicationPackageManager.configurationChanged();
- //Log.i(TAG, "Configuration changed in " + currentPackageName());
- {
- Iterator<WeakReference<Resources>> it =
- mActiveResources.values().iterator();
- //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
- // mActiveResources.entrySet().iterator();
- while (it.hasNext()) {
- WeakReference<Resources> v = it.next();
- Resources r = v.get();
- if (r != null) {
- r.updateConfiguration(config, dm);
- //Log.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
- } else {
- //Log.i(TAG, "Removing old resources " + v.getKey());
- it.remove();
- }
- }
+ if (!mConfiguration.isOtherSeqNewer(config)) {
+ return;
}
+ mConfiguration.updateFrom(config);
callbacks = collectComponentCallbacksLocked(false, config);
}
- final int N = callbacks.size();
- for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
+ if (callbacks != null) {
+ final int N = callbacks.size();
+ for (int i=0; i<N; i++) {
+ performConfigurationChanged(callbacks.get(i), config);
+ }
}
}
@@ -3810,7 +4025,7 @@ public final class ActivityThread {
return;
}
- if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle activity config changed: "
+ if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name);
performConfigurationChanged(r.activity, mConfiguration);
@@ -3822,13 +4037,13 @@ public final class ActivityThread {
Debug.startMethodTracing(pcd.path, pcd.fd.getFileDescriptor(),
8 * 1024 * 1024, 0);
} catch (RuntimeException e) {
- Log.w(TAG, "Profiling failed on path " + pcd.path
+ Slog.w(TAG, "Profiling failed on path " + pcd.path
+ " -- can the process access this path?");
} finally {
try {
pcd.fd.close();
} catch (IOException e) {
- Log.w(TAG, "Failure closing profile fd", e);
+ Slog.w(TAG, "Failure closing profile fd", e);
}
}
} else {
@@ -3836,11 +4051,36 @@ public final class ActivityThread {
}
}
+ final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
+ boolean hasPkgInfo = false;
+ if (packages != null) {
+ for (int i=packages.length-1; i>=0; i--) {
+ //Slog.i(TAG, "Cleaning old package: " + packages[i]);
+ if (!hasPkgInfo) {
+ WeakReference<PackageInfo> ref;
+ ref = mPackages.get(packages[i]);
+ if (ref != null && ref.get() != null) {
+ hasPkgInfo = true;
+ } else {
+ ref = mResourcePackages.get(packages[i]);
+ if (ref != null && ref.get() != null) {
+ hasPkgInfo = true;
+ }
+ }
+ }
+ mPackages.remove(packages[i]);
+ mResourcePackages.remove(packages[i]);
+ }
+ }
+ ContextImpl.ApplicationPackageManager.handlePackageBroadcast(cmd, packages,
+ hasPkgInfo);
+ }
+
final void handleLowMemory() {
ArrayList<ComponentCallbacks> callbacks
= new ArrayList<ComponentCallbacks>();
- synchronized(mPackages) {
+ synchronized (mPackages) {
callbacks = collectComponentCallbacksLocked(true, null);
}
@@ -3865,10 +4105,6 @@ public final class ActivityThread {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
- // We now rely on this being set by zygote.
- //Process.setGid(data.appInfo.gid);
- //Process.setUid(data.appInfo.uid);
-
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName);
@@ -3907,7 +4143,7 @@ public final class ActivityThread {
// XXX should have option to change the port.
Debug.changeDebugPort(8100);
if (data.debugMode == IApplicationThread.DEBUG_WAIT) {
- Log.w(TAG, "Application " + data.info.getPackageName()
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ " is waiting for the debugger on port 8100...");
IActivityManager mgr = ActivityManagerNative.getDefault();
@@ -3924,13 +4160,13 @@ public final class ActivityThread {
}
} else {
- Log.w(TAG, "Application " + data.info.getPackageName()
+ Slog.w(TAG, "Application " + data.info.getPackageName()
+ " can be debugged on port 8100...");
}
}
if (data.instrumentationName != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(data.info, null, this);
InstrumentationInfo ii = null;
try {
@@ -3955,7 +4191,7 @@ public final class ActivityThread {
instrApp.dataDir = ii.dataDir;
PackageInfo pi = getPackageInfo(instrApp,
appContext.getClassLoader(), false, true);
- ApplicationContext instrContext = new ApplicationContext();
+ ContextImpl instrContext = new ContextImpl();
instrContext.init(pi, null, this);
try {
@@ -3999,6 +4235,9 @@ public final class ActivityThread {
List<ProviderInfo> providers = data.providers;
if (providers != null) {
installContentProviders(app, providers);
+ // For process that contain content providers, we want to
+ // ensure that the JIT is enabled "at some point".
+ mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
try {
@@ -4017,7 +4256,7 @@ public final class ActivityThread {
if (mBoundApplication.profileFile != null && mBoundApplication.handlingProfiling) {
Debug.stopMethodTracing();
}
- //Log.i(TAG, "am: " + ActivityManagerNative.getDefault()
+ //Slog.i(TAG, "am: " + ActivityManagerNative.getDefault()
// + ", app thr: " + mAppThread);
try {
am.finishInstrumentation(mAppThread, resultCode, results);
@@ -4074,7 +4313,7 @@ public final class ActivityThread {
} catch (RemoteException ex) {
}
if (holder == null) {
- Log.e(TAG, "Failed to find provider info for " + name);
+ Slog.e(TAG, "Failed to find provider info for " + name);
return null;
}
if (holder.permissionFailure != null) {
@@ -4084,12 +4323,12 @@ public final class ActivityThread {
IContentProvider prov = installProvider(context, holder.provider,
holder.info, true);
- //Log.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
+ //Slog.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
if (holder.noReleaseNeeded || holder.provider == null) {
// We are not going to release the provider if it is an external
// provider that doesn't care about being released, or if it is
// a local provider running in this process.
- //Log.i(TAG, "*** NO RELEASE NEEDED");
+ //Slog.i(TAG, "*** NO RELEASE NEEDED");
synchronized(mProviderMap) {
mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000));
}
@@ -4121,7 +4360,7 @@ public final class ActivityThread {
synchronized(mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc == null) {
- if(localLOGV) Log.v(TAG, "releaseProvider::Weird shouldnt be here");
+ if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldnt be here");
return false;
} else {
prc.count--;
@@ -4153,7 +4392,7 @@ public final class ActivityThread {
if (name != null) {
try {
- if(localLOGV) Log.v(TAG, "removeProvider::Invoking " +
+ if(localLOGV) Slog.v(TAG, "removeProvider::Invoking " +
"ActivityManagerNative.removeContentProvider(" + name);
ActivityManagerNative.getDefault().removeContentProvider(
getApplicationThread(), name);
@@ -4179,10 +4418,10 @@ public final class ActivityThread {
if (myBinder == providerBinder) {
//find if its published by this process itself
if(pr.mLocalProvider != null) {
- if(localLOGV) Log.i(TAG, "removeProvider::found local provider returning");
+ if(localLOGV) Slog.i(TAG, "removeProvider::found local provider returning");
return name;
}
- if(localLOGV) Log.v(TAG, "removeProvider::Not local provider Unlinking " +
+ if(localLOGV) Slog.v(TAG, "removeProvider::Not local provider Unlinking " +
"death recipient");
//content provider is in another process
myBinder.unlinkToDeath(pr, 0);
@@ -4201,7 +4440,7 @@ public final class ActivityThread {
synchronized(mProviderMap) {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
- Log.i(TAG, "Removing dead content provider: " + name);
+ Slog.i(TAG, "Removing dead content provider: " + name);
ProviderRecord removed = mProviderMap.remove(name);
if (removed != null) {
removed.mProvider.asBinder().unlinkToDeath(removed, 0);
@@ -4213,7 +4452,7 @@ public final class ActivityThread {
final void removeDeadProviderLocked(String name, IContentProvider provider) {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
- Log.i(TAG, "Removing dead content provider: " + name);
+ Slog.i(TAG, "Removing dead content provider: " + name);
ProviderRecord removed = mProviderMap.remove(name);
if (removed != null) {
removed.mProvider.asBinder().unlinkToDeath(removed, 0);
@@ -4226,7 +4465,7 @@ public final class ActivityThread {
ContentProvider localProvider = null;
if (provider == null) {
if (noisy) {
- Log.d(TAG, "Loading provider " + info.authority + ": "
+ Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
@@ -4244,7 +4483,7 @@ public final class ActivityThread {
}
}
if (c == null) {
- Log.w(TAG, "Unable to get context for package " +
+ Slog.w(TAG, "Unable to get context for package " +
ai.packageName +
" while loading content provider " +
info.name);
@@ -4256,12 +4495,12 @@ public final class ActivityThread {
loadClass(info.name).newInstance();
provider = localProvider.getIContentProvider();
if (provider == null) {
- Log.e(TAG, "Failed to instantiate class " +
+ Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
- if (Config.LOGV) Log.v(
+ if (Config.LOGV) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
@@ -4274,7 +4513,7 @@ public final class ActivityThread {
return null;
}
} else if (localLOGV) {
- Log.v(TAG, "Installing external provider " + info.authority + ": "
+ Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ info.name);
}
@@ -4303,8 +4542,12 @@ public final class ActivityThread {
private final void attach(boolean system) {
sThreadLocal.set(this);
mSystemThread = system;
- AndroidHttpClient.setThreadBlocked(true);
if (!system) {
+ ViewRoot.addFirstDrawHandler(new Runnable() {
+ public void run() {
+ ensureJitEnabled();
+ }
+ });
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
RuntimeInit.setApplicationObject(mAppThread.asBinder());
IActivityManager mgr = ActivityManagerNative.getDefault();
@@ -4318,7 +4561,7 @@ public final class ActivityThread {
android.ddm.DdmHandleAppName.setAppName("system_process");
try {
mInstrumentation = new Instrumentation();
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(getSystemContext().mPackageInfo, null, this);
Application app = Instrumentation.newApplication(Application.class, context);
mAllApplications.add(app);
@@ -4329,11 +4572,32 @@ public final class ActivityThread {
"Unable to instantiate Application():" + e.toString(), e);
}
}
+
+ ViewRoot.addConfigCallback(new ComponentCallbacks() {
+ public void onConfigurationChanged(Configuration newConfig) {
+ synchronized (mPackages) {
+ // We need to apply this change to the resources
+ // immediately, because upon returning the view
+ // hierarchy will be informed about it.
+ if (applyConfigurationToResourcesLocked(newConfig)) {
+ // This actually changed the resources! Tell
+ // everyone about it.
+ if (mPendingConfiguration == null ||
+ mPendingConfiguration.isOtherSeqNewer(newConfig)) {
+ mPendingConfiguration = newConfig;
+
+ queueOrSendMessage(H.CONFIGURATION_CHANGED, newConfig);
+ }
+ }
+ }
+ }
+ public void onLowMemory() {
+ }
+ });
}
private final void detach()
{
- AndroidHttpClient.setThreadBlocked(false);
sThreadLocal.set(null);
}
@@ -4370,6 +4634,6 @@ public final class ActivityThread {
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
- Log.i(TAG, "Main thread of " + name + " is now exiting");
+ Slog.i(TAG, "Main thread of " + name + " is now exiting");
}
}
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 53c7935..9082003 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -277,7 +277,26 @@ public class AlarmManager
} catch (RemoteException ex) {
}
}
-
+
+ /**
+ * Set the system wall clock time.
+ * Requires the permission android.permission.SET_TIME.
+ *
+ * @param millis time in milliseconds since the Epoch
+ */
+ public void setTime(long millis) {
+ try {
+ mService.setTime(millis);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Set the system default time zone.
+ * Requires the permission android.permission.SET_TIME_ZONE.
+ *
+ * @param timeZone in the format understood by {@link java.util.TimeZone}
+ */
public void setTimeZone(String timeZone) {
try {
mService.setTimeZone(timeZone);
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 20a579a..2714de5 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -35,12 +35,12 @@ import com.android.internal.app.AlertController;
/**
* A subclass of Dialog that can display one, two or three buttons. If you only want to
* display a String in this dialog box, use the setMessage() method. If you
- * want to display a more complex view, look up the FrameLayout called "body"
+ * want to display a more complex view, look up the FrameLayout called "custom"
* and add your view to it:
*
* <pre>
- * FrameLayout fl = (FrameLayout) findViewById(R.id.body);
- * fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
+ * FrameLayout fl = (FrameLayout) findViewById(android.R.id.custom);
+ * fl.addView(myView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
* </pre>
*
* <p>The AlertDialog class takes care of automatically setting
diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java
index 4f91e02..3756529 100644
--- a/core/java/android/app/AliasActivity.java
+++ b/core/java/android/app/AliasActivity.java
@@ -26,6 +26,7 @@ import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Xml;
+
import com.android.internal.util.XmlUtils;
import java.io.IOException;
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index aeae5f9..f0cef98 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -16,9 +16,19 @@
package android.app;
+import android.content.ComponentName;
+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.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.Printer;
+import java.io.PrintWriter;
+import java.io.StringWriter;
/**
* Describes an application error.
@@ -36,6 +46,13 @@ import android.util.Printer;
*/
public class ApplicationErrorReport implements Parcelable {
+ // System property defining error report receiver for system apps
+ static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps";
+
+ // System property defining default error report receiver
+ static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default";
+
+
/**
* Uninitialized error report.
*/
@@ -52,8 +69,13 @@ public class ApplicationErrorReport implements Parcelable {
public static final int TYPE_ANR = 2;
/**
+ * An error report about an application that's consuming too much battery.
+ */
+ public static final int TYPE_BATTERY = 3;
+
+ /**
* Type of this report. Can be one of {@link #TYPE_NONE},
- * {@link #TYPE_CRASH} or {@link #TYPE_ANR}.
+ * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}.
*/
public int type;
@@ -80,6 +102,11 @@ public class ApplicationErrorReport implements Parcelable {
public long time;
/**
+ * Set if the app is on the system image.
+ */
+ public boolean systemApp;
+
+ /**
* If this report is of type {@link #TYPE_CRASH}, contains an instance
* of CrashInfo describing the crash; otherwise null.
*/
@@ -92,6 +119,12 @@ public class ApplicationErrorReport implements Parcelable {
public AnrInfo anrInfo;
/**
+ * If this report is of type {@link #TYPE_BATTERY}, contains an instance
+ * of BatteryInfo; otherwise null.
+ */
+ public BatteryInfo batteryInfo;
+
+ /**
* Create an uninitialized instance of {@link ApplicationErrorReport}.
*/
public ApplicationErrorReport() {
@@ -105,12 +138,75 @@ public class ApplicationErrorReport implements Parcelable {
readFromParcel(in);
}
+ public static ComponentName getErrorReportReceiver(Context context,
+ String packageName, int appFlags) {
+ // check if error reporting is enabled in secure settings
+ int enabled = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.SEND_ACTION_APP_ERROR, 0);
+ if (enabled == 0) {
+ return null;
+ }
+
+ PackageManager pm = context.getPackageManager();
+
+ // look for receiver in the installer package
+ String candidate = pm.getInstallerPackageName(packageName);
+ ComponentName result = getErrorReportReceiver(pm, packageName, candidate);
+ if (result != null) {
+ return result;
+ }
+
+ // if the error app is on the system image, look for system apps
+ // error receiver
+ if ((appFlags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+ candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY);
+ result = getErrorReportReceiver(pm, packageName, candidate);
+ if (result != null) {
+ return result;
+ }
+ }
+
+ // if there is a default receiver, try that
+ candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
+ return getErrorReportReceiver(pm, packageName, candidate);
+ }
+
+ /**
+ * Return activity in receiverPackage that handles ACTION_APP_ERROR.
+ *
+ * @param pm PackageManager isntance
+ * @param errorPackage package which caused the error
+ * @param receiverPackage candidate package to receive the error
+ * @return activity component within receiverPackage which handles
+ * ACTION_APP_ERROR, or null if not found
+ */
+ static ComponentName getErrorReportReceiver(PackageManager pm, String errorPackage,
+ String receiverPackage) {
+ if (receiverPackage == null || receiverPackage.length() == 0) {
+ return null;
+ }
+
+ // break the loop if it's the error report receiver package that crashed
+ if (receiverPackage.equals(errorPackage)) {
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ intent.setPackage(receiverPackage);
+ ResolveInfo info = pm.resolveActivity(intent, 0);
+ if (info == null || info.activityInfo == null) {
+ return null;
+ }
+ return new ComponentName(receiverPackage, info.activityInfo.name);
+ }
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(type);
dest.writeString(packageName);
dest.writeString(installerPackageName);
dest.writeString(processName);
dest.writeLong(time);
+ dest.writeInt(systemApp ? 1 : 0);
switch (type) {
case TYPE_CRASH:
@@ -119,6 +215,9 @@ public class ApplicationErrorReport implements Parcelable {
case TYPE_ANR:
anrInfo.writeToParcel(dest, flags);
break;
+ case TYPE_BATTERY:
+ batteryInfo.writeToParcel(dest, flags);
+ break;
}
}
@@ -128,15 +227,23 @@ public class ApplicationErrorReport implements Parcelable {
installerPackageName = in.readString();
processName = in.readString();
time = in.readLong();
+ systemApp = in.readInt() == 1;
switch (type) {
case TYPE_CRASH:
crashInfo = new CrashInfo(in);
anrInfo = null;
+ batteryInfo = null;
break;
case TYPE_ANR:
anrInfo = new AnrInfo(in);
crashInfo = null;
+ batteryInfo = null;
+ break;
+ case TYPE_BATTERY:
+ batteryInfo = new BatteryInfo(in);
+ anrInfo = null;
+ crashInfo = null;
break;
}
}
@@ -187,6 +294,32 @@ public class ApplicationErrorReport implements Parcelable {
}
/**
+ * Create an instance of CrashInfo initialized from an exception.
+ */
+ public CrashInfo(Throwable tr) {
+ StringWriter sw = new StringWriter();
+ tr.printStackTrace(new PrintWriter(sw));
+ stackTrace = sw.toString();
+ exceptionMessage = tr.getMessage();
+
+ // Populate fields with the "root cause" exception
+ while (tr.getCause() != null) {
+ tr = tr.getCause();
+ String msg = tr.getMessage();
+ if (msg != null && msg.length() > 0) {
+ exceptionMessage = msg;
+ }
+ }
+
+ exceptionClassName = tr.getClass().getName();
+ StackTraceElement trace = tr.getStackTrace()[0];
+ throwFileName = trace.getFileName();
+ throwClassName = trace.getClassName();
+ throwMethodName = trace.getMethodName();
+ throwLineNumber = trace.getLineNumber();
+ }
+
+ /**
* Create an instance of CrashInfo initialized from a Parcel.
*/
public CrashInfo(Parcel in) {
@@ -279,6 +412,68 @@ public class ApplicationErrorReport implements Parcelable {
}
}
+ /**
+ * Describes a battery usage report.
+ */
+ public static class BatteryInfo {
+ /**
+ * Percentage of the battery that was used up by the process.
+ */
+ public int usagePercent;
+
+ /**
+ * Duration in microseconds over which the process used the above
+ * percentage of battery.
+ */
+ public long durationMicros;
+
+ /**
+ * Dump of various info impacting battery use.
+ */
+ public String usageDetails;
+
+ /**
+ * Checkin details.
+ */
+ public String checkinDetails;
+
+ /**
+ * Create an uninitialized instance of BatteryInfo.
+ */
+ public BatteryInfo() {
+ }
+
+ /**
+ * Create an instance of BatteryInfo initialized from a Parcel.
+ */
+ public BatteryInfo(Parcel in) {
+ usagePercent = in.readInt();
+ durationMicros = in.readLong();
+ usageDetails = in.readString();
+ checkinDetails = in.readString();
+ }
+
+ /**
+ * Save a BatteryInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(usagePercent);
+ dest.writeLong(durationMicros);
+ dest.writeString(usageDetails);
+ dest.writeString(checkinDetails);
+ }
+
+ /**
+ * Dump a BatteryInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "usagePercent: " + usagePercent);
+ pw.println(prefix + "durationMicros: " + durationMicros);
+ pw.println(prefix + "usageDetails: " + usageDetails);
+ pw.println(prefix + "checkinDetails: " + checkinDetails);
+ }
+ }
+
public static final Parcelable.Creator<ApplicationErrorReport> CREATOR
= new Parcelable.Creator<ApplicationErrorReport>() {
public ApplicationErrorReport createFromParcel(Parcel source) {
@@ -303,6 +498,7 @@ public class ApplicationErrorReport implements Parcelable {
pw.println(prefix + "installerPackageName: " + installerPackageName);
pw.println(prefix + "processName: " + processName);
pw.println(prefix + "time: " + time);
+ pw.println(prefix + "systemApp: " + systemApp);
switch (type) {
case TYPE_CRASH:
@@ -311,6 +507,9 @@ public class ApplicationErrorReport implements Parcelable {
case TYPE_ANR:
anrInfo.dump(pw, prefix);
break;
+ case TYPE_BATTERY:
+ batteryInfo.dump(pw, prefix);
+ break;
}
}
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 7cba13f..da26a78 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -393,6 +393,15 @@ public abstract class ApplicationThreadNative extends Binder
mi.writeToParcel(reply, 0);
return true;
}
+
+ case DISPATCH_PACKAGE_BROADCAST_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ int cmd = data.readInt();
+ String[] packages = data.readStringArray();
+ dispatchPackageBroadcast(cmd, packages);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -806,5 +815,16 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
reply.recycle();
}
+
+ public void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeInt(cmd);
+ data.writeStringArray(packages);
+ mRemote.transact(DISPATCH_PACKAGE_BROADCAST_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+
+ }
}
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
deleted file mode 100644
index b207998..0000000
--- a/core/java/android/app/BackupAgent.java
+++ /dev/null
@@ -1,149 +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.IBackupAgent;
-import android.backup.BackupDataInput;
-import android.backup.BackupDataOutput;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * This is the central interface between an application and Android's
- * settings backup mechanism.
- *
- * @hide pending API solidification
- */
-public abstract class BackupAgent extends ContextWrapper {
- private static final String TAG = "BackupAgent";
- private static final boolean DEBUG = false;
-
- public BackupAgent() {
- super(null);
- }
-
- public void onCreate() {
- }
-
- public void onDestroy() {
- }
-
- /**
- * The application is being asked to write any data changed since the
- * last time it performed a backup operation. The state data recorded
- * during the last backup pass is provided in the oldState file descriptor.
- * If oldState is null, no old state is available and the application should perform
- * a full backup. In both cases, a representation of the final backup state after
- * this pass should be written to the file pointed to by the newStateFd file descriptor.
- *
- * @param oldState An open, read-only ParcelFileDescriptor pointing to the last backup
- * state provided by the application. May be null, in which
- * case no prior state is being provided and the application should
- * perform a full backup.
- * @param data An open, read/write ParcelFileDescriptor pointing to the backup data
- * destination. Typically the application will use backup helper
- * classes to write to this file.
- * @param newState An open, read/write ParcelFileDescriptor pointing to an empty
- * file. The application should record the final backup state
- * here after writing the requested data to dataFd.
- */
- public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) throws IOException;
-
- /**
- * The application is being restored from backup, and should replace any
- * existing data with the contents of the backup. The backup data is
- * provided in the file pointed to by the dataFd file descriptor. Once
- * the restore is finished, the application should write a representation
- * of the final state to the newStateFd file descriptor,
- *
- * @param data An open, read-only ParcelFileDescriptor pointing to a full snapshot
- * of the application's data.
- * @param appVersionCode The android:versionCode value of the application that backed
- * up this particular data set. This makes it easier for an application's
- * agent to distinguish among several possible older data versions when
- * asked to perform the restore operation.
- * @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 dataFd.
- */
- public abstract void onRestore(BackupDataInput data, int appVersionCode,
- ParcelFileDescriptor newState)
- throws IOException;
-
-
- // ----- Core implementation -----
-
- /**
- * Returns the private interface called by the backup system. Applications will
- * not typically override this.
- */
- public IBinder onBind() {
- return mBinder;
- }
-
- private final IBinder mBinder = new BackupServiceBinder().asBinder();
-
- /** @hide */
- public void attach(Context context) {
- attachBaseContext(context);
- }
-
- // ----- IBackupService binder interface -----
- private class BackupServiceBinder extends IBackupAgent.Stub {
- private static final String TAG = "BackupServiceBinder";
-
- public void doBackup(ParcelFileDescriptor oldState,
- ParcelFileDescriptor data,
- ParcelFileDescriptor newState) throws RemoteException {
- // !!! TODO - real implementation; for now just invoke the callbacks directly
- if (DEBUG) Log.v(TAG, "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
- try {
- BackupAgent.this.onBackup(oldState, output, newState);
- } catch (IOException ex) {
- Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
- throw new RuntimeException(ex);
- } catch (RuntimeException ex) {
- Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
- throw ex;
- }
- }
-
- public void doRestore(ParcelFileDescriptor data, int appVersionCode,
- ParcelFileDescriptor newState) throws RemoteException {
- // !!! TODO - real implementation; for now just invoke the callbacks directly
- if (DEBUG) Log.v(TAG, "doRestore() invoked");
- BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
- try {
- BackupAgent.this.onRestore(input, appVersionCode, newState);
- } catch (IOException ex) {
- Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
- throw new RuntimeException(ex);
- } catch (RuntimeException ex) {
- Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
- throw ex;
- }
- }
- }
-}
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ContextImpl.java
index f48f150..f471f57 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ContextImpl.java
@@ -42,6 +42,7 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
@@ -52,6 +53,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.PackageParser.Package;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -65,11 +67,15 @@ import android.location.LocationManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.ThrottleManager;
+import android.net.IThrottleManager;
import android.net.Uri;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -79,8 +85,11 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StatFs;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.AndroidRuntimeException;
@@ -92,6 +101,9 @@ import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
+import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.os.IDropBoxManagerService;
import java.io.File;
import java.io.FileInputStream;
@@ -142,10 +154,10 @@ class ReceiverRestrictedContext extends ContextWrapper {
}
/**
- * Common implementation of Context API, which Activity and other application
- * classes inherit.
+ * Common implementation of Context API, which provides the base
+ * context object for Activity and other application components.
*/
-class ApplicationContext extends Context {
+class ContextImpl extends Context {
private final static String TAG = "ApplicationContext";
private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
@@ -154,6 +166,7 @@ class ApplicationContext extends Context {
private static AlarmManager sAlarmManager;
private static PowerManager sPowerManager;
private static ConnectivityManager sConnectivityManager;
+ private static ThrottleManager sThrottleManager;
private static WifiManager sWifiManager;
private static LocationManager sLocationManager;
private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
@@ -175,6 +188,7 @@ class ApplicationContext extends Context {
private Context mReceiverRestrictedContext = null;
private SearchManager mSearchManager = null;
private SensorManager mSensorManager = null;
+ private StorageManager mStorageManager = null;
private Vibrator mVibrator = null;
private LayoutInflater mLayoutInflater = null;
private StatusBarManager mStatusBarManager = null;
@@ -182,25 +196,31 @@ class ApplicationContext extends Context {
private ClipboardManager mClipboardManager = null;
private boolean mRestricted;
private AccountManager mAccountManager; // protected by mSync
+ private DropBoxManager mDropBoxManager = null;
+ private DevicePolicyManager mDevicePolicyManager = null;
+ private UiModeManager mUiModeManager = null;
private final Object mSync = new Object();
private File mDatabasesDir;
private File mPreferencesDir;
private File mFilesDir;
-
-
private File mCacheDir;
+ private File mExternalFilesDir;
+ private File mExternalCacheDir;
private static long sInstanceCount = 0;
private static final String[] EMPTY_FILE_LIST = {};
+ // For debug only
+ /*
@Override
protected void finalize() throws Throwable {
super.finalize();
--sInstanceCount;
}
+ */
public static long getInstanceCount() {
return sInstanceCount;
@@ -243,7 +263,8 @@ class ApplicationContext extends Context {
@Override
public Context getApplicationContext() {
- return mMainThread.getApplication();
+ return (mPackageInfo != null) ?
+ mPackageInfo.getApplication() : mMainThread.getApplication();
}
@Override
@@ -429,6 +450,38 @@ class ApplicationContext extends Context {
}
@Override
+ public File getExternalFilesDir(String type) {
+ synchronized (mSync) {
+ if (mExternalFilesDir == null) {
+ mExternalFilesDir = Environment.getExternalStorageAppFilesDirectory(
+ getPackageName());
+ }
+ if (!mExternalFilesDir.exists()) {
+ try {
+ (new File(Environment.getExternalStorageAndroidDataDir(),
+ ".nomedia")).createNewFile();
+ } catch (IOException e) {
+ }
+ if (!mExternalFilesDir.mkdirs()) {
+ Log.w(TAG, "Unable to create external files directory");
+ return null;
+ }
+ }
+ if (type == null) {
+ return mExternalFilesDir;
+ }
+ File dir = new File(mExternalFilesDir, type);
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ Log.w(TAG, "Unable to create external media directory " + dir);
+ return null;
+ }
+ }
+ return dir;
+ }
+ }
+
+ @Override
public File getCacheDir() {
synchronized (mSync) {
if (mCacheDir == null) {
@@ -448,7 +501,28 @@ class ApplicationContext extends Context {
return mCacheDir;
}
-
+ @Override
+ public File getExternalCacheDir() {
+ synchronized (mSync) {
+ if (mExternalCacheDir == null) {
+ mExternalCacheDir = Environment.getExternalStorageAppCacheDirectory(
+ getPackageName());
+ }
+ if (!mExternalCacheDir.exists()) {
+ try {
+ (new File(Environment.getExternalStorageAndroidDataDir(),
+ ".nomedia")).createNewFile();
+ } catch (IOException e) {
+ }
+ if (!mExternalCacheDir.mkdirs()) {
+ Log.w(TAG, "Unable to create external cache directory");
+ return null;
+ }
+ }
+ return mExternalCacheDir;
+ }
+ }
+
@Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
@@ -462,14 +536,7 @@ class ApplicationContext extends Context {
@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
- File dir = getDatabasesDir();
- if (!dir.isDirectory() && dir.mkdir()) {
- FileUtils.setPermissions(dir.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
- }
-
- File f = makeFilename(dir, name);
+ File f = validateFilePath(name, true);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
setFilePermissionsFromMode(f.getPath(), mode, 0);
return db;
@@ -478,7 +545,7 @@ class ApplicationContext extends Context {
@Override
public boolean deleteDatabase(String name) {
try {
- File f = makeFilename(getDatabasesDir(), name);
+ File f = validateFilePath(name, false);
return f.delete();
} catch (Exception e) {
}
@@ -487,7 +554,7 @@ class ApplicationContext extends Context {
@Override
public File getDatabasePath(String name) {
- return makeFilename(getDatabasesDir(), name);
+ return validateFilePath(name, false);
}
@Override
@@ -729,7 +796,7 @@ class ApplicationContext extends Context {
scheduler = mMainThread.getHandler();
}
rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
- receiver, context, scheduler, null, false).getIIntentReceiver();
+ receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
try {
@@ -865,6 +932,8 @@ class ApplicationContext extends Context {
return getPowerManager();
} else if (CONNECTIVITY_SERVICE.equals(name)) {
return getConnectivityManager();
+ } else if (THROTTLE_SERVICE.equals(name)) {
+ return getThrottleManager();
} else if (WIFI_SERVICE.equals(name)) {
return getWifiManager();
} else if (NOTIFICATION_SERVICE.equals(name)) {
@@ -879,6 +948,8 @@ class ApplicationContext extends Context {
return getSearchManager();
} else if (SENSOR_SERVICE.equals(name)) {
return getSensorManager();
+ } else if (STORAGE_SERVICE.equals(name)) {
+ return getStorageManager();
} else if (VIBRATOR_SERVICE.equals(name)) {
return getVibrator();
} else if (STATUS_BAR_SERVICE.equals(name)) {
@@ -896,6 +967,12 @@ class ApplicationContext extends Context {
return getClipboardManager();
} else if (WALLPAPER_SERVICE.equals(name)) {
return getWallpaperManager();
+ } else if (DROPBOX_SERVICE.equals(name)) {
+ return getDropBoxManager();
+ } else if (DEVICE_POLICY_SERVICE.equals(name)) {
+ return getDevicePolicyManager();
+ } else if (UI_MODE_SERVICE.equals(name)) {
+ return getUiModeManager();
}
return null;
@@ -956,6 +1033,18 @@ class ApplicationContext extends Context {
return sConnectivityManager;
}
+ private ThrottleManager getThrottleManager()
+ {
+ synchronized (sSync) {
+ if (sThrottleManager == null) {
+ IBinder b = ServiceManager.getService(THROTTLE_SERVICE);
+ IThrottleManager service = IThrottleManager.Stub.asInterface(b);
+ sThrottleManager = new ThrottleManager(service);
+ }
+ }
+ return sThrottleManager;
+ }
+
private WifiManager getWifiManager()
{
synchronized (sSync) {
@@ -1037,6 +1126,20 @@ class ApplicationContext extends Context {
return mSensorManager;
}
+ private StorageManager getStorageManager() {
+ synchronized (mSync) {
+ if (mStorageManager == null) {
+ try {
+ mStorageManager = new StorageManager(mMainThread.getHandler().getLooper());
+ } catch (RemoteException rex) {
+ Log.e(TAG, "Failed to create StorageManager", rex);
+ mStorageManager = null;
+ }
+ }
+ }
+ return mStorageManager;
+ }
+
private Vibrator getVibrator() {
synchronized (mSync) {
if (mVibrator == null) {
@@ -1045,7 +1148,7 @@ class ApplicationContext extends Context {
}
return mVibrator;
}
-
+
private AudioManager getAudioManager()
{
if (mAudioManager == null) {
@@ -1054,6 +1157,36 @@ class ApplicationContext extends Context {
return mAudioManager;
}
+ private DropBoxManager getDropBoxManager() {
+ synchronized (mSync) {
+ if (mDropBoxManager == null) {
+ IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+ IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+ mDropBoxManager = new DropBoxManager(service);
+ }
+ }
+ return mDropBoxManager;
+ }
+
+ private DevicePolicyManager getDevicePolicyManager() {
+ synchronized (mSync) {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager = DevicePolicyManager.create(this,
+ mMainThread.getHandler());
+ }
+ }
+ return mDevicePolicyManager;
+ }
+
+ private UiModeManager getUiModeManager() {
+ synchronized (mSync) {
+ if (mUiModeManager == null) {
+ mUiModeManager = new UiModeManager();
+ }
+ }
+ return mUiModeManager;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
@@ -1285,13 +1418,13 @@ class ApplicationContext extends Context {
public Context createPackageContext(String packageName, int flags)
throws PackageManager.NameNotFoundException {
if (packageName.equals("system") || packageName.equals("android")) {
- return new ApplicationContext(mMainThread.getSystemContext());
+ return new ContextImpl(mMainThread.getSystemContext());
}
ActivityThread.PackageInfo pi =
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
- ApplicationContext c = new ApplicationContext();
+ ContextImpl c = new ContextImpl();
c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
c.init(pi, null, mMainThread, mResources);
if (c.mResources != null) {
@@ -1328,14 +1461,15 @@ class ApplicationContext extends Context {
return file;
}
- static ApplicationContext createSystemContext(ActivityThread mainThread) {
- ApplicationContext context = new ApplicationContext();
+ static ContextImpl createSystemContext(ActivityThread mainThread) {
+ ContextImpl context = new ContextImpl();
context.init(Resources.getSystem(), mainThread);
return context;
}
- ApplicationContext() {
- ++sInstanceCount;
+ ContextImpl() {
+ // For debug only
+ //++sInstanceCount;
mOuterContext = this;
}
@@ -1345,7 +1479,7 @@ class ApplicationContext extends Context {
*
* @param context Existing application context.
*/
- public ApplicationContext(ApplicationContext context) {
+ public ContextImpl(ContextImpl context) {
++sInstanceCount;
mPackageInfo = context.mPackageInfo;
mResources = context.mResources;
@@ -1365,8 +1499,9 @@ class ApplicationContext extends Context {
mPackageInfo = packageInfo;
mResources = mPackageInfo.getResources(mainThread);
- if (container != null && container.getCompatibilityInfo().applicationScale !=
- mResources.getCompatibilityInfo().applicationScale) {
+ if (mResources != null && container != null
+ && container.getCompatibilityInfo().applicationScale !=
+ mResources.getCompatibilityInfo().applicationScale) {
if (DEBUG) {
Log.d(TAG, "loaded context has different scaling. Using container's" +
" compatiblity info:" + container.getDisplayMetrics());
@@ -1437,12 +1572,35 @@ class ApplicationContext extends Context {
FileUtils.setPermissions(name, perms, -1, -1);
}
+ private File validateFilePath(String name, boolean createDirectory) {
+ File dir;
+ File f;
+
+ if (name.charAt(0) == File.separatorChar) {
+ String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
+ dir = new File(dirPath);
+ name = name.substring(name.lastIndexOf(File.separatorChar));
+ f = new File(dir, name);
+ } else {
+ dir = getDatabasesDir();
+ f = makeFilename(dir, name);
+ }
+
+ if (createDirectory && !dir.isDirectory() && dir.mkdir()) {
+ FileUtils.setPermissions(dir.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ }
+
+ return f;
+ }
+
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
- "File " + name + " contains a path separator");
+ "File " + name + " contains a path separator");
}
// ----------------------------------------------------------------------
@@ -1494,6 +1652,24 @@ class ApplicationContext extends Context {
}
@Override
+ public String[] currentToCanonicalPackageNames(String[] names) {
+ try {
+ return mPM.currentToCanonicalPackageNames(names);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
+ @Override
+ public String[] canonicalToCurrentPackageNames(String[] names) {
+ try {
+ return mPM.canonicalToCurrentPackageNames(names);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
+ @Override
public Intent getLaunchIntentForPackage(String packageName) {
// First see if the package has an INFO activity; the existence of
// such an activity is implied to be the desired front-door for the
@@ -1695,6 +1871,15 @@ class ApplicationContext extends Context {
}
@Override
+ public boolean addPermissionAsync(PermissionInfo info) {
+ try {
+ return mPM.addPermissionAsync(info);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
+ @Override
public void removePermission(String name) {
try {
mPM.removePermission(name);
@@ -1981,31 +2166,7 @@ class ApplicationContext extends Context {
}
@Override public Drawable getApplicationIcon(ApplicationInfo info) {
- final int icon = info.icon;
- if (icon != 0) {
- ResourceName name = new ResourceName(info, icon);
- Drawable dr = getCachedIcon(name);
- if (dr != null) {
- return dr;
- }
- try {
- Resources r = getResourcesForApplication(info);
- dr = r.getDrawable(icon);
- if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x"
- + Integer.toHexString(icon) + " from " + r
- + ": " + dr);
- putCachedIcon(name, dr);
- return dr;
- } catch (NameNotFoundException e) {
- Log.w("PackageManager", "Failure retrieving resources for"
- + info.packageName);
- } catch (RuntimeException e) {
- // If an exception was thrown, fall through to return
- // default icon.
- Log.w("PackageManager", "Failure retrieving app icon", e);
- }
- }
- return getDefaultActivityIcon();
+ return info.loadIcon(this);
}
@Override public Drawable getApplicationIcon(String packageName)
@@ -2058,7 +2219,7 @@ class ApplicationContext extends Context {
}
}
- ApplicationPackageManager(ApplicationContext context,
+ ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
@@ -2083,28 +2244,7 @@ class ApplicationContext extends Context {
return null;
}
- private void establishPackageRemovedReceiver() {
- // mContext.registerReceiverInternal() winds up acquiring the
- // main ActivityManagerService.this lock. If we hold our usual
- // sSync global lock at the same time, we impose a required ordering
- // on those two locks, which is not good for deadlock prevention.
- // Use a dedicated lock around initialization of
- // sPackageRemovedReceiver to avoid this.
- synchronized (sPackageRemovedSync) {
- if (sPackageRemovedReceiver == null) {
- sPackageRemovedReceiver = new PackageRemovedReceiver();
- IntentFilter filter = new IntentFilter(
- Intent.ACTION_PACKAGE_REMOVED);
- filter.addDataScheme("package");
- mContext.registerReceiverInternal(sPackageRemovedReceiver,
- filter, null, null, null);
- }
- }
- }
-
private void putCachedIcon(ResourceName name, Drawable dr) {
- establishPackageRemovedReceiver();
-
synchronized (sSync) {
sIconCache.put(name, new WeakReference<Drawable>(dr));
if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable for "
@@ -2112,40 +2252,51 @@ class ApplicationContext extends Context {
}
}
- private static final class PackageRemovedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- Uri data = intent.getData();
- String ssp;
- if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
- boolean needCleanup = false;
+ static final void handlePackageBroadcast(int cmd, String[] pkgList,
+ boolean hasPkgInfo) {
+ boolean immediateGc = false;
+ if (cmd == IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE) {
+ immediateGc = true;
+ }
+ if (pkgList != null && (pkgList.length > 0)) {
+ boolean needCleanup = false;
+ for (String ssp : pkgList) {
synchronized (sSync) {
- Iterator<ResourceName> it = sIconCache.keySet().iterator();
- while (it.hasNext()) {
- ResourceName nm = it.next();
- if (nm.packageName.equals(ssp)) {
- //Log.i(TAG, "Removing cached drawable for " + nm);
- it.remove();
- needCleanup = true;
+ if (sIconCache.size() > 0) {
+ Iterator<ResourceName> it = sIconCache.keySet().iterator();
+ while (it.hasNext()) {
+ ResourceName nm = it.next();
+ if (nm.packageName.equals(ssp)) {
+ //Log.i(TAG, "Removing cached drawable for " + nm);
+ it.remove();
+ needCleanup = true;
+ }
}
}
- it = sStringCache.keySet().iterator();
- while (it.hasNext()) {
- ResourceName nm = it.next();
- if (nm.packageName.equals(ssp)) {
- //Log.i(TAG, "Removing cached string for " + nm);
- it.remove();
- needCleanup = true;
+ if (sStringCache.size() > 0) {
+ Iterator<ResourceName> it = sStringCache.keySet().iterator();
+ while (it.hasNext()) {
+ ResourceName nm = it.next();
+ if (nm.packageName.equals(ssp)) {
+ //Log.i(TAG, "Removing cached string for " + nm);
+ it.remove();
+ needCleanup = true;
+ }
}
}
}
- if (needCleanup || ActivityThread.currentActivityThread().hasPackageInfo(ssp)) {
+ }
+ if (needCleanup || hasPkgInfo) {
+ if (immediateGc) {
+ // Schedule an immediate gc.
+ Runtime.getRuntime().gc();
+ } else {
ActivityThread.currentActivityThread().scheduleGcIdler();
}
}
}
}
-
+
private static final class ResourceName {
final String packageName;
final int iconId;
@@ -2210,32 +2361,11 @@ class ApplicationContext extends Context {
}
private void putCachedString(ResourceName name, CharSequence cs) {
- establishPackageRemovedReceiver();
-
synchronized (sSync) {
sStringCache.put(name, new WeakReference<CharSequence>(cs));
}
}
- private CharSequence getLabel(ResourceName name, ApplicationInfo app, int id) {
- CharSequence cs = getCachedString(name);
- if (cs != null) {
- return cs;
- }
- try {
- Resources r = getResourcesForApplication(app);
- cs = r.getText(id);
- putCachedString(name, cs);
- } catch (NameNotFoundException e) {
- Log.w("PackageManager", "Failure retrieving resources for"
- + app.packageName);
- } catch (RuntimeException e) {
- // If an exception was thrown, fall through to return null
- Log.w("ApplicationInfo", "Failure retrieving activity name", e);
- }
- return cs;
- }
-
@Override
public CharSequence getText(String packageName, int resid,
ApplicationInfo appInfo) {
@@ -2297,17 +2427,7 @@ class ApplicationContext extends Context {
@Override
public CharSequence getApplicationLabel(ApplicationInfo info) {
- if (info.nonLocalizedLabel != null) {
- return info.nonLocalizedLabel;
- }
- final int id = info.labelRes;
- if (id != 0) {
- CharSequence cs = getLabel(new ResourceName(info, id), info, id);
- if (cs != null) {
- return cs;
- }
- }
- return info.packageName;
+ return info.loadLabel(this);
}
@Override
@@ -2321,6 +2441,15 @@ class ApplicationContext extends Context {
}
@Override
+ public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+ try {
+ mPM.movePackage(packageName, observer, flags);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
public String getInstallerPackageName(String packageName) {
try {
return mPM.getInstallerPackageName(packageName);
@@ -2491,12 +2620,10 @@ class ApplicationContext extends Context {
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
- private final ApplicationContext mContext;
+ private final ContextImpl mContext;
private final IPackageManager mPM;
private static final Object sSync = new Object();
- private static final Object sPackageRemovedSync = new Object();
- private static BroadcastReceiver sPackageRemovedReceiver;
private static HashMap<ResourceName, WeakReference<Drawable> > sIconCache
= new HashMap<ResourceName, WeakReference<Drawable> >();
private static HashMap<ResourceName, WeakReference<CharSequence> > sStringCache
@@ -2740,9 +2867,14 @@ class ApplicationContext extends Context {
private boolean writeFileLocked() {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
- if (!mFile.renameTo(mBackupFile)) {
- Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile);
- return false;
+ if (!mBackupFile.exists()) {
+ if (!mFile.renameTo(mBackupFile)) {
+ Log.e(TAG, "Couldn't rename file " + mFile
+ + " to backup file " + mBackupFile);
+ return false;
+ }
+ } else {
+ mFile.delete();
}
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 58e8b32..0235599 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -27,8 +27,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.util.Config;
-import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
@@ -72,8 +70,6 @@ import java.lang.ref.WeakReference;
*/
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener {
- private static final String LOG_TAG = "Dialog";
-
private Activity mOwnerActivity;
final Context mContext;
@@ -104,6 +100,12 @@ public class Dialog implements DialogInterface, Window.Callback,
private final Thread mUiThread;
private final Handler mHandler = new Handler();
+ private static final int DISMISS = 0x43;
+ private static final int CANCEL = 0x44;
+ private static final int SHOW = 0x45;
+
+ private Handler mListenersHandler;
+
private final Runnable mDismissAction = new Runnable() {
public void run() {
dismissDialog();
@@ -213,8 +215,6 @@ public class Dialog implements DialogInterface, Window.Callback,
*/
public void show() {
if (mShowing) {
- if (Config.LOGV) Log.v(LOG_TAG,
- "[Dialog] start: already showing, ignore");
if (mDecor != null) {
mDecor.setVisibility(View.VISIBLE);
}
@@ -236,10 +236,14 @@ public class Dialog implements DialogInterface, Window.Callback,
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
- mWindowManager.addView(mDecor, l);
- mShowing = true;
- sendShowMessage();
+ try {
+ mWindowManager.addView(mDecor, l);
+ mShowing = true;
+
+ sendShowMessage();
+ } finally {
+ }
}
/**
@@ -266,25 +270,20 @@ public class Dialog implements DialogInterface, Window.Callback,
}
private void dismissDialog() {
- if (mDecor == null) {
- if (Config.LOGV) Log.v(LOG_TAG,
- "[Dialog] dismiss: already dismissed, ignore");
- return;
- }
- if (!mShowing) {
- if (Config.LOGV) Log.v(LOG_TAG,
- "[Dialog] dismiss: not showing, ignore");
+ if (mDecor == null || !mShowing) {
return;
}
- mWindowManager.removeView(mDecor);
-
- mDecor = null;
- mWindow.closeAllPanels();
- onStop();
- mShowing = false;
-
- sendDismissMessage();
+ try {
+ mWindowManager.removeView(mDecor);
+ } finally {
+ mDecor = null;
+ mWindow.closeAllPanels();
+ onStop();
+ mShowing = false;
+
+ sendDismissMessage();
+ }
}
private void sendDismissMessage() {
@@ -668,8 +667,8 @@ public class Dialog implements DialogInterface, Window.Callback,
event.setPackageName(mContext.getPackageName());
LayoutParams params = getWindow().getAttributes();
- boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
- (params.height == LayoutParams.FILL_PARENT);
+ boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+ (params.height == LayoutParams.MATCH_PARENT);
event.setFullScreen(isFullScreen);
return false;
@@ -822,18 +821,15 @@ public class Dialog implements DialogInterface, Window.Callback,
final SearchManager searchManager = (SearchManager) mContext
.getSystemService(Context.SEARCH_SERVICE);
- // can't start search without an associated activity (e.g a system dialog)
- if (!searchManager.hasIdent()) {
+ // associate search with owner activity
+ final ComponentName appName = getAssociatedActivity();
+ if (appName != null) {
+ searchManager.startSearch(null, false, appName, null, false);
+ dismiss();
+ return true;
+ } else {
return false;
}
-
- // associate search with owner activity if possible (otherwise it will default to
- // global search).
- final ComponentName appName = getAssociatedActivity();
- final boolean globalSearch = (appName == null);
- searchManager.startSearch(null, false, appName, null, globalSearch);
- dismiss();
- return true;
}
/**
@@ -995,8 +991,7 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Sets a listener to be invoked when the dialog is shown.
- *
- * @hide Pending API council approval
+ * @param listener The {@link DialogInterface.OnShowListener} to use.
*/
public void setOnShowListener(OnShowListener listener) {
if (listener != null) {
@@ -1038,12 +1033,6 @@ public class Dialog implements DialogInterface, Window.Callback,
mOnKeyListener = onKeyListener;
}
- private static final int DISMISS = 0x43;
- private static final int CANCEL = 0x44;
- private static final int SHOW = 0x45;
-
- private Handler mListenersHandler;
-
private static final class ListenersHandler extends Handler {
private WeakReference<DialogInterface> mDialog;
diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java
index a2e048f..9651078 100644
--- a/core/java/android/app/ExpandableListActivity.java
+++ b/core/java/android/app/ExpandableListActivity.java
@@ -65,21 +65,21 @@ import java.util.Map;
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:paddingLeft=&quot;8dp&quot;
* android:paddingRight=&quot;8dp&quot;&gt;
*
* &lt;ExpandableListView android:id=&quot;@id/android:list&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
*
* &lt;TextView android:id=&quot;@id/android:empty&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#FF0000&quot;
* android:text=&quot;No data&quot;/&gt;
* &lt;/LinearLayout&gt;
@@ -114,19 +114,19 @@ import java.util.Map;
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
*
* &lt;TextView android:id=&quot;@+id/text1&quot;
* android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
*
* &lt;TextView android:id=&quot;@+id/text2&quot;
* android:textSize=&quot;16sp&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
* </pre>
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
index d89db96..acd20bd 100644
--- a/core/java/android/app/FullBackupAgent.java
+++ b/core/java/android/app/FullBackupAgent.java
@@ -1,8 +1,25 @@
+/*
+ * 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.backup.BackupDataInput;
-import android.backup.BackupDataOutput;
-import android.backup.FileBackupHelper;
+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;
@@ -48,7 +65,8 @@ public class FullBackupAgent extends BackupAgent {
}
// That's the file set; now back it all up
- FileBackupHelper helper = new FileBackupHelper(this, (String[])allFiles.toArray());
+ FileBackupHelper helper = new FileBackupHelper(this,
+ allFiles.toArray(new String[allFiles.size()]));
helper.performBackup(oldState, data, newState);
}
diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl
index 8f6b252..c76a517 100644
--- a/core/java/android/app/IActivityController.aidl
+++ b/core/java/android/app/IActivityController.aidl
@@ -43,8 +43,9 @@ interface IActivityController
* normal error recovery (app crash dialog) to occur, false to kill
* it immediately.
*/
- boolean appCrashed(String processName, int pid, String shortMsg,
- String longMsg, in byte[] crashData);
+ boolean appCrashed(String processName, int pid,
+ String shortMsg, String longMsg,
+ long timeMillis, String stackTrace);
/**
* An application process is not responding. Return 0 to show the "app
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9f505ac..31f0a63 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -84,6 +84,14 @@ public interface IActivityManager extends IInterface {
Intent intent, String resolvedType, Uri[] grantedUriPermissions,
int grantedMode, IBinder resultTo, String resultWho, int requestCode,
boolean onlyIfNeeded, boolean debug) throws RemoteException;
+ public WaitResult startActivityAndWait(IApplicationThread caller,
+ Intent intent, String resolvedType, Uri[] grantedUriPermissions,
+ int grantedMode, IBinder resultTo, String resultWho, int requestCode,
+ boolean onlyIfNeeded, boolean debug) throws RemoteException;
+ public int startActivityWithConfig(IApplicationThread caller,
+ Intent intent, String resolvedType, Uri[] grantedUriPermissions,
+ int grantedMode, IBinder resultTo, String resultWho, int requestCode,
+ boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException;
public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
@@ -93,6 +101,7 @@ public interface IActivityManager extends IInterface {
public boolean finishActivity(IBinder token, int code, Intent data)
throws RemoteException;
public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
+ public boolean willActivityBeVisible(IBinder token) throws RemoteException;
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver, IntentFilter filter,
String requiredPermission) throws RemoteException;
@@ -216,7 +225,8 @@ public interface IActivityManager extends IInterface {
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
- public void restartPackage(final String packageName) throws RemoteException;
+ public void killBackgroundProcesses(final String packageName) throws RemoteException;
+ public void forceStopPackage(final String packageName) throws RemoteException;
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
@@ -235,27 +245,30 @@ public interface IActivityManager extends IInterface {
public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
- public boolean killPidsForMemory(int[] pids) throws RemoteException;
+ public boolean killPids(int[] pids, String reason) throws RemoteException;
public void reportPss(IApplicationThread caller, int pss) throws RemoteException;
// Special low-level communication with activity manager.
public void startRunning(String pkg, String cls, String action,
String data) throws RemoteException;
- // Returns 1 if the user wants to debug.
- public int handleApplicationError(IBinder app,
- int flags, /* 1 == can debug */
- String tag, String shortMsg, String longMsg,
- byte[] crashData) throws RemoteException;
+ public void handleApplicationCrash(IBinder app,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
+ public boolean handleApplicationWtf(IBinder app, String tag,
+ ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
/*
* This will deliver the specified signal to all the persistent processes. Currently only
* SIGUSR1 is delivered. All others are ignored.
*/
public void signalPersistentProcesses(int signal) throws RemoteException;
- // Retrieve running application processes in the system
+ // Retrieve info of applications installed on external media that are currently
+ // running.
public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
throws RemoteException;
+ // Retrieve running application processes in the system
+ public List<ApplicationInfo> getRunningExternalApplications()
+ throws RemoteException;
// Get device configuration
public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException;
@@ -288,6 +301,8 @@ public interface IActivityManager extends IInterface {
public void overridePendingTransition(IBinder token, String packageName,
int enterAnim, int exitAnim) throws RemoteException;
+ public boolean isUserAMonkey() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -346,12 +361,55 @@ public interface IActivityManager extends IInterface {
}
};
+ /** Information returned after waiting for an activity start. */
+ public static class WaitResult implements Parcelable {
+ public int result;
+ public boolean timeout;
+ public ComponentName who;
+ public long thisTime;
+ public long totalTime;
+
+ public WaitResult() {
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(result);
+ dest.writeInt(timeout ? 1 : 0);
+ ComponentName.writeToParcel(who, dest);
+ dest.writeLong(thisTime);
+ dest.writeLong(totalTime);
+ }
+
+ public static final Parcelable.Creator<WaitResult> CREATOR
+ = new Parcelable.Creator<WaitResult>() {
+ public WaitResult createFromParcel(Parcel source) {
+ return new WaitResult(source);
+ }
+
+ public WaitResult[] newArray(int size) {
+ return new WaitResult[size];
+ }
+ };
+
+ private WaitResult(Parcel source) {
+ result = source.readInt();
+ timeout = source.readInt() != 0;
+ who = ComponentName.readFromParcel(source);
+ thisTime = source.readLong();
+ totalTime = source.readLong();
+ }
+ };
+
String descriptor = "android.app.IActivityManager";
// Please keep these transaction codes the same -- they are also
// sent by C++ code.
int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
- int HANDLE_APPLICATION_ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
+ int HANDLE_APPLICATION_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;
int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;
int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
@@ -425,8 +483,8 @@ public interface IActivityManager extends IInterface {
int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75;
int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76;
int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77;
- int RESTART_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
- int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
+ 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 REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
@@ -448,4 +506,11 @@ public interface IActivityManager extends IInterface {
int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98;
int START_ACTIVITY_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+99;
int OVERRIDE_PENDING_TRANSITION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+100;
+ int HANDLE_APPLICATION_WTF_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+101;
+ int KILL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+102;
+ int IS_USER_A_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+103;
+ int START_ACTIVITY_AND_WAIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+104;
+ int WILL_ACTIVITY_BE_VISIBLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+105;
+ int START_ACTIVITY_WITH_CONFIG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+106;
+ int GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+107;
}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index cb42236..edb40ed 100755
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -27,6 +27,7 @@ interface IAlarmManager {
void set(int type, long triggerAtTime, in PendingIntent operation);
void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation);
+ void setTime(long millis);
void setTimeZone(String zone);
void remove(in PendingIntent operation);
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index ed810d3..c917e81 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -100,6 +100,9 @@ public interface IApplicationThread extends IInterface {
throws RemoteException;
void setSchedulingGroup(int group) throws RemoteException;
void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException;
+ static final int PACKAGE_REMOVED = 0;
+ static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
+ void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -135,4 +138,5 @@ public interface IApplicationThread extends IInterface {
int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
+ int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
}
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 9b0550f..fed2bc5 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -16,16 +16,17 @@
package android.app;
+import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
/**
* Interface presented by applications being asked to participate in the
- * backup & restore mechanism. End user code does not typically implement
- * this interface; they subclass BackupAgent instead.
+ * backup & restore mechanism. End user code will not typically implement
+ * this interface directly; they subclass BackupAgent instead.
*
* {@hide}
*/
-interface IBackupAgent {
+oneway interface IBackupAgent {
/**
* Request that the app perform an incremental backup.
*
@@ -39,10 +40,18 @@ interface IBackupAgent {
*
* @param newState Read-write file, empty when onBackup() is called,
* where the new state blob is to be recorded.
+ *
+ * @param token Opaque token identifying this transaction. This must
+ * be echoed back to the backup service binder once the new
+ * data has been written to the data and newState files.
+ *
+ * @param callbackBinder Binder on which to indicate operation completion,
+ * passed here as a convenience to the agent.
*/
void doBackup(in ParcelFileDescriptor oldState,
in ParcelFileDescriptor data,
- in ParcelFileDescriptor newState);
+ in ParcelFileDescriptor newState,
+ int token, IBackupManager callbackBinder);
/**
* Restore an entire data snapshot to the application.
@@ -58,7 +67,15 @@ interface IBackupAgent {
* @param newState Read-write file, empty when onRestore() is called,
* that is to be written with the state description that holds after
* the restore has been completed.
+ *
+ * @param token Opaque token identifying this transaction. This must
+ * be echoed back to the backup service binder once the agent is
+ * finished restoring the application based on the restore data
+ * contents.
+ *
+ * @param callbackBinder Binder on which to indicate operation completion,
+ * passed here as a convenience to the agent.
*/
void doRestore(in ParcelFileDescriptor data, int appVersionCode,
- in ParcelFileDescriptor newState);
+ in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder);
}
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index a7d6378..cb03d2c 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,36 +16,16 @@
package android.app;
+import android.app.SearchableInfo;
import android.app.ISearchManagerCallback;
import android.content.ComponentName;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.server.search.SearchableInfo;
/** @hide */
interface ISearchManager {
- SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
+ SearchableInfo getSearchableInfo(in ComponentName launchActivity);
List<SearchableInfo> getSearchablesInGlobalSearch();
- List<SearchableInfo> getSearchablesForWebSearch();
- SearchableInfo getDefaultSearchableForWebSearch();
- void setDefaultWebSearch(in ComponentName component);
- void startSearch(in String initialQuery,
- boolean selectInitialQuery,
- in ComponentName launchActivity,
- in Bundle appSearchData,
- boolean globalSearch,
- ISearchManagerCallback searchManagerCallback,
- int ident);
-
- void triggerSearch(in String query,
- in ComponentName launchActivity,
- in Bundle appSearchData,
- ISearchManagerCallback searchManagerCallback,
- int ident);
-
- void stopSearch();
-
-
- boolean isVisible();
-
+ ComponentName getGlobalSearchActivity();
+ ComponentName getWebSearchActivity();
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
new file mode 100644
index 0000000..7e9873e
--- /dev/null
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/**
+ * Interface used to control special UI modes.
+ * @hide
+ */
+interface IUiModeManager {
+ /**
+ * Enables the car mode. Only the system can do this.
+ * @hide
+ */
+ void enableCarMode(int flags);
+
+ /**
+ * Disables the car mode.
+ */
+ void disableCarMode(int flags);
+
+ /**
+ * Return the current running mode.
+ */
+ int getCurrentModeType();
+
+ /**
+ * Sets the night mode.
+ * The mode can be one of:
+ * 1 - notnight mode
+ * 2 - night mode
+ * 3 - automatic mode switching
+ */
+ void setNightMode(int mode);
+
+ /**
+ * Gets the currently configured night mode. Return 1 for notnight,
+ * 2 for night, and 3 for automatic mode switching.
+ */
+ int getNightMode();
+}
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 804c8eb..7c2d3a0 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.app;
import android.content.Intent;
@@ -8,11 +24,24 @@ import android.os.Looper;
import android.os.Message;
/**
- * An abstract {@link Service} that serializes the handling of the Intents passed upon service
- * start and handles them on a handler thread.
+ * IntentService is a base class for {@link Service}s that handle asynchronous
+ * requests (expressed as {@link Intent}s) on demand. Clients send requests
+ * through {@link android.content.Context#startService(Intent)} calls; the
+ * service is started as needed, handles each Intent in turn using a worker
+ * thread, and stops itself when it runs out of work.
+ *
+ * <p>This "work queue processor" pattern is commonly used to offload tasks
+ * from an application's main thread. The IntentService class exists to
+ * simplify this pattern and take care of the mechanics. To use it, extend
+ * IntentService and implement {@link #onHandleIntent(Intent)}. IntentService
+ * will receive the Intents, launch a worker thread, and stop the service as
+ * appropriate.
+ *
+ * <p>All requests are handled on a single worker thread -- they may take as
+ * long as necessary (and will not block the application's main loop), but
+ * only one request will be processed at a time.
*
- * <p>To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will
- * automatically be stopped when the last enqueued {@link Intent} is handled.
+ * @see android.os.AsyncTask
*/
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
@@ -32,26 +61,42 @@ public abstract class IntentService extends Service {
}
}
+ /**
+ * Creates an IntentService. Invoked by your subclass's constructor.
+ *
+ * @param name Used to name the worker thread, important only for debugging.
+ */
public IntentService(String name) {
super();
mName = name;
}
/**
- * Control redelivery of intents. If called with true,
+ * Sets intent redelivery preferences. Usually called from the constructor
+ * with your preferred semantics.
+ *
+ * <p>If enabled is true,
+ * {@link #onStartCommand(Intent, int, int)} will return
+ * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
+ * {@link #onHandleIntent(Intent)} returns, the process will be restarted
+ * and the intent redelivered. If multiple Intents have been sent, only
+ * the most recent one is guaranteed to be redelivered.
+ *
+ * <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
- * {@link Service#START_REDELIVER_INTENT} instead of
- * {@link Service#START_NOT_STICKY}, so that if this service's process
- * is called while it is executing the Intent in
- * {@link #onHandleIntent(Intent)}, then when later restarted the same Intent
- * will be re-delivered to it, to retry its execution.
+ * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
+ * dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
-
+
@Override
public void onCreate() {
+ // TODO: It would be nice to have an option to hold a partial wakelock
+ // during processing, and to have a static startService(Context, Intent)
+ // method that would launch the service & hand off a wakelock.
+
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
@@ -73,7 +118,7 @@ public abstract class IntentService extends Service {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
-
+
@Override
public void onDestroy() {
mServiceLooper.quit();
@@ -85,9 +130,14 @@ public abstract class IntentService extends Service {
}
/**
- * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}.
- * Note that this will be invoked from a different thread than the one that handles the
- * {@link #onStart} call.
+ * This method is invoked on the worker thread with a request to process.
+ * Only one Intent is processed at a time, but the processing happens on a
+ * worker thread that runs independently from other application logic.
+ * So, if this code takes a long time, it will hold up other requests to
+ * the same IntentService, but it will not hold up anything else.
+ *
+ * @param intent The value passed to {@link
+ * android.content.Context#startService(Intent)}.
*/
protected abstract void onHandleIntent(Intent intent);
}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 19b99c8..84a57b5 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -51,31 +51,31 @@ import android.widget.ListView;
* The following code demonstrates an (ugly) custom screen layout. It has a list
* with a green background, and an alternate red "no data" message.
* </p>
- *
+ *
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:orientation=&quot;vertical&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:paddingLeft=&quot;8dp&quot;
* android:paddingRight=&quot;8dp&quot;&gt;
- *
+ *
* &lt;ListView android:id=&quot;@id/android:list&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#00FF00&quot;
* android:layout_weight=&quot;1&quot;
* android:drawSelectorOnTop=&quot;false&quot;/&gt;
- *
+ *
* &lt;TextView id=&quot;@id/android:empty&quot;
- * android:layout_width=&quot;fill_parent&quot;
- * android:layout_height=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
+ * android:layout_height=&quot;match_parent&quot;
* android:background=&quot;#FF0000&quot;
* android:text=&quot;No data&quot;/&gt;
* &lt;/LinearLayout&gt;
* </pre>
- *
+ *
* <p>
* <strong>Row Layout</strong>
* </p>
@@ -96,27 +96,27 @@ import android.widget.ListView;
* source for the resource two_line_list_item, which displays two data
* fields,one above the other, for each list row.
* </p>
- *
+ *
* <pre>
* &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
* &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;
* android:orientation=&quot;vertical&quot;&gt;
- *
+ *
* &lt;TextView android:id=&quot;@+id/text1&quot;
* android:textSize=&quot;16sp&quot;
* android:textStyle=&quot;bold&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
- *
+ *
* &lt;TextView android:id=&quot;@+id/text2&quot;
* android:textSize=&quot;16sp&quot;
- * android:layout_width=&quot;fill_parent&quot;
+ * android:layout_width=&quot;match_parent&quot;
* android:layout_height=&quot;wrap_content&quot;/&gt;
* &lt;/LinearLayout&gt;
* </pre>
- *
+ *
* <p>
* You must identify the data bound to each TextView object in this layout. The
* syntax for this is discussed in the next section.
@@ -137,40 +137,40 @@ import android.widget.ListView;
* Contacts provider for all contacts, then binding the Name and Company fields
* to a two line row layout in the activity's ListView.
* </p>
- *
+ *
* <pre>
* public class MyListAdapter extends ListActivity {
- *
+ *
* &#064;Override
* protected void onCreate(Bundle savedInstanceState){
* super.onCreate(savedInstanceState);
- *
+ *
* // We'll define a custom screen layout here (the one shown above), but
* // typically, you could just use the standard ListActivity layout.
* setContentView(R.layout.custom_list_activity_view);
- *
+ *
* // Query for all people contacts using the {@link android.provider.Contacts.People} convenience class.
* // Put a managed wrapper around the retrieved cursor so we don't have to worry about
* // requerying or closing it as the activity changes state.
* mCursor = this.getContentResolver().query(People.CONTENT_URI, null, null, null, null);
* startManagingCursor(mCursor);
- *
- * // Now create a new list adapter bound to the cursor.
+ *
+ * // Now create a new list adapter bound to the cursor.
* // SimpleListAdapter is designed for binding to a Cursor.
* ListAdapter adapter = new SimpleCursorAdapter(
* this, // Context.
- * android.R.layout.two_line_list_item, // Specify the row template to use (here, two columns bound to the two retrieved cursor
+ * android.R.layout.two_line_list_item, // Specify the row template to use (here, two columns bound to the two retrieved cursor
* rows).
* mCursor, // Pass in the cursor to bind to.
* new String[] {People.NAME, People.COMPANY}, // Array of cursor columns to bind to.
* new int[] {android.R.id.text1, android.R.id.text2}); // Parallel array of which template objects to bind to those columns.
- *
+ *
* // Bind to our new adapter.
* setListAdapter(adapter);
* }
* }
* </pre>
- *
+ *
* @see #setListAdapter
* @see android.widget.ListView
*/
@@ -194,13 +194,13 @@ public class ListActivity extends Activity {
mList.focusableViewAvailable(mList);
}
};
-
+
/**
* This method will be called when an item in the list is selected.
* Subclasses should override. Subclasses can call
* getListView().getItemAtPosition(position) if they need to access the
* data associated with the selected item.
- *
+ *
* @param l The ListView where the click happened
* @param v The view that was clicked within the ListView
* @param position The position of the view in the list
@@ -208,11 +208,11 @@ public class ListActivity extends Activity {
*/
protected void onListItemClick(ListView l, View v, int position, long id) {
}
-
+
/**
* Ensures the list view has been created before Activity restores all
* of the view states.
- *
+ *
*@see Activity#onRestoreInstanceState(Bundle)
*/
@Override
@@ -222,9 +222,18 @@ public class ListActivity extends Activity {
}
/**
+ * @see Activity#onDestroy()
+ */
+ @Override
+ protected void onDestroy() {
+ mHandler.removeCallbacks(mRequestFocus);
+ super.onDestroy();
+ }
+
+ /**
* Updates the screen state (current list and other views) when the
* content changes.
- *
+ *
* @see Activity#onContentChanged()
*/
@Override
@@ -262,7 +271,7 @@ public class ListActivity extends Activity {
/**
* Set the currently selected list item to the specified
* position with the adapter's data
- *
+ *
* @param position
*/
public void setSelection(int position) {
@@ -290,7 +299,7 @@ public class ListActivity extends Activity {
ensureList();
return mList;
}
-
+
/**
* Get the ListAdapter associated with this activity's ListView.
*/
@@ -303,7 +312,7 @@ public class ListActivity extends Activity {
return;
}
setContentView(com.android.internal.R.layout.list_content);
-
+
}
private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
@@ -313,4 +322,3 @@ public class ListActivity extends Activity {
}
};
}
-
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index be5a7d3..4d72f73 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -443,11 +443,7 @@ public class Notification implements Parcelable
contentView.setTextViewText(com.android.internal.R.id.text, contentText);
}
if (this.when != 0) {
- Date date = new Date(when);
- CharSequence str =
- DateUtils.isToday(when) ? DateFormat.getTimeFormat(context).format(date)
- : DateFormat.getDateFormat(context).format(date);
- contentView.setTextViewText(com.android.internal.R.id.time, str);
+ contentView.setLong(com.android.internal.R.id.time, "setTime", when);
}
this.contentView = contentView;
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index e5a769b..7625c04 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -16,38 +16,37 @@
package android.app;
+
import static android.app.SuggestionsAdapter.getColumnString;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Browser;
-import android.server.search.SearchableInfo;
import android.speech.RecognizerIntent;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.text.util.Regex;
-import android.util.AndroidRuntimeException;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -69,13 +68,9 @@ import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
/**
- * System search dialog. This is controlled by the
- * SearchManagerService and runs in the system process.
+ * Search dialog. This is controlled by the
+ * SearchManager and runs in the current foreground process.
*
* @hide
*/
@@ -88,15 +83,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private static final String INSTANCE_KEY_COMPONENT = "comp";
private static final String INSTANCE_KEY_APPDATA = "data";
- private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
- private static final String INSTANCE_KEY_STORED_COMPONENT = "sComp";
private static final String INSTANCE_KEY_STORED_APPDATA = "sData";
- private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev";
private static final String INSTANCE_KEY_USER_QUERY = "uQry";
- // The extra key used in an intent to the speech recognizer for in-app voice search.
- private static final String EXTRA_CALLING_PACKAGE = "calling_package";
-
// The string used for privateImeOptions to identify to the IME that it should not show
// a microphone button since one already exists in the search dialog.
private static final String IME_OPTION_NO_MICROPHONE = "nm";
@@ -117,18 +106,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private SearchableInfo mSearchable;
private ComponentName mLaunchComponent;
private Bundle mAppSearchData;
- private boolean mGlobalSearchMode;
private Context mActivityContext;
-
- // Values we store to allow user to toggle between in-app search and global search.
- private ComponentName mStoredComponentName;
- private Bundle mStoredAppSearchData;
-
- // stack of previous searchables, to support the BACK key after
- // SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.
- // The top of the stack (= previous searchable) is the last element of the list,
- // since adding and removing is efficient at the end of an ArrayList.
- private ArrayList<ComponentName> mPreviousComponents;
+ private SearchManager mSearchManager;
// For voice searching
private final Intent mVoiceWebSearchIntent;
@@ -144,6 +123,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// that modifies the contents of the text field. But if the user then edits
// the suggestion, the resulting string is saved.
private String mUserQuery;
+ // The query passed in when opening the SearchDialog. Used in the browser
+ // case to determine whether the user has edited the query.
+ private String mInitialQuery;
// A weak map of drawables we've gotten from other packages, so we don't load them
// more than once.
@@ -153,13 +135,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Last known IME options value for the search edit text.
private int mSearchAutoCompleteImeOptions;
+ private BroadcastReceiver mConfChangeListener = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ onConfigurationChanged();
+ }
+ }
+ };
+
/**
* Constructor - fires it up and makes it look like the search UI.
*
* @param context Application Context we can use for system acess
*/
- public SearchDialog(Context context) {
- super(context, com.android.internal.R.style.Theme_GlobalSearchBar);
+ public SearchDialog(Context context, SearchManager searchManager) {
+ super(context, com.android.internal.R.style.Theme_SearchBar);
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
@@ -169,6 +160,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mSearchManager = searchManager;
}
/**
@@ -181,12 +173,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
- lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
// taking up the whole window (even when transparent) is less than ideal,
// but necessary to show the popup window until the window manager supports
// having windows anchored by their parent but not clipped by them.
- lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -242,14 +233,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @return true if search dialog launched, false if not
*/
public boolean show(String initialQuery, boolean selectInitialQuery,
- ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
-
- // Reset any stored values from last time dialog was shown.
- mStoredComponentName = null;
- mStoredAppSearchData = null;
-
- boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData,
- globalSearch);
+ ComponentName componentName, Bundle appSearchData) {
+ boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData);
if (success) {
// Display the drop down as soon as possible instead of waiting for the rest of the
// pending UI stuff to get done, so that things appear faster to the user.
@@ -258,68 +243,20 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return success;
}
- private boolean isInRealAppSearch() {
- return !mGlobalSearchMode
- && (mPreviousComponents == null || mPreviousComponents.isEmpty());
- }
-
/**
- * Called in response to a press of the hard search button in
- * {@link #onKeyDown(int, KeyEvent)}, this method toggles between in-app
- * search and global search when relevant.
- *
- * If pressed within an in-app search context, this switches the search dialog out to
- * global search. If pressed within a global search context that was originally an in-app
- * search context, this switches back to the in-app search context. If pressed within a
- * global search context that has no original in-app search context (e.g., global search
- * from Home), this does nothing.
- *
- * @return false if we wanted to toggle context but could not do so successfully, true
- * in all other cases
- */
- private boolean toggleGlobalSearch() {
- String currentSearchText = mSearchAutoComplete.getText().toString();
- if (!mGlobalSearchMode) {
- mStoredComponentName = mLaunchComponent;
- mStoredAppSearchData = mAppSearchData;
-
- // If this is the browser, we have a special case to not show the icon to the left
- // of the text field, for extra space for url entry (this should be reconciled in
- // Eclair). So special case a second tap of the search button to remove any
- // already-entered text so that we can be sure to show the "Quick Search Box" hint
- // text to still make it clear to the user that we've jumped out to global search.
- //
- // TODO: When the browser icon issue is reconciled in Eclair, remove this special case.
- if (isBrowserSearch()) currentSearchText = "";
-
- return doShow(currentSearchText, false, null, mAppSearchData, true);
- } else {
- if (mStoredComponentName != null) {
- // This means we should toggle *back* to an in-app search context from
- // global search.
- return doShow(currentSearchText, false, mStoredComponentName,
- mStoredAppSearchData, false);
- } else {
- return true;
- }
- }
- }
-
- /**
- * Does the rest of the work required to show the search dialog. Called by both
- * {@link #show(String, boolean, ComponentName, Bundle, boolean)} and
- * {@link #toggleGlobalSearch()}.
- *
+ * Does the rest of the work required to show the search dialog. Called by
+ * {@link #show(String, boolean, ComponentName, Bundle)} and
+ *
* @return true if search dialog showed, false if not
*/
private boolean doShow(String initialQuery, boolean selectInitialQuery,
- ComponentName componentName, Bundle appSearchData,
- boolean globalSearch) {
+ ComponentName componentName, Bundle appSearchData) {
// set up the searchable and show the dialog
- if (!show(componentName, appSearchData, globalSearch)) {
+ if (!show(componentName, appSearchData)) {
return false;
}
+ mInitialQuery = initialQuery == null ? "" : initialQuery;
// finally, load the user's initial text (which may trigger suggestions)
setUserQuery(initialQuery);
if (selectInitialQuery) {
@@ -334,38 +271,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*
* @return <code>true</code> if search dialog launched
*/
- private boolean show(ComponentName componentName, Bundle appSearchData,
- boolean globalSearch) {
+ private boolean show(ComponentName componentName, Bundle appSearchData) {
if (DBG) {
Log.d(LOG_TAG, "show(" + componentName + ", "
- + appSearchData + ", " + globalSearch + ")");
+ + appSearchData + ")");
}
SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
- // Try to get the searchable info for the provided component (or for global search,
- // if globalSearch == true).
- mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
-
- // If we got back nothing, and it wasn't a request for global search, then try again
- // for global search, as we'll try to launch that in lieu of any component-specific search.
- if (!globalSearch && mSearchable == null) {
- globalSearch = true;
- mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
- }
+ // Try to get the searchable info for the provided component.
+ mSearchable = searchManager.getSearchableInfo(componentName);
- // If there's not even a searchable info available for global search, then really give up.
if (mSearchable == null) {
- Log.w(LOG_TAG, "No global search provider.");
return false;
}
mLaunchComponent = componentName;
mAppSearchData = appSearchData;
- // Using globalSearch here is just an optimization, just calling
- // isDefaultSearchable() should always give the same result.
- mGlobalSearchMode = globalSearch || searchManager.isDefaultSearchable(mSearchable);
mActivityContext = mSearchable.getActivityContext(getContext());
// show the dialog. this will call onStart().
@@ -374,27 +297,23 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// of any bad state in the AutoCompleteTextView etc
createContentView();
- // The Dialog uses a ContextThemeWrapper for the context; use this to change the
- // theme out from underneath us, between the global search theme and the in-app
- // search theme. They are identical except that the global search theme does not
- // dim the background of the window (because global search is full screen so it's
- // not needed and this should save a little bit of time on global search invocation).
- Object context = getContext();
- if (context instanceof ContextThemeWrapper) {
- ContextThemeWrapper wrapper = (ContextThemeWrapper) context;
- if (globalSearch) {
- wrapper.setTheme(com.android.internal.R.style.Theme_GlobalSearchBar);
- } else {
- wrapper.setTheme(com.android.internal.R.style.Theme_SearchBar);
- }
- }
show();
}
updateUI();
-
+
return true;
}
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ // Register a listener for configuration change events.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ getContext().registerReceiver(mConfChangeListener, filter);
+ }
+
/**
* The search dialog is being dismissed, so handle all of the local shutdown operations.
*
@@ -405,15 +324,16 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
public void onStop() {
super.onStop();
+ getContext().unregisterReceiver(mConfChangeListener);
+
closeSuggestionsAdapter();
// dump extra memory we're hanging on to
mLaunchComponent = null;
mAppSearchData = null;
mSearchable = null;
- mActivityContext = null;
mUserQuery = null;
- mPreviousComponents = null;
+ mInitialQuery = null;
}
/**
@@ -427,7 +347,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mWorkingSpinner.setVisible(working, false);
mWorkingSpinner.invalidateSelf();
}
-
+
/**
* Closes and gets rid of the suggestions adapter.
*/
@@ -441,7 +361,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
mSuggestionsAdapter = null;
}
-
+
/**
* Save the minimal set of data necessary to recreate the search
*
@@ -457,10 +377,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// setup info so I can recreate this particular search
bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
- bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
- bundle.putParcelable(INSTANCE_KEY_STORED_COMPONENT, mStoredComponentName);
- bundle.putBundle(INSTANCE_KEY_STORED_APPDATA, mStoredAppSearchData);
- bundle.putParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS, mPreviousComponents);
bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
return bundle;
@@ -480,22 +396,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
- boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
- ComponentName storedComponentName =
- savedInstanceState.getParcelable(INSTANCE_KEY_STORED_COMPONENT);
- Bundle storedAppSearchData =
- savedInstanceState.getBundle(INSTANCE_KEY_STORED_APPDATA);
- ArrayList<ComponentName> previousComponents =
- savedInstanceState.getParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS);
String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
- // Set stored state
- mStoredComponentName = storedComponentName;
- mStoredAppSearchData = storedAppSearchData;
- mPreviousComponents = previousComponents;
-
// show the dialog.
- if (!doShow(userQuery, false, launchComponent, appSearchData, globalSearch)) {
+ if (!doShow(userQuery, false, launchComponent, appSearchData)) {
// for some reason, we couldn't re-instantiate
return;
}
@@ -505,16 +409,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* Called after resources have changed, e.g. after screen rotation or locale change.
*/
public void onConfigurationChanged() {
- if (isShowing()) {
+ if (mSearchable != null && isShowing()) {
// Redraw (resources may have changed)
updateSearchButton();
updateSearchAppIcon();
updateSearchBadge();
updateQueryHint();
+ if (isLandscapeMode(getContext())) {
+ mSearchAutoComplete.ensureImeVisible(true);
+ }
mSearchAutoComplete.showDropDownAfterLayout();
- }
+ }
}
-
+
+ static boolean isLandscapeMode(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
/**
* Update the UI according to the info in the current value of {@link #mSearchable}.
*/
@@ -526,7 +438,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
updateSearchAppIcon();
updateSearchBadge();
updateQueryHint();
- updateVoiceButton();
+ updateVoiceButton(TextUtils.isEmpty(mUserQuery));
// In order to properly configure the input method (if one is being used), we
// need to let it know if we'll be providing suggestions. Although it would be
@@ -570,32 +482,25 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// we dismiss the entire dialog instead
mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
- if (!isInRealAppSearch()) {
- mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
- } else {
- mSearchAutoComplete.setDropDownAlwaysVisible(false);
- }
-
mSearchAutoComplete.setForceIgnoreOutsideTouch(true);
// attach the suggestions adapter, if suggestions are available
// The existence of a suggestions authority is the proxy for "suggestions available here"
if (mSearchable.getSuggestAuthority() != null) {
mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable,
- mOutsideDrawablesCache, mGlobalSearchMode);
+ mOutsideDrawablesCache);
mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
}
}
- /**
- * Update the text in the search button. Note: This is deprecated functionality, for
- * 1.0 compatibility only.
- */
- private void updateSearchButton() {
+ private void updateSearchButton() {
String textLabel = null;
Drawable iconLabel = null;
int textId = mSearchable.getSearchButtonText();
- if (textId != 0) {
+ if (isBrowserSearch()){
+ iconLabel = getContext().getResources()
+ .getDrawable(com.android.internal.R.drawable.ic_btn_search_go);
+ } else if (textId != 0) {
textLabel = mActivityContext.getResources().getString(textId);
} else {
iconLabel = getContext().getResources().
@@ -606,11 +511,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
private void updateSearchAppIcon() {
- // In Donut, we special-case the case of the browser to hide the app icon as if it were
- // global search, for extra space for url entry.
- //
- // TODO: Remove this special case once the issue has been reconciled in Eclair.
- if (mGlobalSearchMode || isBrowserSearch()) {
+ if (isBrowserSearch()) {
mAppIcon.setImageResource(0);
mAppIcon.setVisibility(View.GONE);
mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
@@ -681,10 +582,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
/**
* Update the visibility of the voice button. There are actually two voice search modes,
* either of which will activate the button.
+ * @param empty whether the search query text field is empty. If it is, then the other
+ * criteria apply to make the voice button visible. Otherwise the voice button will not
+ * be visible - i.e., if the user has typed a query, remove the voice button.
*/
- private void updateVoiceButton() {
+ private void updateVoiceButton(boolean empty) {
int visibility = View.GONE;
- if (mSearchable.getVoiceSearchEnabled()) {
+ if (mSearchable.getVoiceSearchEnabled() && empty) {
Intent testIntent = null;
if (mSearchable.getVoiceSearchLaunchWebSearch()) {
testIntent = mVoiceWebSearchIntent;
@@ -701,18 +605,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
mVoiceButton.setVisibility(visibility);
}
-
+
+ /** Called by SuggestionsAdapter when the cursor contents changed. */
+ void onDataSetChanged() {
+ if (mSearchAutoComplete != null && mSuggestionsAdapter != null) {
+ mSearchAutoComplete.onFilterComplete(mSuggestionsAdapter.getCount());
+ }
+ }
+
/**
- * Hack to determine whether this is the browser, so we can remove the browser icon
- * to the left of the search field, as a special requirement for Donut.
- *
- * TODO: For Eclair, reconcile this with the rest of the global search UI.
+ * Hack to determine whether this is the browser, so we can adjust the UI.
*/
private boolean isBrowserSearch() {
return mLaunchComponent.flattenToShortString().startsWith("com.android.browser/");
}
- /**
+ /*
* Listeners of various types
*/
@@ -758,12 +666,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return false;
}
- if (keyCode == KeyEvent.KEYCODE_SEARCH && event.getRepeatCount() == 0) {
- event.startTracking();
- // Consume search key for later use.
- return true;
- }
-
// if it's an action specified by the searchable activity, launch the
// entered query with the action key
SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
@@ -774,25 +676,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return super.onKeyDown(keyCode, event);
}
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (DBG) Log.d(LOG_TAG, "onKeyUp(" + keyCode + "," + event + ")");
- if (mSearchable == null) {
- return false;
- }
-
- if (keyCode == KeyEvent.KEYCODE_SEARCH && event.isTracking()
- && !event.isCanceled()) {
- // If the search key is pressed, toggle between global and in-app search. If we are
- // currently doing global search and there is no in-app search context to toggle to,
- // just don't do anything.
- return toggleGlobalSearch();
- }
- return super.onKeyUp(keyCode, event);
- }
-
/**
* Callback to watch the textedit field for empty/non-empty
*/
@@ -808,11 +692,18 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (mSearchable == null) {
return;
}
- updateWidgetState();
if (!mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, remember it.
mUserQuery = s == null ? "" : s.toString();
}
+ updateWidgetState();
+ // Always want to show the microphone if the context is voice.
+ // Also show the microphone if this is a browser search and the
+ // query matches the initial query.
+ updateVoiceButton(mSearchAutoComplete.isEmpty()
+ || (isBrowserSearch() && mInitialQuery.equals(mUserQuery))
+ || (mAppSearchData != null && mAppSearchData.getBoolean(
+ SearchManager.CONTEXT_IS_VOICE)));
}
public void afterTextChanged(Editable s) {
@@ -822,12 +713,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (mSearchable.autoUrlDetect() && !mSearchAutoComplete.isPerformingCompletion()) {
// The user changed the query, check if it is a URL and if so change the search
// button in the soft keyboard to the 'Go' button.
- int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
- if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
- options = options | EditorInfo.IME_ACTION_GO;
- } else {
- options = options | EditorInfo.IME_ACTION_SEARCH;
- }
+ int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION))
+ | EditorInfo.IME_ACTION_GO;
if (options != mSearchAutoCompleteImeOptions) {
mSearchAutoCompleteImeOptions = options;
mSearchAutoComplete.setImeOptions(options);
@@ -839,13 +726,31 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
};
/**
- * Enable/Disable the cancel button based on edit text state (any text?)
+ * Enable/Disable the go button based on edit text state (any text?)
*/
private void updateWidgetState() {
// enable the button if we have one or more non-space characters
boolean enabled = !mSearchAutoComplete.isEmpty();
- mGoButton.setEnabled(enabled);
- mGoButton.setFocusable(enabled);
+ if (isBrowserSearch()) {
+ // In the browser, we hide the search button when there is no text,
+ // or if the text matches the initial query.
+ if (enabled && !mInitialQuery.equals(mUserQuery)) {
+ mSearchAutoComplete.setBackgroundResource(
+ com.android.internal.R.drawable.textfield_search);
+ mGoButton.setVisibility(View.VISIBLE);
+ // Just to be sure
+ mGoButton.setEnabled(true);
+ mGoButton.setFocusable(true);
+ } else {
+ mSearchAutoComplete.setBackgroundResource(
+ com.android.internal.R.drawable.textfield_search_empty);
+ mGoButton.setVisibility(View.GONE);
+ }
+ } else {
+ // Elsewhere we just disable the button
+ mGoButton.setEnabled(enabled);
+ mGoButton.setFocusable(enabled);
+ }
}
/**
@@ -896,16 +801,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (mSearchable == null) {
return;
}
+ SearchableInfo searchable = mSearchable;
try {
- // First stop the existing search before starting voice search, or else we'll end
- // up showing the search dialog again once we return to the app.
- ((SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE)).
- stopSearch();
-
- if (mSearchable.getVoiceSearchLaunchWebSearch()) {
- getContext().startActivity(mVoiceWebSearchIntent);
- } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
- Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
+ if (searchable.getVoiceSearchLaunchWebSearch()) {
+ Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
+ searchable);
+ getContext().startActivity(webSearchIntent);
+ } else if (searchable.getVoiceSearchLaunchRecognizer()) {
+ Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
+ searchable);
getContext().startActivity(appSearchIntent);
}
} catch (ActivityNotFoundException e) {
@@ -913,18 +817,30 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// voice search before showing the button. But just in case...
Log.w(LOG_TAG, "Could not find voice search activity");
}
+ dismiss();
}
};
/**
+ * Create and return an Intent that can launch the voice search activity for web search.
+ */
+ private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
+ Intent voiceIntent = new Intent(baseIntent);
+ ComponentName searchActivity = searchable.getSearchActivity();
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
+ searchActivity == null ? null : searchActivity.flattenToShortString());
+ return voiceIntent;
+ }
+
+ /**
* Create and return an Intent that can launch the voice search activity, perform a specific
* voice transcription, and forward the results to the searchable activity.
*
* @param baseIntent The voice app search intent to start from
* @return A completely-configured intent ready to send to the voice search activity
*/
- private Intent createVoiceAppSearchIntent(Intent baseIntent) {
- ComponentName searchActivity = mSearchable.getSearchActivity();
+ private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
+ ComponentName searchActivity = searchable.getSearchActivity();
// create the necessary intent to set up a search-and-forward operation
// in the voice search system. We have to keep the bundle separate,
@@ -954,24 +870,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
String language = null;
int maxResults = 1;
Resources resources = mActivityContext.getResources();
- if (mSearchable.getVoiceLanguageModeId() != 0) {
- languageModel = resources.getString(mSearchable.getVoiceLanguageModeId());
+ if (searchable.getVoiceLanguageModeId() != 0) {
+ languageModel = resources.getString(searchable.getVoiceLanguageModeId());
}
- if (mSearchable.getVoicePromptTextId() != 0) {
- prompt = resources.getString(mSearchable.getVoicePromptTextId());
+ if (searchable.getVoicePromptTextId() != 0) {
+ prompt = resources.getString(searchable.getVoicePromptTextId());
}
- if (mSearchable.getVoiceLanguageId() != 0) {
- language = resources.getString(mSearchable.getVoiceLanguageId());
+ if (searchable.getVoiceLanguageId() != 0) {
+ language = resources.getString(searchable.getVoiceLanguageId());
}
- if (mSearchable.getVoiceMaxResults() != 0) {
- maxResults = mSearchable.getVoiceMaxResults();
+ if (searchable.getVoiceMaxResults() != 0) {
+ maxResults = searchable.getVoiceMaxResults();
}
voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
- voiceIntent.putExtra(EXTRA_CALLING_PACKAGE,
- searchActivity == null ? null : searchActivity.toShortString());
+ voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
+ searchActivity == null ? null : searchActivity.flattenToShortString());
// Add the values that configure forwarding the results
voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
@@ -1113,7 +1029,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchAutoComplete.setSelection(selPoint);
mSearchAutoComplete.setListSelection(0);
mSearchAutoComplete.clearListSelection();
- mSearchAutoComplete.ensureImeVisible();
+ mSearchAutoComplete.ensureImeVisible(true);
return true;
}
@@ -1164,14 +1080,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
protected void launchQuerySearch(int actionKey, String actionMsg) {
String query = mSearchAutoComplete.getText().toString();
- String action = mGlobalSearchMode ? Intent.ACTION_WEB_SEARCH : Intent.ACTION_SEARCH;
+ String action = Intent.ACTION_SEARCH;
Intent intent = createIntent(action, null, null, query, null,
- actionKey, actionMsg, null);
- // Allow GlobalSearch to log and create shortcut for searches launched by
- // the search button, enter key or an action key.
- if (mGlobalSearchMode) {
- mSuggestionsAdapter.reportSearch(query);
- }
+ actionKey, actionMsg);
launchIntent(intent);
}
@@ -1184,7 +1095,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected boolean launchSuggestion(int position) {
return launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
}
-
+
/**
* Launches an intent based on a suggestion.
*
@@ -1201,20 +1112,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
- // report back about the click
- if (mGlobalSearchMode) {
- // in global search mode, do it via cursor
- mSuggestionsAdapter.callCursorOnClick(c, position, actionKey, actionMsg);
- } else if (intent != null
- && mPreviousComponents != null
- && !mPreviousComponents.isEmpty()) {
- // in-app search (and we have pivoted in as told by mPreviousComponents,
- // which is used for keeping track of what we pop back to when we are pivoting into
- // in app search.)
- reportInAppClickToGlobalSearch(c, intent);
- }
-
- // launch the intent
+ // launch the intent
launchIntent(intent);
return true;
@@ -1223,164 +1121,28 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Report a click from an in app search result back to global search for shortcutting porpoises.
- *
- * @param c The cursor that is pointing to the clicked position.
- * @param intent The intent that will be launched for the click.
- */
- private void reportInAppClickToGlobalSearch(Cursor c, Intent intent) {
- // for in app search, still tell global search via content provider
- Uri uri = getClickReportingUri();
- final ContentValues cv = new ContentValues();
- cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_QUERY, mUserQuery);
- final ComponentName source = mSearchable.getSearchActivity();
- cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_COMPONENT, source.flattenToShortString());
-
- // grab the intent columns from the intent we created since it has additional
- // logic for falling back on the searchable default
- cv.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, intent.getAction());
- cv.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, intent.getDataString());
- cv.put(SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME,
- intent.getComponent().flattenToShortString());
-
- // ensure the icons will work for global search
- cv.put(SearchManager.SUGGEST_COLUMN_ICON_1,
- wrapIconForPackage(
- mSearchable.getSuggestPackage(),
- getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_1)));
- cv.put(SearchManager.SUGGEST_COLUMN_ICON_2,
- wrapIconForPackage(
- mSearchable.getSuggestPackage(),
- getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_2)));
-
- // the rest can be passed through directly
- cv.put(SearchManager.SUGGEST_COLUMN_FORMAT,
- getColumnString(c, SearchManager.SUGGEST_COLUMN_FORMAT));
- cv.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
- getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_1));
- cv.put(SearchManager.SUGGEST_COLUMN_TEXT_2,
- getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_2));
- cv.put(SearchManager.SUGGEST_COLUMN_QUERY,
- getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY));
- cv.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
- getColumnString(c, SearchManager.SUGGEST_COLUMN_SHORTCUT_ID));
- // note: deliberately omitting background color since it is only for global search
- // "more results" entries
- mContext.getContentResolver().insert(uri, cv);
- }
-
- /**
- * @return A URI appropriate for reporting a click.
- */
- private Uri getClickReportingUri() {
- Uri.Builder uriBuilder = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(SearchManager.SEARCH_CLICK_REPORT_AUTHORITY);
-
- uriBuilder.appendPath(SearchManager.SEARCH_CLICK_REPORT_URI_PATH);
-
- return uriBuilder
- .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
- .fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
- .build();
- }
-
- /**
- * Wraps an icon for a particular package. If the icon is a resource id, it is converted into
- * an android.resource:// URI.
- *
- * @param packageName The source of the icon
- * @param icon The icon retrieved from a suggestion column
- * @return An icon string appropriate for the package.
- */
- private String wrapIconForPackage(String packageName, String icon) {
- if (icon == null || icon.length() == 0 || "0".equals(icon)) {
- // SearchManager specifies that null or zero can be returned to indicate
- // no icon. We also allow empty string.
- return null;
- } else if (!Character.isDigit(icon.charAt(0))){
- return icon;
- } else {
- return new Uri.Builder()
- .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
- .authority(packageName)
- .encodedPath(icon)
- .toString();
- }
- }
-
- /**
- * Launches an intent, including any special intent handling. Doesn't dismiss the dialog
- * since that will be handled in {@link SearchDialogWrapper#performActivityResuming}
+ * Launches an intent, including any special intent handling.
*/
private void launchIntent(Intent intent) {
if (intent == null) {
return;
}
- if (handleSpecialIntent(intent)){
- return;
- }
Log.d(LOG_TAG, "launching " + intent);
try {
- // in global search mode, we send the activity straight to the original suggestion
- // source. this is because GlobalSearch may not have permission to launch the
- // intent, and to avoid the extra step of going through GlobalSearch.
- if (mGlobalSearchMode) {
- launchGlobalSearchIntent(intent);
- if (mStoredComponentName != null) {
- // If we're embedded in an application, dismiss the dialog.
- // This ensures that if the intent is handled by the current
- // activity, it's not obscured by the dialog.
- dismiss();
- }
- } else {
- // If the intent was created from a suggestion, it will always have an explicit
- // component here.
- Log.i(LOG_TAG, "Starting (as ourselves) " + intent.toURI());
- getContext().startActivity(intent);
- // If the search switches to a different activity,
- // SearchDialogWrapper#performActivityResuming
- // will handle hiding the dialog when the next activity starts, but for
- // real in-app search, we still need to dismiss the dialog.
- if (isInRealAppSearch()) {
- dismiss();
- }
- }
+ // If the intent was created from a suggestion, it will always have an explicit
+ // component here.
+ Log.i(LOG_TAG, "Starting (as ourselves) " + intent.toURI());
+ getContext().startActivity(intent);
+ // If the search switches to a different activity,
+ // SearchDialogWrapper#performActivityResuming
+ // will handle hiding the dialog when the next activity starts, but for
+ // real in-app search, we still need to dismiss the dialog.
+ dismiss();
} catch (RuntimeException ex) {
Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
}
}
- private void launchGlobalSearchIntent(Intent intent) {
- final String packageName;
- // GlobalSearch puts the original source of the suggestion in the
- // 'component name' column. If set, we send the intent to that activity.
- // We trust GlobalSearch to always set this to the suggestion source.
- String intentComponent = intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY);
- if (intentComponent != null) {
- ComponentName componentName = ComponentName.unflattenFromString(intentComponent);
- intent.setComponent(componentName);
- intent.removeExtra(SearchManager.COMPONENT_NAME_KEY);
- // Launch the intent as the suggestion source.
- // This prevents sources from using the search dialog to launch
- // intents that they don't have permission for themselves.
- packageName = componentName.getPackageName();
- } else {
- // If there is no component in the suggestion, it must be a built-in suggestion
- // from GlobalSearch (e.g. "Search the web for") or the intent
- // launched when pressing the search/go button in the search dialog.
- // Launch the intent with the permissions of GlobalSearch.
- packageName = mSearchable.getSearchActivity().getPackageName();
- }
-
- // Launch all global search suggestions as new tasks, since they don't relate
- // to the current task.
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- setBrowserApplicationId(intent);
-
- startActivityInPackage(intent, packageName);
- }
-
/**
* If the intent is to open an HTTP or HTTPS URL, we set
* {@link Browser#EXTRA_APPLICATION_ID} so that any existing browser window that
@@ -1397,106 +1159,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Starts an activity as if it had been started by the given package.
- *
- * @param intent The description of the activity to start.
- * @param packageName
- * @throws ActivityNotFoundException If the intent could not be resolved to
- * and existing activity.
- * @throws SecurityException If the package does not have permission to start
- * start the activity.
- * @throws AndroidRuntimeException If some other error occurs.
- */
- private void startActivityInPackage(Intent intent, String packageName) {
- try {
- int uid = ActivityThread.getPackageManager().getPackageUid(packageName);
- if (uid < 0) {
- throw new AndroidRuntimeException("Package UID not found " + packageName);
- }
- String resolvedType = intent.resolveTypeIfNeeded(getContext().getContentResolver());
- IBinder resultTo = null;
- String resultWho = null;
- int requestCode = -1;
- boolean onlyIfNeeded = false;
- Log.i(LOG_TAG, "Starting (uid " + uid + ", " + packageName + ") " + intent.toURI());
- int result = ActivityManagerNative.getDefault().startActivityInPackage(
- uid, intent, resolvedType, resultTo, resultWho, requestCode, onlyIfNeeded);
- checkStartActivityResult(result, intent);
- } catch (RemoteException ex) {
- throw new AndroidRuntimeException(ex);
- }
- }
-
- // Stolen from Instrumentation.checkStartActivityResult()
- private static void checkStartActivityResult(int res, Intent intent) {
- if (res >= IActivityManager.START_SUCCESS) {
- return;
- }
- switch (res) {
- case IActivityManager.START_INTENT_NOT_RESOLVED:
- case IActivityManager.START_CLASS_NOT_FOUND:
- if (intent.getComponent() != null)
- throw new ActivityNotFoundException(
- "Unable to find explicit activity class "
- + intent.getComponent().toShortString()
- + "; have you declared this activity in your AndroidManifest.xml?");
- throw new ActivityNotFoundException(
- "No Activity found to handle " + intent);
- case IActivityManager.START_PERMISSION_DENIED:
- throw new SecurityException("Not allowed to start activity "
- + intent);
- case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
- throw new AndroidRuntimeException(
- "FORWARD_RESULT_FLAG used while also requesting a result");
- default:
- throw new AndroidRuntimeException("Unknown error code "
- + res + " when starting " + intent);
- }
- }
-
- /**
- * Handles the special intent actions declared in {@link SearchManager}.
- *
- * @return <code>true</code> if the intent was handled.
- */
- private boolean handleSpecialIntent(Intent intent) {
- String action = intent.getAction();
- if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) {
- handleChangeSourceIntent(intent);
- return true;
- }
- return false;
- }
-
- /**
- * Handles {@link SearchManager#INTENT_ACTION_CHANGE_SEARCH_SOURCE}.
- */
- private void handleChangeSourceIntent(Intent intent) {
- Uri dataUri = intent.getData();
- if (dataUri == null) {
- Log.w(LOG_TAG, "SearchManager.INTENT_ACTION_CHANGE_SOURCE without intent data.");
- return;
- }
- ComponentName componentName = ComponentName.unflattenFromString(dataUri.toString());
- if (componentName == null) {
- Log.w(LOG_TAG, "Invalid ComponentName: " + dataUri);
- return;
- }
- if (DBG) Log.d(LOG_TAG, "Switching to " + componentName);
-
- pushPreviousComponent(mLaunchComponent);
- if (!show(componentName, mAppSearchData, false)) {
- Log.w(LOG_TAG, "Failed to switch to source " + componentName);
- popPreviousComponent();
- return;
- }
-
- String query = intent.getStringExtra(SearchManager.QUERY);
- setUserQuery(query);
- mSearchAutoComplete.showDropDown();
- }
-
- /**
* Sets the list item selection in the AutoCompleteTextView's ListView.
*/
public void setListSelection(int index) {
@@ -1504,61 +1166,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Checks if there are any previous searchable components in the history stack.
- */
- private boolean hasPreviousComponent() {
- return mPreviousComponents != null && !mPreviousComponents.isEmpty();
- }
-
- /**
- * Saves the previous component that was searched, so that we can go
- * back to it.
- */
- private void pushPreviousComponent(ComponentName componentName) {
- if (mPreviousComponents == null) {
- mPreviousComponents = new ArrayList<ComponentName>();
- }
- mPreviousComponents.add(componentName);
- }
-
- /**
- * Pops the previous component off the stack and returns it.
- *
- * @return The component name, or <code>null</code> if there was
- * no previous component.
- */
- private ComponentName popPreviousComponent() {
- if (!hasPreviousComponent()) {
- return null;
- }
- return mPreviousComponents.remove(mPreviousComponents.size() - 1);
- }
-
- /**
- * Goes back to the previous component that was searched, if any.
- *
- * @return <code>true</code> if there was a previous component that we could go back to.
- */
- private boolean backToPreviousComponent() {
- ComponentName previous = popPreviousComponent();
- if (previous == null) {
- return false;
- }
-
- if (!show(previous, mAppSearchData, false)) {
- Log.w(LOG_TAG, "Failed to switch to source " + previous);
- return false;
- }
-
- // must touch text to trigger suggestions
- // TODO: should this be the text as it was when the user left
- // the source that we are now going back to?
- String query = mSearchAutoComplete.getText().toString();
- setUserQuery(query);
- return true;
- }
-
- /**
* When a particular suggestion has been selected, perform the various lookups required
* to use the suggestion. This includes checking the cursor for suggestion-specific data,
* and/or falling back to the XML for defaults; It also creates REST style Uri data when
@@ -1607,10 +1214,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
- String mode = mGlobalSearchMode ? SearchManager.MODE_GLOBAL_SEARCH_SUGGESTION : null;
return createIntent(action, dataUri, extraData, query, componentName, actionKey,
- actionMsg, mode);
+ actionMsg);
} catch (RuntimeException e ) {
int rowNum;
try { // be really paranoid now
@@ -1641,16 +1247,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @return The intent.
*/
private Intent createIntent(String action, Uri data, String extraData, String query,
- String componentName, int actionKey, String actionMsg, String mode) {
+ String componentName, int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// We need CLEAR_TOP to avoid reusing an old task that has other activities
// on top of the one we want. We don't want to do this in in-app search though,
// as it can be destructive to the activity stack.
- if (mGlobalSearchMode) {
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- }
if (data != null) {
intent.setData(data);
}
@@ -1661,9 +1264,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (extraData != null) {
intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
}
- if (componentName != null) {
- intent.putExtra(SearchManager.COMPONENT_NAME_KEY, componentName);
- }
if (mAppSearchData != null) {
intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
}
@@ -1671,16 +1271,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
intent.putExtra(SearchManager.ACTION_KEY, actionKey);
intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
}
- if (mode != null) {
- intent.putExtra(SearchManager.SEARCH_MODE, mode);
- }
- // Only allow 3rd-party intents from GlobalSearch
- if (!mGlobalSearchMode) {
- intent.setComponent(mSearchable.getSearchActivity());
- }
+ intent.setComponent(mSearchable.getSearchActivity());
return intent;
}
-
+
/**
* For a given suggestion and a given cursor row, get the action message. If not provided
* by the specific row/column, also check for a single definition (for the action key).
@@ -1814,6 +1408,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
InputMethodManager inputManager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(this, 0);
+ // If in landscape mode, then make sure that
+ // the ime is in front of the dropdown.
+ if (isLandscapeMode(getContext())) {
+ ensureImeVisible(true);
+ }
}
}
@@ -1837,12 +1436,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
return;
}
- // Otherwise, go back to any previous source (e.g. back to QSB when
- // pivoted into a source.
- if (!backToPreviousComponent()) {
- // If no previous source, close search dialog
- cancel();
- }
+ // Close search dialog
+ cancel();
}
/**
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 2e94a2f..a1ca707 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -16,17 +16,22 @@
package android.app;
+import android.Manifest;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.server.search.SearchableInfo;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -38,1188 +43,18 @@ import java.util.List;
*
* <p>In practice, you won't interact with this class directly, as search
* services are provided through methods in {@link android.app.Activity Activity}
- * methods and the the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
- * {@link android.content.Intent Intent}. This class does provide a basic
- * overview of search services and how to integrate them with your activities.
- * If you do require direct access to the SearchManager, do not instantiate
- * this class directly; instead, retrieve it through
+ * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
+ * {@link android.content.Intent Intent}.
+ * If you do require direct access to the SearchManager, do not instantiate
+ * this class directly. Instead, retrieve it through
* {@link android.content.Context#getSystemService
* context.getSystemService(Context.SEARCH_SERVICE)}.
- *
- * <p>Topics covered here:
- * <ol>
- * <li><a href="#DeveloperGuide">Developer Guide</a>
- * <li><a href="#HowSearchIsInvoked">How Search Is Invoked</a>
- * <li><a href="#ImplementingSearchForYourApp">Implementing Search for Your App</a>
- * <li><a href="#Suggestions">Search Suggestions</a>
- * <li><a href="#ExposingSearchSuggestionsToQuickSearchBox">Exposing Search Suggestions to
- * Quick Search Box</a></li>
- * <li><a href="#ActionKeys">Action Keys</a>
- * <li><a href="#SearchabilityMetadata">Searchability Metadata</a>
- * <li><a href="#PassingSearchContext">Passing Search Context</a>
- * <li><a href="#ProtectingUserPrivacy">Protecting User Privacy</a>
- * </ol>
- *
- * <a name="DeveloperGuide"></a>
- * <h3>Developer Guide</h3>
- *
- * <p>The ability to search for user, system, or network based data is considered to be
- * a core user-level feature of the Android platform. At any time, the user should be
- * able to use a familiar command, button, or keystroke to invoke search, and the user
- * should be able to search any data which is available to them.
- *
- * <p>To make search appear to the user as a seamless system-wide feature, the application
- * framework centrally controls it, offering APIs to individual applications to control how they
- * are searched. Applications can customize how search is invoked, how the search dialog looks,
- * and what type of search results are available, including suggestions that are available as the
- * user types.
- *
- * <p>Even applications which are not searchable will by default support the invocation of
- * search to trigger Quick Search Box, the system's 'global search'.
- *
- * <a name="HowSearchIsInvoked"></a>
- * <h3>How Search Is Invoked</h3>
- *
- * <p>Unless impossible or inapplicable, all applications should support
- * invoking the search UI. This means that when the user invokes the search command,
- * a search UI will be presented to them. The search command is currently defined as a menu
- * item called "Search" (with an alphabetic shortcut key of "S"), or on many devices, a dedicated
- * search button key.
- * <p>If your application is not inherently searchable, the default implementation will cause
- * the search UI to be invoked in a "global search" mode known as Quick Search Box. As the user
- * types, search suggestions from across the device and the web will be surfaced, and if they
- * click the "Search" button, this will bring the browser to the front and will launch a web-based
- * search. The user will be able to click the "Back" button and return to your application.
- * <p>In general this is implemented by your activity, or the {@link android.app.Activity Activity}
- * base class, which captures the search command and invokes the SearchManager to
- * display and operate the search UI. You can also cause the search UI to be presented in response
- * to user keystrokes in your activity (for example, to instantly start filter searching while
- * viewing a list and typing any key).
- * <p>The search UI is presented as a floating
- * window and does not cause any change in the activity stack. If the user
- * cancels search, the previous activity re-emerges. If the user launches a
- * search, this will be done by sending a search {@link android.content.Intent Intent} (see below),
- * and the normal intent-handling sequence will take place (your activity will pause,
- * etc.)
- * <p><b>What you need to do:</b> First, you should consider the way in which you want to
- * handle invoking search. There are four broad (and partially overlapping) categories for
- * you to choose from.
- * <ul><li>You can capture the search command yourself, by including a <i>search</i>
- * button or menu item - and invoking the search UI directly.</li>
- * <li>You can provide a <i>type-to-search</i> feature, in which search is invoked automatically
- * when the user enters any characters.</li>
- * <li>Even if your application is not inherently searchable, you can allow global search,
- * via the search key (or even via a search menu item).
- * <li>You can disable search entirely. This should only be used in very rare circumstances,
- * as search is a system-wide feature and users will expect it to be available in all contexts.</li>
- * </ul>
- *
- * <p><b>How to define a search menu.</b> The system provides the following resources which may
- * be useful when adding a search item to your menu:
- * <ul><li>android.R.drawable.ic_search_category_default is an icon you can use in your menu.</li>
- * <li>{@link #MENU_KEY SearchManager.MENU_KEY} is the recommended alphabetic shortcut.</li>
- * </ul>
- *
- * <p><b>How to invoke search directly.</b> In order to invoke search directly, from a button
- * or menu item, you can launch a generic search by calling
- * {@link android.app.Activity#onSearchRequested onSearchRequested} as shown:
- * <pre class="prettyprint">
- * onSearchRequested();</pre>
- *
- * <p><b>How to implement type-to-search.</b> While setting up your activity, call
- * {@link android.app.Activity#setDefaultKeyMode setDefaultKeyMode}:
- * <pre class="prettyprint">
- * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // search within your activity
- * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL); // search using platform global search</pre>
- *
- * <p><b>How to enable global search with Quick Search Box.</b> In addition to searching within
- * your activity or application, you can also use the Search Manager to invoke a platform-global
- * search, which uses Quick Search Box to search across the device and the web. There are two ways
- * to do this:
- * <ul><li>You can simply define "search" within your application or activity to mean global search.
- * This is described in more detail in the
- * <a href="#SearchabilityMetadata">Searchability Metadata</a> section. Briefly, you will
- * add a single meta-data entry to your manifest, declaring that the default search
- * for your application is "*". This indicates to the system that no application-specific
- * search activity is provided, and that it should launch web-based search instead.</li>
- * <li>Simply do nothing and the default implementation of
- * {@link android.app.Activity#onSearchRequested} will cause global search to be triggered.
- * (You can also always trigger search via a direct call to {@link android.app.Activity#startSearch}.
- * This is most useful if you wish to provide local searchability <i>and</i> access to global
- * search.)</li></ul>
- *
- * <p><b>How to disable search from your activity.</b> Search is a system-wide feature and users
- * will expect it to be available in all contexts. If your UI design absolutely precludes
- * launching search, override {@link android.app.Activity#onSearchRequested onSearchRequested}
- * as shown:
- * <pre class="prettyprint">
- * &#64;Override
- * public boolean onSearchRequested() {
- * return false;
- * }</pre>
- *
- * <p><b>Managing focus and knowing if search is active.</b> The search UI is not a separate
- * activity, and when the UI is invoked or dismissed, your activity will not typically be paused,
- * resumed, or otherwise notified by the methods defined in
- * <a href="{@docRoot}guide/topics/fundamentals.html#actlife">Application Fundamentals:
- * Activity Lifecycle</a>. The search UI is
- * handled in the same way as other system UI elements which may appear from time to time, such as
- * notifications, screen locks, or other system alerts:
- * <p>When the search UI appears, your activity will lose input focus.
- * <p>When the search activity is dismissed, there are three possible outcomes:
- * <ul><li>If the user simply canceled the search UI, your activity will regain input focus and
- * proceed as before. See {@link #setOnDismissListener} and {@link #setOnCancelListener} if you
- * required direct notification of search dialog dismissals.</li>
- * <li>If the user launched a search, and this required switching to another activity to receive
- * and process the search {@link android.content.Intent Intent}, your activity will receive the
- * normal sequence of activity pause or stop notifications.</li>
- * <li>If the user launched a search, and the current activity is the recipient of the search
- * {@link android.content.Intent Intent}, you will receive notification via the
- * {@link android.app.Activity#onNewIntent onNewIntent()} method.</li></ul>
- * <p>This list is provided in order to clarify the ways in which your activities will interact with
- * the search UI. More details on searchable activities and search intents are provided in the
- * sections below.
- *
- * <a name="ImplementingSearchForYourApp"></a>
- * <h3>Implementing Search for Your App</h3>
- *
- * <p>The following steps are necessary in order to implement search.
- * <ul>
- * <li>Implement search invocation as described above. (Strictly speaking,
- * these are decoupled, but it would make little sense to be "searchable" but not
- * "search-invoking".)</li>
- * <li>Your application should have an activity that takes a search string and
- * converts it to a list of results. This could be your primary display activity
- * or it could be a dedicated search results activity. This is your <i>searchable</i>
- * activity and every query-search application must have one.</li>
- * <li>In the searchable activity, in onCreate(), you must receive and handle the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
- * {@link android.content.Intent Intent}. The text to search (query string) for is provided by
- * calling
- * {@link #QUERY getStringExtra(SearchManager.QUERY)}.</li>
- * <li>To identify and support your searchable activity, you'll need to
- * provide an XML file providing searchability configuration parameters, a reference to that
- * in your searchable activity's
- * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry, and an
- * intent-filter declaring that you can receive ACTION_SEARCH intents. This is described in more
- * detail in the <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li>
- * <li>Your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> also needs a
- * metadata entry providing a global reference to the searchable activity. This is the "glue"
- * directing the search UI, when invoked from any of your <i>other</i> activities, to use your
- * application as the default search context. This is also described in more detail in the
- * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li>
- * <li>Finally, you may want to define your search results activity as single-top with the
- * {@link android.R.attr#launchMode singleTop} launchMode flag. This allows the system
- * to launch searches from/to the same activity without creating a pile of them on the
- * activity stack. If you do this, be sure to also override
- * {@link android.app.Activity#onNewIntent onNewIntent} to handle the
- * updated intents (with new queries) as they arrive.</li>
- * </ul>
- *
- * <p>Code snippet showing handling of intents in your search activity:
- * <pre class="prettyprint">
- * &#64;Override
- * protected void onCreate(Bundle icicle) {
- * super.onCreate(icicle);
- *
- * final Intent queryIntent = getIntent();
- * final String queryAction = queryIntent.getAction();
- * if (Intent.ACTION_SEARCH.equals(queryAction)) {
- * doSearchWithIntent(queryIntent);
- * }
- * }
- *
- * private void doSearchWithIntent(final Intent queryIntent) {
- * final String queryString = queryIntent.getStringExtra(SearchManager.QUERY);
- * doSearchWithQuery(queryString);
- * }</pre>
- *
- * <a name="Suggestions"></a>
- * <h3>Search Suggestions</h3>
- *
- * <p>A powerful feature of the search system is the ability of any application to easily provide
- * live "suggestions" in order to prompt the user. Each application implements suggestions in a
- * different, unique, and appropriate way. Suggestions be drawn from many sources, including but
- * not limited to:
- * <ul>
- * <li>Actual searchable results (e.g. names in the address book)</li>
- * <li>Recently entered queries</li>
- * <li>Recently viewed data or results</li>
- * <li>Contextually appropriate queries or results</li>
- * <li>Summaries of possible results</li>
- * </ul>
- *
- * <p>Once an application is configured to provide search suggestions, those same suggestions can
- * easily be made available to the system-wide Quick Search Box, providing faster access to its
- * content from one central prominent place. See
- * <a href="#ExposingSearchSuggestionsToQuickSearchBox">Exposing Search Suggestions to Quick Search
- * Box</a> for more details.
- *
- * <p>The primary form of suggestions is known as <i>queried suggestions</i> and is based on query
- * text that the user has already typed. This would generally be based on partial matches in
- * the available data. In certain situations - for example, when no query text has been typed yet -
- * an application may also opt to provide <i>zero-query suggestions</i>.
- * These would typically be drawn from the same data source, but because no partial query text is
- * available, they should be weighted based on other factors - for example, most recent queries
- * or most recent results.
- *
- * <p><b>Overview of how suggestions are provided.</b> Suggestions are accessed via a
- * {@link android.content.ContentProvider Content Provider}. When the search manager identifies a
- * particular activity as searchable, it will check for certain metadata which indicates that
- * there is also a source of suggestions. If suggestions are provided, the following steps are
- * taken.
- * <ul><li>Using formatting information found in the metadata, the user's query text (whatever
- * has been typed so far) will be formatted into a query and sent to the suggestions
- * {@link android.content.ContentProvider Content Provider}.</li>
- * <li>The suggestions {@link android.content.ContentProvider Content Provider} will create a
- * {@link android.database.Cursor Cursor} which can iterate over the possible suggestions.</li>
- * <li>The search manager will populate a list using display data found in each row of the cursor,
- * and display these suggestions to the user.</li>
- * <li>If the user types another key, or changes the query in any way, the above steps are repeated
- * and the suggestions list is updated or repopulated.</li>
- * <li>If the user clicks or touches the "GO" button, the suggestions are ignored and the search is
- * launched using the normal {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} type of
- * {@link android.content.Intent Intent}.</li>
- * <li>If the user uses the directional controls to navigate the focus into the suggestions list,
- * the query text will be updated while the user navigates from suggestion to suggestion. The user
- * can then click or touch the updated query and edit it further. If the user navigates back to
- * the edit field, the original typed query is restored.</li>
- * <li>If the user clicks or touches a particular suggestion, then a combination of data from the
- * cursor and
- * values found in the metadata are used to synthesize an Intent and send it to the application.
- * Depending on the design of the activity and the way it implements search, this might be a
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} (in order to launch a query), or it
- * might be a {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, in order to proceed directly
- * to display of specific data.</li>
- * </ul>
- *
- * <p><b>Simple Recent-Query-Based Suggestions.</b> The Android framework provides a simple Search
- * Suggestions provider, which simply records and replays recent queries. For many applications,
- * this will be sufficient. The basic steps you will need to
- * do, in order to use the built-in recent queries suggestions provider, are as follows:
- * <ul>
- * <li>Implement and test query search, as described in the previous sections.</li>
- * <li>Create a Provider within your application by extending
- * {@link android.content.SearchRecentSuggestionsProvider}.</li>
- * <li>Create a manifest entry describing your provider.</li>
- * <li>Update your searchable activity's XML configuration file with information about your
- * provider.</li>
- * <li>In your searchable activities, capture any user-generated queries and record them
- * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery}.
- * </li>
- * </ul>
- * <p>For complete implementation details, please refer to
- * {@link android.content.SearchRecentSuggestionsProvider}. The rest of the information in this
- * section should not be necessary, as it refers to custom suggestions providers.
- *
- * <p><b>Creating a Customized Suggestions Provider:</b> In order to create more sophisticated
- * suggestion providers, you'll need to take the following steps:
- * <ul>
- * <li>Implement and test query search, as described in the previous sections.</li>
- * <li>Decide how you wish to <i>receive</i> suggestions. Just like queries that the user enters,
- * suggestions will be delivered to your searchable activity as
- * {@link android.content.Intent Intent} messages; Unlike simple queries, you have quite a bit of
- * flexibility in forming those intents. A query search application will probably
- * wish to continue receiving the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
- * {@link android.content.Intent Intent}, which will launch a query search using query text as
- * provided by the suggestion. A filter search application will probably wish to
- * receive the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}
- * {@link android.content.Intent Intent}, which will take the user directly to a selected entry.
- * Other interesting suggestions, including hybrids, are possible, and the suggestion provider
- * can easily mix-and-match results to provide a richer set of suggestions for the user. Finally,
- * you'll need to update your searchable activity (or other activities) to receive the intents
- * as you've defined them.</li>
- * <li>Implement a Content Provider that provides suggestions. If you already have one, and it
- * has access to your suggestions data, you can use that provider. If not, you'll have to create
- * one. You'll also provide information about your Content Provider in your
- * package's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</li>
- * <li>Update your searchable activity's XML configuration file. There are two categories of
- * information used for suggestions:
- * <ul><li>The first is (required) data that the search manager will
- * use to format the queries which are sent to the Content Provider.</li>
- * <li>The second is (optional) parameters to configure structure
- * if intents generated by suggestions.</li></li>
- * </ul>
- * </ul>
- *
- * <p><b>Configuring your Content Provider to Receive Suggestion Queries.</b> The basic job of
- * a search suggestions {@link android.content.ContentProvider Content Provider} is to provide
- * "live" (while-you-type) conversion of the user's query text into a set of zero or more
- * suggestions. Each application is free to define the conversion, and as described above there are
- * many possible solutions. This section simply defines how to communicate with the suggestion
- * provider.
- *
- * <p>The Search Manager must first determine if your package provides suggestions. This is done
- * by examination of your searchable meta-data XML file. The android:searchSuggestAuthority
- * attribute, if provided, is the signal to obtain & display suggestions.
- *
- * <p>Every query includes a Uri, and the Search Manager will format the Uri as shown:
- * <p><pre class="prettyprint">
- * content:// your.suggest.authority / your.suggest.path / SearchManager.SUGGEST_URI_PATH_QUERY</pre>
- *
- * <p>Your Content Provider can receive the query text in one of two ways.
- * <ul>
- * <li><b>Query provided as a selection argument.</b> If you define the attribute value
- * android:searchSuggestSelection and include a string, this string will be passed as the
- * <i>selection</i> parameter to your Content Provider's query function. You must define a single
- * selection argument, using the '?' character. The user's query text will be passed to you
- * as the first element of the selection arguments array.</li>
- * <li><b>Query provided with Data Uri.</b> If you <i>do not</i> define the attribute value
- * android:searchSuggestSelection, then the Search Manager will append another "/" followed by
- * the user's query to the query Uri. The query will be encoding using Uri encoding rules - don't
- * forget to decode it. (See {@link android.net.Uri#getPathSegments} and
- * {@link android.net.Uri#getLastPathSegment} for helpful utilities you can use here.)</li>
- * </ul>
- *
- * <p><b>Providing access to Content Providers that require permissions.</b> If your content
- * provider declares an android:readPermission in your application's manifest, you must provide
- * access to the search infrastructure to the search suggestion path by including a path-permission
- * that grants android:readPermission access to "android.permission.GLOBAL_SEARCH". Granting access
- * explicitly to the search infrastructure ensures it will be able to access the search suggestions
- * without needing to know ahead of time any other details of the permissions protecting your
- * provider. Content providers that require no permissions are already available to the search
- * infrastructure. Here is an example of a provider that protects access to it with permissions,
- * and provides read access to the search infrastructure to the path that it expects to receive the
- * suggestion query on:
- * <pre class="prettyprint">
- * &lt;provider android:name="MyProvider" android:authorities="myprovider"
- * android:readPermission="android.permission.READ_MY_DATA"
- * android:writePermission="android.permission.WRITE_MY_DATA"&gt;
- * &lt;path-permission android:path="/search_suggest_query"
- * android:readPermission="android.permission.GLOBAL_SEARCH" /&gt;
- * &lt;/provider&gt;
- * </pre>
- *
- * <p><b>Handling empty queries.</b> Your application should handle the "empty query"
- * (no user text entered) case properly, and generate useful suggestions in this case. There are a
- * number of ways to do this; Two are outlined here:
- * <ul><li>For a simple filter search of local data, you could simply present the entire dataset,
- * unfiltered. (example: People)</li>
- * <li>For a query search, you could simply present the most recent queries. This allows the user
- * to quickly repeat a recent search.</li></ul>
- *
- * <p><b>The Format of Individual Suggestions.</b> Your suggestions are communicated back to the
- * Search Manager by way of a {@link android.database.Cursor Cursor}. The Search Manager will
- * usually pass a null Projection, which means that your provider can simply return all appropriate
- * columns for each suggestion. The columns currently defined are:
- *
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Column Name</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tbody>
- * <tr><th>{@link #SUGGEST_COLUMN_FORMAT}</th>
- * <td><i>Unused - can be null.</i></td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_TEXT_1}</th>
- * <td>This is the line of text that will be presented to the user as the suggestion.</td>
- * <td align="center">Yes</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_TEXT_2}</th>
- * <td>If your cursor includes this column, then all suggestions will be provided in a
- * two-line format. The data in this column will be displayed as a second, smaller
- * line of text below the primary suggestion, or it can be null or empty to indicate no
- * text in this row's suggestion.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_ICON_1}</th>
- * <td>If your cursor includes this column, then all suggestions will be provided in an
- * icons+text format. This value should be a reference to the icon to
- * draw on the left side, or it can be null or zero to indicate no icon in this row.
- * </td>
- * <td align="center">No.</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_ICON_2}</th>
- * <td>If your cursor includes this column, then all suggestions will be provided in an
- * icons+text format. This value should be a reference to the icon to
- * draw on the right side, or it can be null or zero to indicate no icon in this row.
- * </td>
- * <td align="center">No.</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_INTENT_ACTION}</th>
- * <td>If this column exists <i>and</i> this element exists at the given row, this is the
- * action that will be used when forming the suggestion's intent. If the element is
- * not provided, the action will be taken from the android:searchSuggestIntentAction
- * field in your XML metadata. <i>At least one of these must be present for the
- * suggestion to generate an intent.</i> Note: If your action is the same for all
- * suggestions, it is more efficient to specify it using XML metadata and omit it from
- * the cursor.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA}</th>
- * <td>If this column exists <i>and</i> this element exists at the given row, this is the
- * data that will be used when forming the suggestion's intent. If the element is not
- * provided, the data will be taken from the android:searchSuggestIntentData field in
- * your XML metadata. If neither source is provided, the Intent's data field will be
- * null. Note: If your data is the same for all suggestions, or can be described
- * using a constant part and a specific ID, it is more efficient to specify it using
- * XML metadata and omit it from the cursor.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA_ID}</th>
- * <td>If this column exists <i>and</i> this element exists at the given row, then "/" and
- * this value will be appended to the data field in the Intent. This should only be
- * used if the data field has already been set to an appropriate base string.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA}</th>
- * <td>If this column exists <i>and</i> this element exists at a given row, this is the
- * data that will be used when forming the suggestion's intent. If not provided,
- * the Intent's extra data field will be null. This column allows suggestions to
- * provide additional arbitrary data which will be included as an extra under the
- * key {@link #EXTRA_DATA_KEY}.</td>
- * <td align="center">No.</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_QUERY}</th>
- * <td>If this column exists <i>and</i> this element exists at the given row, this is the
- * data that will be used when forming the suggestion's query.</td>
- * <td align="center">Required if suggestion's action is
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_SHORTCUT_ID}</th>
- * <td>This column is used to indicate whether a search suggestion should be stored as a
- * shortcut, and whether it should be validated. Shortcuts are usually formed when the
- * user clicks a suggestion from Quick Search Box. If missing, the result will be
- * stored as a shortcut and never refreshed. If set to
- * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut.
- * Otherwise, the shortcut id will be used to check back for for an up to date
- * suggestion using {@link #SUGGEST_URI_PATH_SHORTCUT}. Read more about shortcut
- * refreshing in the section about
- * <a href="#ExposingSearchSuggestionsToQuickSearchBox">exposing search suggestions to
- * Quick Search Box</a>.</td>
- * <td align="center">No. Only applicable to sources included in Quick Search Box.</td>
- * </tr>
- *
- * <tr><th>{@link #SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING}</th>
- * <td>This column is used to specify that a spinner should be shown in lieu of an icon2
- * while the shortcut of this suggestion is being refreshed in Quick Search Box.</td>
- * <td align="center">No. Only applicable to sources included in Quick Search Box.</td>
- * </tr>
- *
- * <tr><th><i>Other Columns</i></th>
- * <td>Finally, if you have defined any <a href="#ActionKeys">Action Keys</a> and you wish
- * for them to have suggestion-specific definitions, you'll need to define one
- * additional column per action key. The action key will only trigger if the
- * currently-selection suggestion has a non-empty string in the corresponding column.
- * See the section on <a href="#ActionKeys">Action Keys</a> for additional details and
- * implementation steps.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <p>Clearly there are quite a few permutations of your suggestion data, but in the next section
- * we'll look at a few simple combinations that you'll select from.
- *
- * <p><b>The Format Of Intents Sent By Search Suggestions.</b> Although there are many ways to
- * configure these intents, this document will provide specific information on just a few of them.
- * <ul><li><b>Launch a query.</b> In this model, each suggestion represents a query that your
- * searchable activity can perform, and the {@link android.content.Intent Intent} will be formatted
- * exactly like those sent when the user enters query text and clicks the "GO" button:
- * <ul>
- * <li><b>Action:</b> {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} provided
- * using your XML metadata (android:searchSuggestIntentAction).</li>
- * <li><b>Data:</b> empty (not used).</li>
- * <li><b>Query:</b> query text supplied by the cursor.</li>
- * </ul>
- * </li>
- * <li><b>Go directly to a result, using a complete Data Uri.</b> In this model, the user will be
- * taken directly to a specific result.
- * <ul>
- * <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li>
- * <li><b>Data:</b> a complete Uri, supplied by the cursor, that identifies the desired data.</li>
- * <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li>
- * </ul>
- * </li>
- * <li><b>Go directly to a result, using a synthesized Data Uri.</b> This has the same result
- * as the previous suggestion, but provides the Data Uri in a different way.
- * <ul>
- * <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li>
- * <li><b>Data:</b> The search manager will assemble a Data Uri using the following elements:
- * a Uri fragment provided in your XML metadata (android:searchSuggestIntentData), followed by
- * a single "/", followed by the value found in the {@link #SUGGEST_COLUMN_INTENT_DATA_ID}
- * entry in your cursor.</li>
- * <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li>
- * </ul>
- * </li>
- * </ul>
- * <p>This list is not meant to be exhaustive. Applications should feel free to define other types
- * of suggestions. For example, you could reduce long lists of results to summaries, and use one
- * of the above intents (or one of your own) with specially formatted Data Uri's to display more
- * detailed results. Or you could display textual shortcuts as suggestions, but launch a display
- * in a more data-appropriate format such as media artwork.
- *
- * <p><b>Suggestion Rewriting.</b> If the user navigates through the suggestions list, the UI
- * may temporarily rewrite the user's query with a query that matches the currently selected
- * suggestion. This enables the user to see what query is being suggested, and also allows the user
- * to click or touch in the entry EditText element and make further edits to the query before
- * dispatching it. In order to perform this correctly, the Search UI needs to know exactly what
- * text to rewrite the query with.
- *
- * <p>For each suggestion, the following logic is used to select a new query string:
- * <ul><li>If the suggestion provides an explicit value in the {@link #SUGGEST_COLUMN_QUERY}
- * column, this value will be used.</li>
- * <li>If the metadata includes the queryRewriteFromData flag, and the suggestion provides an
- * explicit value for the intent Data field, this Uri will be used. Note that this should only be
- * used with Uri's that are intended to be user-visible, such as HTTP. Internal Uri schemes should
- * not be used in this way.</li>
- * <li>If the metadata includes the queryRewriteFromText flag, the text in
- * {@link #SUGGEST_COLUMN_TEXT_1} will be used. This should be used for suggestions in which no
- * query text is provided and the SUGGEST_COLUMN_INTENT_DATA values are not suitable for user
- * inspection and editing.</li></ul>
- *
- * <a name="ExposingSearchSuggestionsToQuickSearchBox"></a>
- * <h3>Exposing Search Suggestions to Quick Search Box</h3>
- *
- * <p>Once your application is set up to provide search suggestions, making them available to the
- * globally accessable Quick Search Box is as easy as setting android:includeInGlobalSearch to
- * "true" in your searchable metadata file. Beyond that, here are some more details of how
- * suggestions interact with Quick Search Box, and optional ways that you may customize suggestions
- * for your application.
- *
- * <p><b>Important Note:</b> By default, your application will not be enabled as a suggestion
- * provider (or "searchable item") in Quick Search Box. Once your app is installed, the user must
- * enable it as a "searchable item" in the Search settings in order to receive your app's
- * suggestions in Quick Search Box. You should consider how to message this to users of your app -
- * perhaps with a note to the user the first time they launch the app about how to enable search
- * suggestions. This gives your app a chance to be queried for suggestions as the user types into
- * Quick Search Box, though exactly how or if your suggestions will be surfaced is decided by Quick
- * Search Box.
- *
- * <p><b>Source Ranking:</b> Once your application's search results are made available to Quick
- * Search Box, how they surface to the user for a particular query will be determined as appropriate
- * by Quick Search Box ranking. This may depend on how many other apps have results for that query,
- * and how often the user has clicked on your results compared to the other apps - but there is no
- * guarantee about how ranking will occur, or whether your app's suggestions will show at all for
- * a given query. In general, you can expect that providing quality results will increase the
- * likelihood that your app's suggestions are provided in a prominent position, and apps that
- * provide lower quality suggestions will be more likely to be ranked lower and/or not displayed.
- *
- * <p><b>Search Settings:</b> Each app that is available to Quick Search Box has an entry in the
- * system settings where the user can enable or disable the inclusion of its results. Below the
- * name of the application, each application may provide a brief description of what kind of
- * information will be made available via a search settings description string pointed to by the
- * android:searchSettingsDescription attribute in the searchable metadata. Note that the
- * user will need to visit this settings menu to enable search suggestions for your app before your
- * app will have a chance to provide search suggestions to Quick Search Box - see the section
- * called "Important Note" above.
- *
- * <p><b>Shortcuts:</b> Suggestions that are clicked on by the user may be automatically made into
- * shortcuts, which are suggestions that have been copied from your provider in order to be quickly
- * displayed without the need to re-query the original sources. Shortcutted suggestions may be
- * displayed for the query that yielded the suggestion and for any prefixes of that query. You can
- * request how to have your app's suggestions made into shortcuts, and whether they should be
- * refreshed, using the {@link #SUGGEST_COLUMN_SHORTCUT_ID} column:
- * <ul><li>Suggestions that do not include a shortcut id column will be made into shortcuts and
- * never refreshed. This makes sense for suggestions that refer to data that will never be changed
- * or removed.</li>
- * <li>Suggestions that include a shortcut id will be re-queried for a fresh version of the
- * suggestion each time the shortcut is displayed. The shortcut will be quickly displayed with
- * whatever data was most recently available until the refresh query returns, after which the
- * suggestion will be dynamically refreshed with the up to date information. The shortcut refresh
- * query will be sent to your suggestion provider with a uri of {@link #SUGGEST_URI_PATH_SHORTCUT}.
- * The result should contain one suggestion using the same columns as the suggestion query, or be
- * empty, indicating that the shortcut is no longer valid. Shortcut ids make sense when referring
- * to data that may change over time, such as a contact's presence status. If a suggestion refers
- * to data that could take longer to refresh, such as a network based refresh of a stock quote, you
- * may include {@link #SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING} to show a progress spinner for the
- * right hand icon until the refresh is complete.</li>
- * <li>Finally, to prevent a suggestion from being copied into a shortcut, you may provide a
- * shortcut id with a value of {@link #SUGGEST_NEVER_MAKE_SHORTCUT}.</li></ul>
- *
- * Note that Quick Search Box will ultimately decide whether to shortcut your app's suggestions,
- * considering these values as a strong request from your application.
- *
- * <a name="ActionKeys"></a>
- * <h3>Action Keys</h3>
- *
- * <p>Searchable activities may also wish to provide shortcuts based on the various action keys
- * available on the device. The most basic example of this is the contacts app, which enables the
- * green "dial" key for quick access during searching. Not all action keys are available on
- * every device, and not all are allowed to be overriden in this way. (For example, the "Home"
- * key must always return to the home screen, with no exceptions.)
- *
- * <p>In order to define action keys for your searchable application, you must do two things.
- *
- * <ul>
- * <li>You'll add one or more <i>actionkey</i> elements to your searchable metadata configuration
- * file. Each element defines one of the keycodes you are interested in,
- * defines the conditions under which they are sent, and provides details
- * on how to communicate the action key event back to your searchable activity.</li>
- * <li>In your broadcast receiver, if you wish, you can check for action keys by checking the
- * extras field of the {@link android.content.Intent Intent}.</li>
- * </ul>
- *
- * <p><b>Updating metadata.</b> For each keycode of interest, you must add an &lt;actionkey&gt;
- * element. Within this element you must define two or three attributes. The first attribute,
- * &lt;android:keycode&gt;, is required; It is the key code of the action key event, as defined in
- * {@link android.view.KeyEvent}. The remaining two attributes define the value of the actionkey's
- * <i>message</i>, which will be passed to your searchable activity in the
- * {@link android.content.Intent Intent} (see below for more details). Although each of these
- * attributes is optional, you must define one or both for the action key to have any effect.
- * &lt;android:queryActionMsg&gt; provides the message that will be sent if the action key is
- * pressed while the user is simply entering query text. &lt;android:suggestActionMsgColumn&gt;
- * is used when action keys are tied to specific suggestions. This attribute provides the name
- * of a <i>column</i> in your suggestion cursor; The individual suggestion, in that column,
- * provides the message. (If the cell is empty or null, that suggestion will not work with that
- * action key.)
- * <p>See the <a href="#SearchabilityMetadata">Searchability Metadata</a> section for more details
- * and examples.
- *
- * <p><b>Receiving Action Keys</b> Intents launched by action keys will be specially marked
- * using a combination of values. This enables your searchable application to examine the intent,
- * if necessary, and perform special processing. For example, clicking a suggested contact might
- * simply display them; Selecting a suggested contact and clicking the dial button might
- * immediately call them.
- *
- * <p>When a search {@link android.content.Intent Intent} is launched by an action key, two values
- * will be added to the extras field.
- * <ul>
- * <li>To examine the key code, use {@link android.content.Intent#getIntExtra
- * getIntExtra(SearchManager.ACTION_KEY)}.</li>
- * <li>To examine the message string, use {@link android.content.Intent#getStringExtra
- * getStringExtra(SearchManager.ACTION_MSG)}</li>
- * </ul>
- *
- * <a name="SearchabilityMetadata"></a>
- * <h3>Searchability Metadata</h3>
- *
- * <p>Every activity that is searchable must provide a small amount of additional information
- * in order to properly configure the search system. This controls the way that your search
- * is presented to the user, and controls for the various modalities described previously.
- *
- * <p>If your application is not searchable,
- * then you do not need to provide any search metadata, and you can skip the rest of this section.
- * When this search metadata cannot be found, the search manager will assume that the activity
- * does not implement search. (Note: to implement web-based search, you will need to add
- * the android.app.default_searchable metadata to your manifest, as shown below.)
- *
- * <p>Values you supply in metadata apply only to each local searchable activity. Each
- * searchable activity can define a completely unique search experience relevant to its own
- * capabilities and user experience requirements, and a single application can even define multiple
- * searchable activities.
- *
- * <p><b>Metadata for searchable activity.</b> As with your search implementations described
- * above, you must first identify which of your activities is searchable. In the
- * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for this activity, you must
- * provide two elements:
- * <ul><li>An intent-filter specifying that you can receive and process the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} {@link android.content.Intent Intent}.
- * </li>
- * <li>A reference to a small XML file (typically called "searchable.xml") which contains the
- * remaining configuration information for how your application implements search.</li></ul>
- *
- * <p>Here is a snippet showing the necessary elements in the
- * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for your searchable activity.
- * <pre class="prettyprint">
- * &lt;!-- Search Activity - searchable --&gt;
- * &lt;activity android:name="MySearchActivity"
- * android:label="Search"
- * android:launchMode="singleTop"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.intent.action.SEARCH" /&gt;
- * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;meta-data android:name="android.app.searchable"
- * android:resource="@xml/searchable" /&gt;
- * &lt;/activity&gt;</pre>
- *
- * <p>Next, you must provide the rest of the searchability configuration in
- * the small XML file, stored in the ../xml/ folder in your build. The XML file is a
- * simple enumeration of the search configuration parameters for searching within this activity,
- * application, or package. Here is a sample XML file (named searchable.xml, for use with
- * the above manifest) for a query-search activity.
- *
- * <pre class="prettyprint">
- * &lt;searchable xmlns:android="http://schemas.android.com/apk/res/android"
- * android:label="@string/search_label"
- * android:hint="@string/search_hint" &gt;
- * &lt;/searchable&gt;</pre>
- *
- * <p>Note that all user-visible strings <i>must</i> be provided in the form of "@string"
- * references. Hard-coded strings, which cannot be localized, will not work properly in search
- * metadata.
- *
- * <p>Attributes you can set in search metadata:
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tbody>
- * <tr><th>android:label</th>
- * <td>This is the name for your application that will be presented to the user in a
- * list of search targets, or in the search box as a label.</td>
- * <td align="center">Yes</td>
- * </tr>
- *
- * <tr><th>android:icon</th>
- * <td><strong>This is deprecated.</strong><br/>The default
- * application icon is now always used, so this attribute is
- * obsolete.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:hint</th>
- * <td>This is the text to display in the search text field when no text
- * has been entered by the user.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:searchMode</th>
- * <td>If provided and non-zero, sets additional modes for control of the search
- * presentation. The following mode bits are defined:
- * <table border="2" align="center" frame="hsides" rules="rows">
- * <tbody>
- * <tr><th>showSearchLabelAsBadge</th>
- * <td>If set, this flag enables the display of the search target (label)
- * above the search box. As an alternative, you may
- * want to instead use "hint" text in the search box.
- * See the "android:hint" attribute above.</td>
- * </tr>
- * <tr><th>showSearchIconAsBadge</th>
- * <td><strong>This is deprecated.</strong><br/>The default
- * application icon is now always used, so this
- * option is obsolete.</td>
- * </tr>
- * <tr><th>queryRewriteFromData</th>
- * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA
- * to be considered as the text for suggestion query rewriting. This should
- * only be used when the values in SUGGEST_COLUMN_INTENT_DATA are suitable
- * for user inspection and editing - typically, HTTP/HTTPS Uri's.</td>
- * </tr>
- * <tr><th>queryRewriteFromText</th>
- * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_TEXT_1 to
- * be considered as the text for suggestion query rewriting. This should
- * be used for suggestions in which no query text is provided and the
- * SUGGEST_COLUMN_INTENT_DATA values are not suitable for user inspection
- * and editing.</td>
- * </tr>
- * </tbody>
- * </table>
- * Note that the icon of your app will likely be shown alongside any badge you specify,
- * to differentiate search in your app from Quick Search Box. The display of this icon
- * is not under the app's control.
- * </td>
- *
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:inputType</th>
- * <td>If provided, supplies a hint about the type of search text the user will be
- * entering. For most searches, in which free form text is expected, this attribute
- * need not be provided. Suitable values for this attribute are described in the
- * <a href="../R.attr.html#inputType">inputType</a> attribute.</td>
- * <td align="center">No</td>
- * </tr>
- * <tr><th>android:imeOptions</th>
- * <td>If provided, supplies additional options for the input method.
- * For most searches, in which free form text is expected, this attribute
- * need not be provided, and will default to "actionSearch".
- * Suitable values for this attribute are described in the
- * <a href="../R.attr.html#imeOptions">imeOptions</a> attribute.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <p><b>Styleable Resources in your Metadata.</b> It's possible to provide alternate strings
- * for your searchable application, in order to provide localization and/or to better visual
- * presentation on different device configurations. Each searchable activity has a single XML
- * metadata file, but any resource references can be replaced at runtime based on device
- * configuration, language setting, and other system inputs.
- *
- * <p>A concrete example is the "hint" text you supply using the android:searchHint attribute.
- * In portrait mode you'll have less screen space and may need to provide a shorter string, but
- * in landscape mode you can provide a longer, more descriptive hint. To do this, you'll need to
- * define two or more strings.xml files, in the following directories:
- * <ul><li>.../res/values-land/strings.xml</li>
- * <li>.../res/values-port/strings.xml</li>
- * <li>.../res/values/strings.xml</li></ul>
- *
- * <p>For more complete documentation on this capability, see
- * <a href="{@docRoot}guide/topics/resources/resources-i18n.html#AlternateResources">Resources and
- * Internationalization: Alternate Resources</a>.
- *
- * <p><b>Metadata for non-searchable activities.</b> Activities which are part of a searchable
- * application, but don't implement search itself, require a bit of "glue" in order to cause
- * them to invoke search using your searchable activity as their primary context. If this is not
- * provided, then searches from these activities will use the system default search context.
- *
- * <p>The simplest way to specify this is to add a <i>search reference</i> element to the
- * application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file.
- * The value of this reference can be either of:
- * <ul><li>The name of your searchable activity.
- * It is typically prefixed by '.' to indicate that it's in the same package.</li>
- * <li>A "*" indicates that the system may select a default searchable activity, in which
- * case it will typically select web-based search.</li>
- * </ul>
- *
- * <p>Here is a snippet showing the necessary addition to the manifest entry for your
- * non-searchable activities.
- * <pre class="prettyprint">
- * &lt;application&gt;
- * &lt;meta-data android:name="android.app.default_searchable"
- * android:value=".MySearchActivity" /&gt;
- *
- * &lt;!-- followed by activities, providers, etc... --&gt;
- * &lt;/application&gt;</pre>
- *
- * <p>You can also specify android.app.default_searchable on a per-activity basis, by including
- * the meta-data element (as shown above) in one or more activity sections. If found, these will
- * override the reference in the application section. The only reason to configure your application
- * this way would be if you wish to partition it into separate sections with different search
- * behaviors; Otherwise this configuration is not recommended.
- *
- * <p><b>Additional metadata for search suggestions.</b> If you have defined a content provider
- * to generate search suggestions, you'll need to publish it to the system, and you'll need to
- * provide a bit of additional XML metadata in order to configure communications with it.
- *
- * <p>First, in your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>, you'll add the
- * following lines.
- * <pre class="prettyprint">
- * &lt;!-- Content provider for search suggestions --&gt;
- * &lt;provider android:name="YourSuggestionProviderClass"
- * android:authorities="your.suggestion.authority" /&gt;</pre>
- *
- * <p>Next, you'll add a few lines to your XML metadata file, as shown:
- * <pre class="prettyprint">
- * &lt;!-- Required attribute for any suggestions provider --&gt;
- * android:searchSuggestAuthority="your.suggestion.authority"
- *
- * &lt;!-- Optional attribute for configuring queries --&gt;
- * android:searchSuggestSelection="field =?"
- *
- * &lt;!-- Optional attributes for configuring intent construction --&gt;
- * android:searchSuggestIntentAction="intent action string"
- * android:searchSuggestIntentData="intent data Uri" /&gt;</pre>
- *
- * <p>Elements of search metadata that support suggestions:
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tbody>
- * <tr><th>android:searchSuggestAuthority</th>
- * <td>This value must match the authority string provided in the <i>provider</i> section
- * of your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</td>
- * <td align="center">Yes</td>
- * </tr>
- *
- * <tr><th>android:searchSuggestPath</th>
- * <td>If provided, this will be inserted in the suggestions query Uri, after the authority
- * you have provide but before the standard suggestions path. This is only required if
- * you have a single content provider issuing different types of suggestions (e.g. for
- * different data types) and you need a way to disambiguate the suggestions queries
- * when they are received.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:searchSuggestSelection</th>
- * <td>If provided, this value will be passed into your query function as the
- * <i>selection</i> parameter. Typically this will be a WHERE clause for your database,
- * and will contain a single question mark, which represents the actual query string
- * that has been typed by the user. However, you can also use any non-null value
- * to simply trigger the delivery of the query text (via selection arguments), and then
- * use the query text in any way appropriate for your provider (ignoring the actual
- * text of the selection parameter.)</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:searchSuggestIntentAction</th>
- * <td>If provided, and not overridden by the selected suggestion, this value will be
- * placed in the action field of the {@link android.content.Intent Intent} when the
- * user clicks a suggestion.</td>
- * <td align="center">No</td>
- *
- * <tr><th>android:searchSuggestIntentData</th>
- * <td>If provided, and not overridden by the selected suggestion, this value will be
- * placed in the data field of the {@link android.content.Intent Intent} when the user
- * clicks a suggestion.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <p>Elements of search metadata that configure search suggestions being available to Quick Search
- * Box:
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tr><th>android:includeInGlobalSearch</th>
- * <td>If true, indicates the search suggestions provided by your application should be
- * included in the globally accessible Quick Search Box. The attributes below are only
- * applicable if this is set to true.</td>
- * <td align="center">Yes</td>
- * </tr>
- *
- * <tr><th>android:searchSettingsDescription</th>
- * <td>If provided, provides a brief description of the search suggestions that are provided
- * by your application to Quick Search Box, and will be displayed in the search settings
- * entry for your application.</td>
- * <td align="center">No</td>
- * </tr>
*
- * <tr><th>android:queryAfterZeroResults</th>
- * <td>Indicates whether a source should be invoked for supersets of queries it has
- * returned zero results for in the past. For example, if a source returned zero
- * results for "bo", it would be ignored for "bob". If set to false, this source
- * will only be ignored for a single session; the next time the search dialog is
- * invoked, all sources will be queried. The default value is false.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:searchSuggestThreshold</th>
- * <td>Indicates the minimum number of characters needed to trigger a source from Quick
- * Search Box. Only guarantees that a source will not be queried for anything shorter
- * than the threshold. The default value is 0.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <p><b>Additional metadata for search action keys.</b> For each action key that you would like to
- * define, you'll need to add an additional element defining that key, and using the attributes
- * discussed in <a href="#ActionKeys">Action Keys</a>. A simple example is shown here:
- *
- * <pre class="prettyprint">&lt;actionkey
- * android:keycode="KEYCODE_CALL"
- * android:queryActionMsg="call"
- * android:suggestActionMsg="call"
- * android:suggestActionMsgColumn="call_column" /&gt;</pre>
- *
- * <p>Elements of search metadata that support search action keys. Note that although each of the
- * action message elements are marked as <i>optional</i>, at least one must be present for the
- * action key to have any effect.
- *
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tbody>
- * <tr><th>android:keycode</th>
- * <td>This attribute denotes the action key you wish to respond to. Note that not
- * all action keys are actually supported using this mechanism, as many of them are
- * used for typing, navigation, or system functions. This will be added to the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to
- * your searchable activity. To examine the key code, use
- * {@link android.content.Intent#getIntExtra getIntExtra(SearchManager.ACTION_KEY)}.
- * <p>Note, in addition to the keycode, you must also provide one or more of the action
- * specifier attributes.</td>
- * <td align="center">Yes</td>
- * </tr>
- *
- * <tr><th>android:queryActionMsg</th>
- * <td>If you wish to handle an action key during normal search query entry, you
- * must define an action string here. This will be added to the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your
- * searchable activity. To examine the string, use
- * {@link android.content.Intent#getStringExtra
- * getStringExtra(SearchManager.ACTION_MSG)}.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:suggestActionMsg</th>
- * <td>If you wish to handle an action key while a suggestion is being displayed <i>and
- * selected</i>, there are two ways to handle this. If <i>all</i> of your suggestions
- * can handle the action key, you can simply define the action message using this
- * attribute. This will be added to the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to
- * your searchable activity. To examine the string, use
- * {@link android.content.Intent#getStringExtra
- * getStringExtra(SearchManager.ACTION_MSG)}.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:suggestActionMsgColumn</th>
- * <td>If you wish to handle an action key while a suggestion is being displayed <i>and
- * selected</i>, but you do not wish to enable this action key for every suggestion,
- * then you can use this attribute to control it on a suggestion-by-suggestion basis.
- * First, you must define a column (and name it here) where your suggestions will
- * include the action string. Then, in your content provider, you must provide this
- * column, and when desired, provide data in this column.
- * The search manager will look at your suggestion cursor, using the string
- * provided here in order to select a column, and will use that to select a string from
- * the cursor. That string will be added to the
- * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to
- * your searchable activity. To examine the string, use
- * {@link android.content.Intent#getStringExtra
- * getStringExtra(SearchManager.ACTION_MSG)}. <i>If the data does not exist for the
- * selection suggestion, the action key will be ignored.</i></td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <p><b>Additional metadata for enabling voice search.</b> To enable voice search for your
- * activity, you can add fields to the metadata that enable and configure voice search. When
- * enabled (and available on the device), a voice search button will be displayed in the
- * Search UI. Clicking this button will launch a voice search activity. When the user has
- * finished speaking, the voice search phrase will be transcribed into text and presented to the
- * searchable activity as if it were a typed query.
- *
- * <p>Elements of search metadata that support voice search:
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr>
- * </thead>
- *
- * <tr><th>android:voiceSearchMode</th>
- * <td>If provided and non-zero, enables voice search. (Voice search may not be
- * provided by the device, in which case these flags will have no effect.) The
- * following mode bits are defined:
- * <table border="2" align="center" frame="hsides" rules="rows">
- * <tbody>
- * <tr><th>showVoiceSearchButton</th>
- * <td>If set, display a voice search button. This only takes effect if voice
- * search is available on the device. If set, then launchWebSearch or
- * launchRecognizer must also be set.</td>
- * </tr>
- * <tr><th>launchWebSearch</th>
- * <td>If set, the voice search button will take the user directly to a
- * built-in voice web search activity. Most applications will not use this
- * flag, as it will take the user away from the activity in which search
- * was invoked.</td>
- * </tr>
- * <tr><th>launchRecognizer</th>
- * <td>If set, the voice search button will take the user directly to a
- * built-in voice recording activity. This activity will prompt the user
- * to speak, transcribe the spoken text, and forward the resulting query
- * text to the searchable activity, just as if the user had typed it into
- * the search UI and clicked the search button.</td>
- * </tr>
- * </tbody>
- * </table></td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:voiceLanguageModel</th>
- * <td>If provided, this specifies the language model that should be used by the voice
- * recognition system.
- * See {@link android.speech.RecognizerIntent#EXTRA_LANGUAGE_MODEL}
- * for more information. If not provided, the default value
- * {@link android.speech.RecognizerIntent#LANGUAGE_MODEL_FREE_FORM} will be used.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:voicePromptText</th>
- * <td>If provided, this specifies a prompt that will be displayed during voice input.
- * (If not provided, a default prompt will be displayed.)</td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:voiceLanguage</th>
- * <td>If provided, this specifies the spoken language to be expected. This is only
- * needed if it is different from the current value of
- * {@link java.util.Locale#getDefault()}.
- * </td>
- * <td align="center">No</td>
- * </tr>
- *
- * <tr><th>android:voiceMaxResults</th>
- * <td>If provided, enforces the maximum number of results to return, including the "best"
- * result which will always be provided as the SEARCH intent's primary query. Must be
- * one or greater. Use {@link android.speech.RecognizerIntent#EXTRA_RESULTS}
- * to get the results from the intent. If not provided, the recognizer will choose
- * how many results to return.</td>
- * <td align="center">No</td>
- * </tr>
- *
- * </tbody>
- * </table>
- *
- * <a name="PassingSearchContext"></a>
- * <h3>Passing Search Context</h3>
- *
- * <p>In order to improve search experience, an application may wish to specify
- * additional data along with the search, such as local history or context. For
- * example, a maps search would be improved by including the current location.
- * In order to simplify the structure of your activities, this can be done using
- * the search manager.
- *
- * <p>Any data can be provided at the time the search is launched, as long as it
- * can be stored in a {@link android.os.Bundle Bundle} object.
- *
- * <p>To pass application data into the Search Manager, you'll need to override
- * {@link android.app.Activity#onSearchRequested onSearchRequested} as follows:
- *
- * <pre class="prettyprint">
- * &#64;Override
- * public boolean onSearchRequested() {
- * Bundle appData = new Bundle();
- * appData.put...();
- * appData.put...();
- * startSearch(null, false, appData, false);
- * return true;
- * }</pre>
- *
- * <p>To receive application data from the Search Manager, you'll extract it from
- * the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
- * {@link android.content.Intent Intent} as follows:
- *
- * <pre class="prettyprint">
- * final Bundle appData = queryIntent.getBundleExtra(SearchManager.APP_DATA);
- * if (appData != null) {
- * appData.get...();
- * appData.get...();
- * }</pre>
- *
- * <a name="ProtectingUserPrivacy"></a>
- * <h3>Protecting User Privacy</h3>
- *
- * <p>Many users consider their activities on the phone, including searches, to be private
- * information. Applications that implement search should take steps to protect users' privacy
- * wherever possible. This section covers two areas of concern, but you should consider your search
- * design carefully and take any additional steps necessary.
- *
- * <p><b>Don't send personal information to servers, and if you do, don't log it.</b>
- * "Personal information" is information that can personally identify your users, such as name,
- * email address or billing information, or other data which can be reasonably linked to such
- * information. If your application implements search with the assistance of a server, try to
- * avoid sending personal information with your searches. For example, if you are searching for
- * businesses near a zip code, you don't need to send the user ID as well - just send the zip code
- * to the server. If you do need to send personal information, you should take steps to avoid
- * logging it. If you must log it, you should protect that data very carefully, and erase it as
- * soon as possible.
- *
- * <p><b>Provide the user with a way to clear their search history.</b> The Search Manager helps
- * your application provide context-specific suggestions. Sometimes these suggestions are based
- * on previous searches, or other actions taken by the user in an earlier session. A user may not
- * wish for previous searches to be revealed to other users, for instance if they share their phone
- * with a friend. If your application provides suggestions that can reveal previous activities,
- * you should implement a "Clear History" menu, preference, or button. If you are using
- * {@link android.provider.SearchRecentSuggestions}, you can simply call its
- * {@link android.provider.SearchRecentSuggestions#clearHistory() clearHistory()} method from
- * your "Clear History" UI. If you are implementing your own form of recent suggestions, you'll
- * need to provide a similar a "clear history" API in your provider, and call it from your
- * "Clear History" UI.
+ * <div class="special">
+ * <p>For a guide to using the search dialog and adding search
+ * suggestions in your application, see the Dev Guide topic about <strong><a
+ * href="{@docRoot}guide/topics/search/index.html">Search</a></strong>.</p>
+ * </div>
*/
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
@@ -1274,16 +109,6 @@ public class SearchManager
public final static String APP_DATA = "app_data";
/**
- * Intent app_data bundle key: Use this key with the bundle from
- * {@link android.content.Intent#getBundleExtra
- * content.Intent.getBundleExtra(APP_DATA)} to obtain the source identifier
- * set by the activity that launched the search.
- *
- * @hide
- */
- public final static String SOURCE = "source";
-
- /**
* Intent extra data key: Use {@link android.content.Intent#getBundleExtra
* content.Intent.getBundleExtra(SEARCH_MODE)} to get the search mode used
* to launch the intent.
@@ -1294,15 +119,6 @@ public class SearchManager
public final static String SEARCH_MODE = "search_mode";
/**
- * Value for the {@link #SEARCH_MODE} key.
- * This is used if the intent was launched by clicking a suggestion in global search
- * mode (Quick Search Box).
- *
- * @hide
- */
- public static final String MODE_GLOBAL_SEARCH_SUGGESTION = "global_search_suggestion";
-
- /**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()}
* to obtain the keycode that the user used to trigger this query. It will be zero if the
@@ -1313,70 +129,25 @@ public class SearchManager
public final static String ACTION_KEY = "action_key";
/**
- * Intent component name key: This key will be used for the extra populated by the
- * {@link #SUGGEST_COLUMN_INTENT_COMPONENT_NAME} column.
- *
- * {@hide}
- */
- public final static String COMPONENT_NAME_KEY = "intent_component_name_key";
-
- /**
* Intent extra data key: This key will be used for the extra populated by the
* {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
*/
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
/**
- * Defines the constants used in the communication between {@link android.app.SearchDialog} and
- * the global search provider via {@link Cursor#respond(android.os.Bundle)}.
- *
- * @hide
+ * Boolean extra data key for {@link #INTENT_ACTION_GLOBAL_SEARCH} intents. If {@code true},
+ * the initial query should be selected when the global search activity is started, so
+ * that the user can easily replace it with another query.
*/
- public static class DialogCursorProtocol {
+ public final static String EXTRA_SELECT_QUERY = "select_query";
- /**
- * The sent bundle will contain this integer key, with a value set to one of the events
- * below.
- */
- public final static String METHOD = "DialogCursorProtocol.method";
-
- /**
- * After data has been refreshed.
- */
- public final static int POST_REFRESH = 0;
- public final static String POST_REFRESH_RECEIVE_ISPENDING
- = "DialogCursorProtocol.POST_REFRESH.isPending";
- public final static String POST_REFRESH_RECEIVE_DISPLAY_NOTIFY
- = "DialogCursorProtocol.POST_REFRESH.displayNotify";
-
- /**
- * When a position has been clicked.
- */
- public final static int CLICK = 2;
- public final static String CLICK_SEND_POSITION
- = "DialogCursorProtocol.CLICK.sendPosition";
- public final static String CLICK_SEND_MAX_DISPLAY_POS
- = "DialogCursorProtocol.CLICK.sendDisplayPosition";
- public final static String CLICK_SEND_ACTION_KEY
- = "DialogCursorProtocol.CLICK.sendActionKey";
- public final static String CLICK_SEND_ACTION_MSG
- = "DialogCursorProtocol.CLICK.sendActionMsg";
- public final static String CLICK_RECEIVE_SELECTED_POS
- = "DialogCursorProtocol.CLICK.receiveSelectedPosition";
-
- /**
- * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed.
- */
- public final static int THRESH_HIT = 3;
-
- /**
- * When a search is started without using a suggestion.
- */
- public final static int SEARCH = 4;
- public final static String SEARCH_SEND_MAX_DISPLAY_POS
- = "DialogCursorProtocol.SEARCH.sendDisplayPosition";
- public final static String SEARCH_SEND_QUERY = "DialogCursorProtocol.SEARCH.query";
- }
+ /**
+ * Boolean extra data key for a suggestion provider to return in {@link Cursor#getExtras} to
+ * indicate that the search is not complete yet. This can be used by the search UI
+ * to indicate that a search is in progress. The suggestion provider can return partial results
+ * this way and send a change notification on the cursor when more results are available.
+ */
+ public final static String CURSOR_EXTRA_KEY_IN_PROGRESS = "in_progress";
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
@@ -1419,40 +190,6 @@ public class SearchManager
public final static String SHORTCUT_MIME_TYPE =
"vnd.android.cursor.item/vnd.android.search.suggest";
-
- /**
- * The authority of the provider to report clicks to when a click is detected after pivoting
- * into a specific app's search from global search.
- *
- * In addition to the columns below, the suggestion columns are used to pass along the full
- * suggestion so it can be shortcutted.
- *
- * @hide
- */
- public final static String SEARCH_CLICK_REPORT_AUTHORITY =
- "com.android.globalsearch.stats";
-
- /**
- * The path the write goes to.
- *
- * @hide
- */
- public final static String SEARCH_CLICK_REPORT_URI_PATH = "click";
-
- /**
- * The column storing the query for the click.
- *
- * @hide
- */
- public final static String SEARCH_CLICK_REPORT_COLUMN_QUERY = "query";
-
- /**
- * The column storing the component name of the application that was pivoted into.
- *
- * @hide
- */
- public final static String SEARCH_CLICK_REPORT_COLUMN_COMPONENT = "component";
-
/**
* Column name for suggestions cursor. <i>Unused - can be null or column can be omitted.</i>
*/
@@ -1468,6 +205,16 @@ public class SearchManager
* a much smaller appearance.
*/
public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2";
+
+ /**
+ * Column name for suggestions cursor. <i>Optional.</i> This is a URL that will be shown
+ * as the second line of text instead of {@link #SUGGEST_COLUMN_TEXT_2}. This is a separate
+ * column so that the search UI knows to display the text as a URL, e.g. by using a different
+ * color. If this column is absent, or has the value {@code null},
+ * {@link #SUGGEST_COLUMN_TEXT_2} will be used instead.
+ */
+ public final static String SUGGEST_COLUMN_TEXT_2_URL = "suggest_text_2_url";
+
/**
* Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
* then all suggestions will be provided in a format that includes space for two small icons,
@@ -1529,10 +276,7 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
/**
- * Column name for suggestions cursor. <i>Optional.</i> This column allows suggestions
- * to provide additional arbitrary data which will be included as an extra under the key
- * {@link #COMPONENT_NAME_KEY}. For use by the global search system only - if other providers
- * attempt to use this column, the value will be overwritten by global search.
+ * TODO: Remove
*
* @hide
*/
@@ -1592,31 +336,12 @@ public class SearchManager
public final static String SUGGEST_PARAMETER_LIMIT = "limit";
/**
- * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
- * the search dialog will switch to a different suggestion source when the
- * suggestion is clicked.
- *
- * {@link #SUGGEST_COLUMN_INTENT_DATA} must contain
- * the flattened {@link ComponentName} of the activity which is to be searched.
- *
- * TODO: Should {@link #SUGGEST_COLUMN_INTENT_DATA} instead contain a URI in the format
- * used by {@link android.provider.Applications}?
- *
- * TODO: This intent should be protected by the same permission that we use
- * for replacing the global search provider.
- *
- * The query text field will be set to the value of {@link #SUGGEST_COLUMN_QUERY}.
- *
- * @hide Pending API council approval.
- */
- public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE
- = "android.search.action.CHANGE_SEARCH_SOURCE";
-
- /**
- * Intent action for finding the global search activity.
+ * Intent action for starting the global search activity.
* The global search provider should handle this intent.
- *
- * @hide Pending API council approval.
+ *
+ * Supported extra data keys: {@link #QUERY},
+ * {@link #EXTRA_SELECT_QUERY},
+ * {@link #APP_DATA}.
*/
public final static String INTENT_ACTION_GLOBAL_SEARCH
= "android.search.action.GLOBAL_SEARCH";
@@ -1624,8 +349,6 @@ public class SearchManager
/**
* Intent action for starting the global search settings activity.
* The global search provider should handle this intent.
- *
- * @hide Pending API council approval.
*/
public final static String INTENT_ACTION_SEARCH_SETTINGS
= "android.search.action.SEARCH_SETTINGS";
@@ -1661,7 +384,16 @@ public class SearchManager
* @hide
*/
public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
-
+
+ /**
+ * This means that context is voice, and therefore the SearchDialog should
+ * continue showing the microphone until the user indicates that he/she does
+ * not want to re-speak (e.g. by typing).
+ *
+ * @hide
+ */
+ public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE";
+
/**
* Reference to the shared system search service.
*/
@@ -1670,13 +402,6 @@ public class SearchManager
private final Context mContext;
/**
- * compact representation of the activity associated with this search manager so
- * we can say who we are when starting search. the search managerservice, in turn,
- * uses this to properly handle the back stack.
- */
- private int mIdent;
-
- /**
* The package associated with this seach manager.
*/
private String mAssociatedPackage;
@@ -1686,7 +411,7 @@ public class SearchManager
/* package */ OnDismissListener mDismissListener = null;
/* package */ OnCancelListener mCancelListener = null;
- private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
+ private SearchDialog mSearchDialog;
/*package*/ SearchManager(Context context, Handler handler) {
mContext = context;
@@ -1694,21 +419,6 @@ public class SearchManager
mService = ISearchManager.Stub.asInterface(
ServiceManager.getService(Context.SEARCH_SERVICE));
}
-
- /*package*/ boolean hasIdent() {
- return mIdent != 0;
- }
-
- /*package*/ void setIdent(int ident, ComponentName component) {
- if (mIdent != 0) {
- throw new IllegalStateException("mIdent already set");
- }
- if (component == null) {
- throw new IllegalArgumentException("component must be non-null");
- }
- mIdent = ident;
- mAssociatedPackage = component.getPackageName();
- }
/**
* Launch search UI.
@@ -1755,18 +465,91 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
- if (mIdent == 0) throw new IllegalArgumentException(
- "Called from outside of an Activity context");
- if (!globalSearch && !mAssociatedPackage.equals(launchActivity.getPackageName())) {
- Log.w(TAG, "invoking app search on a different package " +
- "not associated with this search manager");
+ if (globalSearch) {
+ startGlobalSearch(initialQuery, selectInitialQuery, appSearchData);
+ return;
+ }
+
+ ensureSearchDialog();
+
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData);
+ }
+
+ private void ensureSearchDialog() {
+ if (mSearchDialog == null) {
+ mSearchDialog = new SearchDialog(mContext, this);
+ mSearchDialog.setOnCancelListener(this);
+ mSearchDialog.setOnDismissListener(this);
+ }
+ }
+
+ /**
+ * Starts the global search activity.
+ */
+ /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
+ Bundle appSearchData) {
+ ComponentName globalSearchActivity = getGlobalSearchActivity();
+ if (globalSearchActivity == null) {
+ Log.w(TAG, "No global search activity found.");
+ return;
+ }
+ Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(globalSearchActivity);
+ // Make sure that we have a Bundle to put source in
+ if (appSearchData == null) {
+ appSearchData = new Bundle();
+ } else {
+ appSearchData = new Bundle(appSearchData);
+ }
+ // Set source to package name of app that starts global search, if not set already.
+ if (!appSearchData.containsKey("source")) {
+ appSearchData.putString("source", mContext.getPackageName());
+ }
+ intent.putExtra(APP_DATA, appSearchData);
+ if (!TextUtils.isEmpty(initialQuery)) {
+ intent.putExtra(QUERY, initialQuery);
+ }
+ if (selectInitialQuery) {
+ intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
}
try {
- // activate the search manager and start it up!
- mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch, mSearchManagerCallback, mIdent);
+ if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
+ }
+ }
+
+ /**
+ * Gets the name of the global search activity.
+ *
+ * @hide
+ */
+ public ComponentName getGlobalSearchActivity() {
+ try {
+ return mService.getGlobalSearchActivity();
} catch (RemoteException ex) {
- Log.e(TAG, "startSearch() failed.", ex);
+ Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the name of the web search activity.
+ *
+ * @return The name of the default activity for web searches. This activity
+ * can be used to get web search suggestions. Returns {@code null} if
+ * there is no default web search activity.
+ *
+ * @hide
+ */
+ public ComponentName getWebSearchActivity() {
+ try {
+ return mService.getWebSearchActivity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getWebSearchActivity() failed: " + ex);
+ return null;
}
}
@@ -1786,8 +569,6 @@ public class SearchManager
public void triggerSearch(String query,
ComponentName launchActivity,
Bundle appSearchData) {
- if (mIdent == 0) throw new IllegalArgumentException(
- "Called from outside of an Activity context");
if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
throw new IllegalArgumentException("invoking app search on a different package " +
"not associated with this search manager");
@@ -1796,12 +577,8 @@ public class SearchManager
Log.w(TAG, "triggerSearch called with empty query, ignoring.");
return;
}
- try {
- mService.triggerSearch(query, launchActivity, appSearchData, mSearchManagerCallback,
- mIdent);
- } catch (RemoteException ex) {
- Log.e(TAG, "triggerSearch() failed.", ex);
- }
+ startSearch(query, false, launchActivity, appSearchData, false);
+ mSearchDialog.launchQuerySearch();
}
/**
@@ -1816,10 +593,8 @@ public class SearchManager
* @see #startSearch
*/
public void stopSearch() {
- if (DBG) debug("stopSearch()");
- try {
- mService.stopSearch();
- } catch (RemoteException ex) {
+ if (mSearchDialog != null) {
+ mSearchDialog.cancel();
}
}
@@ -1833,13 +608,7 @@ public class SearchManager
* @hide
*/
public boolean isVisible() {
- if (DBG) debug("isVisible()");
- try {
- return mService.isVisible();
- } catch (RemoteException e) {
- Log.e(TAG, "isVisible() failed: " + e);
- return false;
- }
+ return mSearchDialog == null? false : mSearchDialog.isShowing();
}
/**
@@ -1886,44 +655,14 @@ public class SearchManager
mCancelListener = listener;
}
- private class SearchManagerCallback extends ISearchManagerCallback.Stub {
-
- private final Runnable mFireOnDismiss = new Runnable() {
- public void run() {
- if (DBG) debug("mFireOnDismiss");
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
- }
- };
-
- private final Runnable mFireOnCancel = new Runnable() {
- public void run() {
- if (DBG) debug("mFireOnCancel");
- if (mCancelListener != null) {
- mCancelListener.onCancel();
- }
- }
- };
-
- public void onDismiss() {
- if (DBG) debug("onDismiss()");
- mHandler.post(mFireOnDismiss);
- }
-
- public void onCancel() {
- if (DBG) debug("onCancel()");
- mHandler.post(mFireOnCancel);
- }
-
- }
-
/**
* @deprecated This method is an obsolete internal implementation detail. Do not use.
*/
@Deprecated
public void onCancel(DialogInterface dialog) {
- throw new UnsupportedOperationException();
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
}
/**
@@ -1931,40 +670,26 @@ public class SearchManager
*/
@Deprecated
public void onDismiss(DialogInterface dialog) {
- throw new UnsupportedOperationException();
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss();
+ }
}
/**
- * Gets information about a searchable activity. This method is static so that it can
- * be used from non-Activity contexts.
+ * Gets information about a searchable activity.
*
* @param componentName The activity to get searchable information for.
- * @param globalSearch If <code>false</code>, return information about the given activity.
- * If <code>true</code>, return information about the global search activity.
- * @return Searchable information, or <code>null</code> if the activity is not searchable.
- *
- * @hide because SearchableInfo is not part of the API.
+ * @return Searchable information, or <code>null</code> if the activity does not
+ * exist, or is not searchable.
*/
- public SearchableInfo getSearchableInfo(ComponentName componentName,
- boolean globalSearch) {
+ public SearchableInfo getSearchableInfo(ComponentName componentName) {
try {
- return mService.getSearchableInfo(componentName, globalSearch);
+ return mService.getSearchableInfo(componentName);
} catch (RemoteException ex) {
Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
-
- /**
- * Checks whether the given searchable is the default searchable.
- *
- * @hide because SearchableInfo is not part of the API.
- */
- public boolean isDefaultSearchable(SearchableInfo searchable) {
- SearchableInfo defaultSearchable = getSearchableInfo(null, true);
- return defaultSearchable != null
- && defaultSearchable.getSearchActivity().equals(searchable.getSearchActivity());
- }
/**
* Gets a cursor with search suggestions.
@@ -2039,10 +764,8 @@ public class SearchManager
* Returns a list of the searchable activities that can be included in global search.
*
* @return a list containing searchable information for all searchable activities
- * that have the <code>exported</code> attribute set in their searchable
- * meta-data.
- *
- * @hide because SearchableInfo is not part of the API.
+ * that have the <code>android:includeInGlobalSearch</code> attribute set
+ * in their searchable meta-data.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
try {
@@ -2053,56 +776,4 @@ public class SearchManager
}
}
- /**
- * Returns a list of the searchable activities that handle web searches.
- *
- * @return a list of all searchable activities that handle
- * {@link android.content.Intent#ACTION_WEB_SEARCH}.
- *
- * @hide because SearchableInfo is not part of the API.
- */
- public List<SearchableInfo> getSearchablesForWebSearch() {
- try {
- return mService.getSearchablesForWebSearch();
- } catch (RemoteException e) {
- Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
- return null;
- }
- }
-
- /**
- * Returns the default searchable activity for web searches.
- *
- * @return searchable information for the activity handling web searches by default.
- *
- * @hide because SearchableInfo is not part of the API.
- */
- public SearchableInfo getDefaultSearchableForWebSearch() {
- try {
- return mService.getDefaultSearchableForWebSearch();
- } catch (RemoteException e) {
- Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
- return null;
- }
- }
-
- /**
- * Sets the default searchable activity for web searches.
- *
- * @param component Name of the component to set as default activity for web searches.
- *
- * @hide
- */
- public void setDefaultWebSearch(ComponentName component) {
- try {
- mService.setDefaultWebSearch(component);
- } catch (RemoteException e) {
- Log.e(TAG, "setDefaultWebSearch() failed: " + e);
- }
- }
-
- private static void debug(String msg) {
- Thread thread = Thread.currentThread();
- Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
- }
-} \ No newline at end of file
+}
diff --git a/core/java/android/server/search/SearchableInfo.aidl b/core/java/android/app/SearchableInfo.aidl
index 9576c2b..146b373 100644
--- a/core/java/android/server/search/SearchableInfo.aidl
+++ b/core/java/android/app/SearchableInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.server.search;
+package android.app;
parcelable SearchableInfo;
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index 69ef98c..5482f60 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.server.search;
+package android.app;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -37,6 +37,15 @@ import android.view.inputmethod.EditorInfo;
import java.io.IOException;
import java.util.HashMap;
+/**
+ * Searchability meta-data for an activity. Only applications that search other applications
+ * should need to use this class.
+ * See <a href="{@docRoot}guide/topics/search/searchable-config.html">Searchable Configuration</a>
+ * for more information about declaring searchability meta-data for your application.
+ *
+ * @see SearchManager#getSearchableInfo(ComponentName)
+ * @see SearchManager#getSearchablesInGlobalSearch()
+ */
public final class SearchableInfo implements Parcelable {
// general debugging support
@@ -49,13 +58,13 @@ public final class SearchableInfo implements Parcelable {
private static final String MD_LABEL_SEARCHABLE = "android.app.searchable";
private static final String MD_XML_ELEMENT_SEARCHABLE = "searchable";
private static final String MD_XML_ELEMENT_SEARCHABLE_ACTION_KEY = "actionkey";
-
+
// flags in the searchMode attribute
private static final int SEARCH_MODE_BADGE_LABEL = 0x04;
private static final int SEARCH_MODE_BADGE_ICON = 0x08;
private static final int SEARCH_MODE_QUERY_REWRITE_FROM_DATA = 0x10;
private static final int SEARCH_MODE_QUERY_REWRITE_FROM_TEXT = 0x20;
-
+
// true member variables - what we know about the searchability
private final int mLabelId;
private final ComponentName mSearchActivity;
@@ -68,7 +77,7 @@ public final class SearchableInfo implements Parcelable {
private final boolean mIncludeInGlobalSearch;
private final boolean mQueryAfterZeroResults;
private final boolean mAutoUrlDetect;
- private final String mSettingsDescription;
+ private final int mSettingsDescriptionId;
private final String mSuggestAuthority;
private final String mSuggestPath;
private final String mSuggestSelection;
@@ -81,22 +90,22 @@ public final class SearchableInfo implements Parcelable {
// This is not final, to allow lazy initialization.
private HashMap<Integer,ActionKeyInfo> mActionKeys = null;
private final String mSuggestProviderPackage;
-
+
// Flag values for Searchable_voiceSearchMode
- private static int VOICE_SEARCH_SHOW_BUTTON = 1;
- private static int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
- private static int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
+ private static final int VOICE_SEARCH_SHOW_BUTTON = 1;
+ private static final int VOICE_SEARCH_LAUNCH_WEB_SEARCH = 2;
+ private static final int VOICE_SEARCH_LAUNCH_RECOGNIZER = 4;
private final int mVoiceSearchMode;
private final int mVoiceLanguageModeId; // voiceLanguageModel
private final int mVoicePromptTextId; // voicePromptText
private final int mVoiceLanguageId; // voiceLanguage
private final int mVoiceMaxResults; // voiceMaxResults
-
/**
- * Retrieve the authority for obtaining search suggestions.
- *
- * @return Returns a string containing the suggestions authority.
+ * Gets the search suggestion content provider authority.
+ *
+ * @return The search suggestions authority, or {@code null} if not set.
+ * @see android.R.styleable#Searchable_searchSuggestAuthority
*/
public String getSuggestAuthority() {
return mSuggestAuthority;
@@ -112,6 +121,8 @@ public final class SearchableInfo implements Parcelable {
/**
* Gets the component name of the searchable activity.
+ *
+ * @return A component name, never {@code null}.
*/
public ComponentName getSearchActivity() {
return mSearchActivity;
@@ -119,6 +130,10 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the badge should be a text label.
+ *
+ * @see android.R.styleable#Searchable_searchMode
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeLabel() {
return 0 != (mSearchMode & SEARCH_MODE_BADGE_LABEL);
@@ -126,6 +141,10 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the badge should be an icon.
+ *
+ * @see android.R.styleable#Searchable_searchMode
+ *
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public boolean useBadgeIcon() {
return (0 != (mSearchMode & SEARCH_MODE_BADGE_ICON)) && (mIconId != 0);
@@ -133,6 +152,8 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the text in the query field should come from the suggestion intent data.
+ *
+ * @see android.R.styleable#Searchable_searchMode
*/
public boolean shouldRewriteQueryFromData() {
return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_DATA);
@@ -140,87 +161,95 @@ public final class SearchableInfo implements Parcelable {
/**
* Checks whether the text in the query field should come from the suggestion title.
+ *
+ * @see android.R.styleable#Searchable_searchMode
*/
public boolean shouldRewriteQueryFromText() {
return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT);
}
-
+
/**
- * Gets the description to use for this source in system search settings, or null if
- * none has been specified.
+ * Gets the resource id of the description string to use for this source in system search
+ * settings, or {@code 0} if none has been specified.
+ *
+ * @see android.R.styleable#Searchable_searchSettingsDescription
*/
- public String getSettingsDescription() {
- return mSettingsDescription;
+ public int getSettingsDescriptionId() {
+ return mSettingsDescriptionId;
}
/**
- * Retrieve the path for obtaining search suggestions.
+ * Gets the content provider path for obtaining search suggestions.
*
- * @return Returns a string containing the suggestions path, or null if not provided.
+ * @return The suggestion path, or {@code null} if not set.
+ * @see android.R.styleable#Searchable_searchSuggestPath
*/
public String getSuggestPath() {
return mSuggestPath;
}
-
+
/**
- * Retrieve the selection pattern for obtaining search suggestions. This must
- * include a single ? which will be used for the user-typed characters.
- *
- * @return Returns a string containing the suggestions authority.
+ * Gets the selection for obtaining search suggestions.
+ *
+ * @see android.R.styleable#Searchable_searchSuggestSelection
*/
public String getSuggestSelection() {
return mSuggestSelection;
}
-
+
/**
- * Retrieve the (optional) intent action for use with these suggestions. This is
- * useful if all intents will have the same action (e.g. "android.intent.action.VIEW").
- *
- * Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_ACTION column.
- *
- * @return Returns a string containing the default intent action.
+ * Gets the optional intent action for use with these suggestions. This is
+ * useful if all intents will have the same action
+ * (e.g. {@link android.content.Intent#ACTION_VIEW})
+ *
+ * This can be overriden in any given suggestion using the column
+ * {@link SearchManager#SUGGEST_COLUMN_INTENT_ACTION}.
+ *
+ * @return The default intent action, or {@code null} if not set.
+ * @see android.R.styleable#Searchable_searchSuggestIntentAction
*/
public String getSuggestIntentAction() {
return mSuggestIntentAction;
}
-
+
/**
- * Retrieve the (optional) intent data for use with these suggestions. This is
- * useful if all intents will have similar data URIs (e.g. "android.intent.action.VIEW"),
+ * Gets the optional intent data for use with these suggestions. This is
+ * useful if all intents will have similar data URIs,
* but you'll likely need to provide a specific ID as well via the column
- * AUTOSUGGEST_COLUMN_INTENT_DATA_ID, which will be appended to the intent data URI.
+ * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA_ID}, which will be appended to the
+ * intent data URI.
*
- * Can be overriden in any given suggestion via the AUTOSUGGEST_COLUMN_INTENT_DATA column.
+ * This can be overriden in any given suggestion using the column
+ * {@link SearchManager#SUGGEST_COLUMN_INTENT_DATA}.
*
- * @return Returns a string containing the default intent data.
+ * @return The default intent data, or {@code null} if not set.
+ * @see android.R.styleable#Searchable_searchSuggestIntentData
*/
public String getSuggestIntentData() {
return mSuggestIntentData;
}
-
+
/**
- * Gets the suggestion threshold for use with these suggestions.
+ * Gets the suggestion threshold.
*
- * @return The value of the <code>searchSuggestThreshold</code> attribute,
- * or 0 if the attribute is not set.
+ * @return The suggestion threshold, or {@code 0} if not set.
+ * @see android.R.styleable#Searchable_searchSuggestThreshold
*/
public int getSuggestThreshold() {
return mSuggestThreshold;
}
-
+
/**
- * Get the context for the searchable activity.
- *
- * This is fairly expensive so do it on the original scan, or when an app is
- * selected, but don't hang on to the result forever.
- *
+ * Get the context for the searchable activity.
+ *
* @param context You need to supply a context to start with
* @return Returns a context related to the searchable activity
+ * @hide
*/
public Context getActivityContext(Context context) {
return createActivityContext(context, mSearchActivity);
}
-
+
/**
* Creates a context for another activity.
*/
@@ -229,42 +258,40 @@ public final class SearchableInfo implements Parcelable {
try {
theirContext = context.createPackageContext(activity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
- // unexpected, but we deal with this by null-checking theirContext
+ Log.e(LOG_TAG, "Package not found " + activity.getPackageName());
} catch (java.lang.SecurityException e) {
- // unexpected, but we deal with this by null-checking theirContext
+ Log.e(LOG_TAG, "Can't make context for " + activity.getPackageName(), e);
}
return theirContext;
}
-
+
/**
- * Get the context for the suggestions provider.
- *
- * This is fairly expensive so do it on the original scan, or when an app is
- * selected, but don't hang on to the result forever.
- *
+ * Get the context for the suggestions provider.
+ *
* @param context You need to supply a context to start with
* @param activityContext If we can determine that the provider and the activity are the
- * same, we'll just return this one.
- * @return Returns a context related to the context provider
+ * same, we'll just return this one.
+ * @return Returns a context related to the suggestion provider
+ * @hide
*/
public Context getProviderContext(Context context, Context activityContext) {
Context theirContext = null;
if (mSearchActivity.getPackageName().equals(mSuggestProviderPackage)) {
return activityContext;
}
- if (mSuggestProviderPackage != null)
- try {
- theirContext = context.createPackageContext(mSuggestProviderPackage, 0);
- } catch (PackageManager.NameNotFoundException e) {
- // unexpected, but we deal with this by null-checking theirContext
- } catch (java.lang.SecurityException e) {
- // unexpected, but we deal with this by null-checking theirContext
+ if (mSuggestProviderPackage != null) {
+ try {
+ theirContext = context.createPackageContext(mSuggestProviderPackage, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // unexpected, but we deal with this by null-checking theirContext
+ } catch (java.lang.SecurityException e) {
+ // unexpected, but we deal with this by null-checking theirContext
+ }
}
-
return theirContext;
}
-
+
/**
* Constructor
*
@@ -292,7 +319,7 @@ public final class SearchableInfo implements Parcelable {
InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_NORMAL);
mSearchImeOptions = a.getInt(com.android.internal.R.styleable.Searchable_imeOptions,
- EditorInfo.IME_ACTION_SEARCH);
+ EditorInfo.IME_ACTION_GO);
mIncludeInGlobalSearch = a.getBoolean(
com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false);
mQueryAfterZeroResults = a.getBoolean(
@@ -300,8 +327,8 @@ public final class SearchableInfo implements Parcelable {
mAutoUrlDetect = a.getBoolean(
com.android.internal.R.styleable.Searchable_autoUrlDetect, false);
- mSettingsDescription = a.getString(
- com.android.internal.R.styleable.Searchable_searchSettingsDescription);
+ mSettingsDescriptionId = a.getResourceId(
+ com.android.internal.R.styleable.Searchable_searchSettingsDescription, 0);
mSuggestAuthority = a.getString(
com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
mSuggestPath = a.getString(
@@ -345,17 +372,22 @@ public final class SearchableInfo implements Parcelable {
throw new IllegalArgumentException("Search label must be a resource reference.");
}
}
-
+
/**
- * Private class used to hold the "action key" configuration
+ * Information about an action key in searchability meta-data.
+ *
+ * @see SearchableInfo#findActionKey(int)
+ *
+ * @hide This feature is used very little, and on many devices there are no reasonable
+ * keys to use for actions.
*/
public static class ActionKeyInfo implements Parcelable {
-
+
private final int mKeyCode;
private final String mQueryActionMsg;
private final String mSuggestActionMsg;
private final String mSuggestActionMsgColumn;
-
+
/**
* Create one object using attributeset as input data.
* @param activityContext runtime context of the activity that the action key information
@@ -364,7 +396,7 @@ public final class SearchableInfo implements Parcelable {
* construct the object.
* @throws IllegalArgumentException if the action key configuration is invalid
*/
- public ActionKeyInfo(Context activityContext, AttributeSet attr) {
+ ActionKeyInfo(Context activityContext, AttributeSet attr) {
TypedArray a = activityContext.obtainStyledAttributes(attr,
com.android.internal.R.styleable.SearchableActionKey);
@@ -395,25 +427,41 @@ public final class SearchableInfo implements Parcelable {
* @param in The Parcel containing the previously written ActionKeyInfo,
* positioned at the location in the buffer where it was written.
*/
- public ActionKeyInfo(Parcel in) {
+ private ActionKeyInfo(Parcel in) {
mKeyCode = in.readInt();
mQueryActionMsg = in.readString();
mSuggestActionMsg = in.readString();
mSuggestActionMsgColumn = in.readString();
}
+ /**
+ * Gets the key code that this action key info is for.
+ * @see android.R.styleable#SearchableActionKey_keycode
+ */
public int getKeyCode() {
return mKeyCode;
}
+ /**
+ * Gets the action message to use for queries.
+ * @see android.R.styleable#SearchableActionKey_queryActionMsg
+ */
public String getQueryActionMsg() {
return mQueryActionMsg;
}
+ /**
+ * Gets the action message to use for suggestions.
+ * @see android.R.styleable#SearchableActionKey_suggestActionMsg
+ */
public String getSuggestActionMsg() {
return mSuggestActionMsg;
}
+ /**
+ * Gets the name of the column to get the suggestion action message from.
+ * @see android.R.styleable#SearchableActionKey_suggestActionMsgColumn
+ */
public String getSuggestActionMsgColumn() {
return mSuggestActionMsgColumn;
}
@@ -429,12 +477,14 @@ public final class SearchableInfo implements Parcelable {
dest.writeString(mSuggestActionMsgColumn);
}
}
-
+
/**
* If any action keys were defined for this searchable activity, look up and return.
*
* @param keyCode The key that was pressed
- * @return Returns the ActionKeyInfo record, or null if none defined
+ * @return Returns the action key info, or {@code null} if none defined.
+ *
+ * @hide ActionKeyInfo is hidden
*/
public ActionKeyInfo findActionKey(int keyCode) {
if (mActionKeys == null) {
@@ -457,6 +507,8 @@ public final class SearchableInfo implements Parcelable {
* @param activityInfo Activity to get search information from.
* @return Search information about the given activity, or {@code null} if
* the activity has no or invalid searchability meta-data.
+ *
+ * @hide For use by SearchManagerService.
*/
public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) {
// for each component, try to find metadata
@@ -478,7 +530,7 @@ public final class SearchableInfo implements Parcelable {
+ ",suggestAuthority=" + searchable.getSuggestAuthority()
+ ",target=" + searchable.getSearchActivity().getClassName()
+ ",global=" + searchable.shouldIncludeInGlobalSearch()
- + ",settingsDescription=" + searchable.getSettingsDescription()
+ + ",settingsDescription=" + searchable.getSettingsDescriptionId()
+ ",threshold=" + searchable.getSuggestThreshold());
} else {
Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
@@ -486,7 +538,7 @@ public final class SearchableInfo implements Parcelable {
}
return searchable;
}
-
+
/**
* Get the metadata for a given activity
*
@@ -500,7 +552,8 @@ public final class SearchableInfo implements Parcelable {
final ComponentName cName) {
SearchableInfo result = null;
Context activityContext = createActivityContext(context, cName);
-
+ if (activityContext == null) return null;
+
// in order to use the attributes mechanism, we have to walk the parser
// forward through the file until it's reading the tag of interest.
try {
@@ -549,147 +602,170 @@ public final class SearchableInfo implements Parcelable {
}
/**
- * Return the "label" (user-visible name) of this searchable context. This must be
- * accessed using the target (searchable) Activity's resources, not simply the context of the
- * caller.
+ * Gets the "label" (user-visible name) of this searchable context. This must be
+ * read using the searchable Activity's resources.
*
- * @return Returns the resource Id
+ * @return A resource id, or {@code 0} if no label was specified.
+ * @see android.R.styleable#Searchable_label
+ *
+ * @hide deprecated functionality
*/
public int getLabelId() {
return mLabelId;
}
-
+
/**
- * Return the resource Id of the hint text. This must be
- * accessed using the target (searchable) Activity's resources, not simply the context of the
- * caller.
+ * Gets the resource id of the hint text. This must be
+ * read using the searchable Activity's resources.
*
- * @return Returns the resource Id, or 0 if not specified by this package.
+ * @return A resource id, or {@code 0} if no hint was specified.
+ * @see android.R.styleable#Searchable_hint
*/
public int getHintId() {
return mHintId;
}
-
+
/**
- * Return the icon Id specified by the Searchable_icon meta-data entry. This must be
- * accessed using the target (searchable) Activity's resources, not simply the context of the
- * caller.
+ * Gets the icon id specified by the Searchable_icon meta-data entry. This must be
+ * read using the searchable Activity's resources.
*
- * @return Returns the resource id.
+ * @return A resource id, or {@code 0} if no icon was specified.
+ * @see android.R.styleable#Searchable_icon
+ *
+ * @hide deprecated functionality
*/
public int getIconId() {
return mIconId;
}
-
+
/**
- * @return true if android:voiceSearchMode="showVoiceSearchButton"
+ * Checks if the searchable activity wants the voice search button to be shown.
+ *
+ * @see android.R.styleable#Searchable_voiceSearchMode
*/
public boolean getVoiceSearchEnabled() {
return 0 != (mVoiceSearchMode & VOICE_SEARCH_SHOW_BUTTON);
}
-
+
/**
- * @return true if android:voiceSearchMode="launchWebSearch"
+ * Checks if voice search should start web search.
+ *
+ * @see android.R.styleable#Searchable_voiceSearchMode
*/
public boolean getVoiceSearchLaunchWebSearch() {
return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_WEB_SEARCH);
}
-
+
/**
- * @return true if android:voiceSearchMode="launchRecognizer"
+ * Checks if voice search should start in-app search.
+ *
+ * @see android.R.styleable#Searchable_voiceSearchMode
*/
public boolean getVoiceSearchLaunchRecognizer() {
return 0 != (mVoiceSearchMode & VOICE_SEARCH_LAUNCH_RECOGNIZER);
}
-
+
/**
- * @return the resource Id of the language model string, if specified in the searchable
- * activity's metadata, or 0 if not specified.
+ * Gets the resource id of the voice search language model string.
+ *
+ * @return A resource id, or {@code 0} if no language model was specified.
+ * @see android.R.styleable#Searchable_voiceLanguageModel
*/
public int getVoiceLanguageModeId() {
return mVoiceLanguageModeId;
}
-
+
/**
- * @return the resource Id of the voice prompt text string, if specified in the searchable
- * activity's metadata, or 0 if not specified.
+ * Gets the resource id of the voice prompt text string.
+ *
+ * @return A resource id, or {@code 0} if no voice prompt text was specified.
+ * @see android.R.styleable#Searchable_voicePromptText
*/
public int getVoicePromptTextId() {
return mVoicePromptTextId;
}
-
+
/**
- * @return the resource Id of the spoken langauge, if specified in the searchable
- * activity's metadata, or 0 if not specified.
+ * Gets the resource id of the spoken language to recognize in voice search.
+ *
+ * @return A resource id, or {@code 0} if no language was specified.
+ * @see android.R.styleable#Searchable_voiceLanguage
*/
public int getVoiceLanguageId() {
return mVoiceLanguageId;
}
-
+
/**
+ * The maximum number of voice recognition results to return.
+ *
* @return the max results count, if specified in the searchable
- * activity's metadata, or 0 if not specified.
+ * activity's metadata, or {@code 0} if not specified.
+ * @see android.R.styleable#Searchable_voiceMaxResults
*/
public int getVoiceMaxResults() {
return mVoiceMaxResults;
}
-
+
/**
- * Return the resource Id of replacement text for the "Search" button.
- *
- * @return Returns the resource Id, or 0 if not specified by this package.
+ * Gets the resource id of replacement text for the "Search" button.
+ *
+ * @return A resource id, or {@code 0} if no replacement text was specified.
+ * @see android.R.styleable#Searchable_searchButtonText
+ * @hide This feature is deprecated, no need to add it to the API.
*/
public int getSearchButtonText() {
return mSearchButtonText;
}
-
+
/**
- * Return the input type as specified in the searchable attributes. This will default to
- * InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input).
+ * Gets the input type as specified in the searchable attributes. This will default to
+ * {@link InputType#TYPE_CLASS_TEXT} if not specified (which is appropriate
+ * for free text input).
*
* @return the input type
+ * @see android.R.styleable#Searchable_inputType
*/
public int getInputType() {
return mSearchInputType;
}
-
+
/**
- * Return the input method options specified in the searchable attributes.
- * This will default to EditorInfo.ACTION_SEARCH if not specified (which is
+ * Gets the input method options specified in the searchable attributes.
+ * This will default to {@link EditorInfo#IME_ACTION_GO} if not specified (which is
* appropriate for a search box).
*
* @return the input type
+ * @see android.R.styleable#Searchable_imeOptions
*/
public int getImeOptions() {
return mSearchImeOptions;
}
-
+
/**
- * Checks whether the searchable is exported.
+ * Checks whether the searchable should be included in global search.
*
- * @return The value of the <code>exported</code> attribute,
- * or <code>false</code> if the attribute is not set.
+ * @return The value of the {@link android.R.styleable#Searchable_includeInGlobalSearch}
+ * attribute, or {@code false} if the attribute is not set.
+ * @see android.R.styleable#Searchable_includeInGlobalSearch
*/
public boolean shouldIncludeInGlobalSearch() {
return mIncludeInGlobalSearch;
}
/**
- * Checks whether this searchable activity should be invoked after a query returned zero
- * results.
+ * Checks whether this searchable activity should be queried for suggestions if a prefix
+ * of the query has returned no results.
*
- * @return The value of the <code>queryAfterZeroResults</code> attribute,
- * or <code>false</code> if the attribute is not set.
+ * @see android.R.styleable#Searchable_queryAfterZeroResults
*/
public boolean queryAfterZeroResults() {
return mQueryAfterZeroResults;
}
/**
- * Checks whether this searchable activity has auto URL detect turned on.
+ * Checks whether this searchable activity has auto URL detection turned on.
*
- * @return The value of the <code>autoUrlDetect</code> attribute,
- * or <code>false</code> if the attribute is not set.
+ * @see android.R.styleable#Searchable_autoUrlDetect
*/
public boolean autoUrlDetect() {
return mAutoUrlDetect;
@@ -710,13 +786,13 @@ public final class SearchableInfo implements Parcelable {
};
/**
- * Instantiate a new SearchableInfo from the data in a Parcel that was
+ * Instantiates a new SearchableInfo from the data in a Parcel that was
* previously written with {@link #writeToParcel(Parcel, int)}.
*
* @param in The Parcel containing the previously written SearchableInfo,
* positioned at the location in the buffer where it was written.
*/
- public SearchableInfo(Parcel in) {
+ SearchableInfo(Parcel in) {
mLabelId = in.readInt();
mSearchActivity = ComponentName.readFromParcel(in);
mHintId = in.readInt();
@@ -729,7 +805,7 @@ public final class SearchableInfo implements Parcelable {
mQueryAfterZeroResults = in.readInt() != 0;
mAutoUrlDetect = in.readInt() != 0;
- mSettingsDescription = in.readString();
+ mSettingsDescriptionId = in.readInt();
mSuggestAuthority = in.readString();
mSuggestPath = in.readString();
mSuggestSelection = in.readString();
@@ -767,7 +843,7 @@ public final class SearchableInfo implements Parcelable {
dest.writeInt(mQueryAfterZeroResults ? 1 : 0);
dest.writeInt(mAutoUrlDetect ? 1 : 0);
- dest.writeString(mSettingsDescription);
+ dest.writeInt(mSettingsDescriptionId);
dest.writeString(mSuggestAuthority);
dest.writeString(mSuggestPath);
dest.writeString(mSuggestSelection);
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 30e1712..697a987 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -31,8 +31,9 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
- * A Service is an application component that runs in the background, not
- * interacting with the user, for an indefinite period of time. Each service
+ * A Service is an application component representing either an application's desire
+ * to perform a longer-running operation while not interacting with the user
+ * or to supply functionality for other applications to use. Each service
* class must have a corresponding
* {@link android.R.styleable#AndroidManifestService &lt;service&gt;}
* declaration in its package's <code>AndroidManifest.xml</code>. Services
@@ -46,18 +47,65 @@ import java.io.PrintWriter;
* networking) operations, it should spawn its own thread in which to do that
* work. More information on this can be found in
* <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
- * Processes and Threads</a>.</p>
+ * Processes and Threads</a>. The {@link IntentService} class is available
+ * as a standard implementation of Service that has its own thread where it
+ * schedules its work to be done.</p>
*
* <p>The Service class is an important part of an
* <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p>
*
* <p>Topics covered here:
* <ol>
+ * <li><a href="#WhatIsAService">What is a Service?</a>
* <li><a href="#ServiceLifecycle">Service Lifecycle</a>
* <li><a href="#Permissions">Permissions</a>
* <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * <li><a href="#LocalServiceSample">Local Service Sample</a>
+ * <li><a href="#RemoteMessengerServiceSample">Remote Messenger Service Sample</a>
* </ol>
*
+ * <a name="WhatIsAService"></a>
+ * <h3>What is a Service?</h3>
+ *
+ * <p>Most confusion about the Service class actually revolves around what
+ * it is <em>not</em>:</p>
+ *
+ * <ul>
+ * <li> A Service is <b>not</b> a separate process. The Service object itself
+ * does not imply it is running in its own process; unless otherwise specified,
+ * it runs in the same process as the application it is part of.
+ * <li> A Service is <b>not</b> a thread. It is not a means itself to do work off
+ * of the main thread (to avoid Application Not Responding errors).
+ * </ul>
+ *
+ * <p>Thus a Service itself is actually very simple, providing two main features:</p>
+ *
+ * <ul>
+ * <li>A facility for the application to tell the system <em>about</em>
+ * something it wants to be doing in the background (even when the user is not
+ * directly interacting with the application). This corresponds to calls to
+ * {@link android.content.Context#startService Context.startService()}, which
+ * ask the system to schedule work for the service, to be run until the service
+ * or someone else explicitly stop it.
+ * <li>A facility for an application to expose some of its functionality to
+ * other applications. This corresponds to calls to
+ * {@link android.content.Context#bindService Context.bindService()}, which
+ * allows a long-standing connection to be made to the service in order to
+ * interact with it.
+ * </ul>
+ *
+ * <p>When a Service component is actually created, for either of these reasons,
+ * all that the system actually does is instantiate the component
+ * and call its {@link #onCreate} and any other appropriate callbacks on the
+ * main thread. It is up to the Service to implement these with the appropriate
+ * behavior, such as creating a secondary thread in which it does its work.</p>
+ *
+ * <p>Note that because Service itself is so simple, you can make your
+ * interaction with it as simple or complicated as you want: from treating it
+ * as a local Java object that you make direct method calls on (as illustrated
+ * by <a href="#LocalServiceSample">Local Service Sample</a>), to providing
+ * a full remoteable interface using AIDL.</p>
+ *
* <a name="ServiceLifecycle"></a>
* <h3>Service Lifecycle</h3>
*
@@ -166,6 +214,64 @@ import java.io.PrintWriter;
* (such as an {@link android.app.Activity}) can, of course, increase the
* importance of the overall
* process beyond just the importance of the service itself.
+ *
+ * <a name="LocalServiceSample"></a>
+ * <h3>Local Service Sample</h3>
+ *
+ * <p>One of the most common uses of a Service is as a secondary component
+ * running alongside other parts of an application, in the same process as
+ * the rest of the components. All components of an .apk run in the same
+ * process unless explicitly stated otherwise, so this is a typical situation.
+ *
+ * <p>When used in this way, by assuming the
+ * components are in the same process, you can greatly simplify the interaction
+ * between them: clients of the service can simply cast the IBinder they
+ * receive from it to a concrete class published by the service.
+ *
+ * <p>An example of this use of a Service is shown here. First is the Service
+ * itself, publishing a custom class when bound:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalService.java
+ * service}
+ *
+ * <p>With that done, one can now write client code that directly accesses the
+ * running service, such as:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.java
+ * bind}
+ *
+ * <a name="RemoteMessengerServiceSample"></a>
+ * <h3>Remote Messenger Service Sample</h3>
+ *
+ * <p>If you need to be able to write a Service that can perform complicated
+ * communication with clients in remote processes (beyond simply the use of
+ * {@link Context#startService(Intent) Context.startService} to send
+ * commands to it), then you can use the {@link android.os.Messenger} class
+ * instead of writing full AIDL files.
+ *
+ * <p>An example of a Service that uses Messenger as its client interface
+ * is shown here. First is the Service itself, publishing a Messenger to
+ * an internal Handler when bound:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.java
+ * service}
+ *
+ * <p>If we want to make this service run in a remote process (instead of the
+ * standard one for its .apk), we can use <code>android:process</code> in its
+ * manifest tag to specify one:
+ *
+ * {@sample development/samples/ApiDemos/AndroidManifest.xml remote_service_declaration}
+ *
+ * <p>Note that the name "remote" chosen here is arbitrary, and you can use
+ * other names if you want additional processes. The ':' prefix appends the
+ * name to your package's standard process name.
+ *
+ * <p>With that done, clients can now bind to the service and send messages
+ * to it. Note that this allows clients to register with it to receive
+ * messages back as well:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.java
+ * bind}
*/
public abstract class Service extends ContextWrapper implements ComponentCallbacks {
private static final String TAG = "Service";
@@ -287,6 +393,14 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* {@link #onStart} and returns either {@link #START_STICKY}
* or {@link #START_STICKY_COMPATIBILITY}.
*
+ * <p>If you need your application to run on platform versions prior to API
+ * level 5, you can use the following model to handle the older {@link #onStart}
+ * callback in that case. The <code>handleCommand</code> method is implemented by
+ * you as appropriate:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
+ * start_compatibility}
+ *
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
@@ -462,6 +576,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* if your service is performing background music playback, so the user
* would notice if their music stopped playing.
*
+ * <p>If you need your application to run on platform versions prior to API
+ * level 5, you can use the following model to call the the older {@link #setForeground}
+ * or this modern method as appropriate:
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
+ * foreground_compatibility}
+ *
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
* NotificationManager.notify(int, Notification)}.
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 51d7393..72ec616 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -31,12 +31,12 @@ import android.os.ServiceManager;
public class StatusBarManager {
/**
* Flag for {@link #disable} to make the status bar not expandable. Unless you also
- * set {@link #DISABLE_NOTIFICATIONS}, new notifications will continue to show.
+ * set {@link #DISABLE_NOTIFICATION_ICONS}, new notifications will continue to show.
*/
public static final int DISABLE_EXPAND = 0x00000001;
/**
- * Flag for {@link #disable} to hide notification icons and ticker text.
+ * Flag for {@link #disable} to hide notification icons and scrolling ticker text.
*/
public static final int DISABLE_NOTIFICATION_ICONS = 0x00000002;
@@ -47,6 +47,12 @@ public class StatusBarManager {
public static final int DISABLE_NOTIFICATION_ALERTS = 0x00000004;
/**
+ * Flag for {@link #disable} to hide only the scrolling ticker. Note that
+ * {@link #DISABLE_NOTIFICATION_ICONS} implies {@link #DISABLE_NOTIFICATION_TICKER}.
+ */
+ public static final int DISABLE_NOTIFICATION_TICKER = 0x00000008;
+
+ /**
* Re-enable all of the status bar features that you've disabled.
*/
public static final int DISABLE_NONE = 0x00000000;
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 12be97c..8d8864f 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -16,7 +16,8 @@
package android.app;
-import android.app.SearchManager.DialogCursorProtocol;
+import com.android.internal.R;
+
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -24,6 +25,7 @@ import android.content.ContentResolver.OpenResourceIdResult;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.ColorDrawable;
@@ -31,12 +33,13 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.server.search.SearchableInfo;
-import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.util.SparseArray;
-import android.view.KeyEvent;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
@@ -66,39 +69,20 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
private Context mProviderContext;
private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
private SparseArray<Drawable.ConstantState> mBackgroundsCache;
- private boolean mGlobalSearchMode;
private boolean mClosed = false;
+ // URL color
+ private ColorStateList mUrlColor;
+
// Cached column indexes, updated when the cursor changes.
- private int mFormatCol;
private int mText1Col;
private int mText2Col;
+ private int mText2UrlCol;
private int mIconName1Col;
private int mIconName2Col;
private int mBackgroundColorCol;
-
- // The extra used to tell a cursor to close itself. This is a hack, see the description by
- // its use later in this file.
- private static final String EXTRA_CURSOR_RESPOND_CLOSE_CURSOR = "cursor_respond_close_cursor";
-
- // The bundle which contains {EXTRA_CURSOR_RESPOND_CLOSE_CURSOR=true}, just cached once
- // so we don't bother recreating it a bunch.
- private final Bundle mCursorRespondCloseCursorBundle;
-
- // This value is stored in SuggestionsAdapter by the SearchDialog to indicate whether
- // a particular list item should be selected upon the next call to notifyDataSetChanged.
- // This is used to indicate the index of the "More results..." list item so that when
- // the data set changes after a click of "More results...", we can correctly tell the
- // ListView to scroll to the right line item. It gets reset to NONE every time it
- // is consumed.
- private int mListItemToSelect = NONE;
- static final int NONE = -1;
- // holds the maximum position that has been displayed to the user
- int mMaxDisplayed = NONE;
-
- // holds the position that, when displayed, should result in notifying the cursor
- int mDisplayNotifyPos = NONE;
+ static final int NONE = -1;
private final Runnable mStartSpinnerRunnable;
private final Runnable mStopSpinnerRunnable;
@@ -111,8 +95,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public SuggestionsAdapter(Context context, SearchDialog searchDialog,
SearchableInfo searchable,
- WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache,
- boolean globalSearchMode) {
+ WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
super(context,
com.android.internal.R.layout.search_dropdown_item_icons_2line,
null, // no initial cursor
@@ -127,7 +110,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mOutsideDrawablesCache = outsideDrawablesCache;
mBackgroundsCache = new SparseArray<Drawable.ConstantState>();
- mGlobalSearchMode = globalSearchMode;
mStartSpinnerRunnable = new Runnable() {
public void run() {
@@ -140,10 +122,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mSearchDialog.setWorking(false);
}
};
-
- // Create this once because we'll reuse it a bunch.
- mCursorRespondCloseCursorBundle = new Bundle();
- mCursorRespondCloseCursorBundle.putBoolean(EXTRA_CURSOR_RESPOND_CLOSE_CURSOR, true);
// delay 500ms when deleting
getFilter().setDelayer(new Filter.Delayer() {
@@ -152,7 +130,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public long getPostingDelay(CharSequence constraint) {
if (constraint == null) return 0;
-
+
long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0;
mPreviousLength = constraint.length();
return delay;
@@ -178,28 +156,27 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
String query = (constraint == null) ? "" : constraint.toString();
- if (!mGlobalSearchMode) {
- /**
- * for in app search we show the progress spinner until the cursor is returned with
- * the results. for global search we manage the progress bar using
- * {@link DialogCursorProtocol#POST_REFRESH_RECEIVE_ISPENDING}.
- */
- mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
- }
+ /**
+ * for in app search we show the progress spinner until the cursor is returned with
+ * the results.
+ */
+ Cursor cursor = null;
+ mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
try {
- final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
+ cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
// trigger fill window so the spinner stays up until the results are copied over and
// closer to being ready
- if (!mGlobalSearchMode && cursor != null) cursor.getCount();
- return cursor;
+ if (cursor != null) {
+ cursor.getCount();
+ return cursor;
+ }
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
- return null;
- } finally {
- if (!mGlobalSearchMode) {
- mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
- }
}
+ // If cursor is null or an exception was thrown, stop the spinner and return null.
+ // changeCursor doesn't get called if cursor is null
+ mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+ return null;
}
public void close() {
@@ -208,6 +185,42 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mClosed = true;
}
+ @Override
+ public void notifyDataSetChanged() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+ super.notifyDataSetChanged();
+
+ mSearchDialog.onDataSetChanged();
+
+ updateSpinnerState(getCursor());
+ }
+
+ @Override
+ public void notifyDataSetInvalidated() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
+ super.notifyDataSetInvalidated();
+
+ updateSpinnerState(getCursor());
+ }
+
+ private void updateSpinnerState(Cursor cursor) {
+ Bundle extras = cursor != null ? cursor.getExtras() : null;
+ if (DBG) {
+ Log.d(LOG_TAG, "updateSpinnerState - extra = "
+ + (extras != null
+ ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
+ : null));
+ }
+ // Check if the Cursor indicates that the query is not complete and show the spinner
+ if (extras != null
+ && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
+ mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
+ return;
+ }
+ // If cursor is null or is done, stop the spinner
+ mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+ }
+
/**
* Cache columns.
*/
@@ -222,107 +235,22 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
try {
- Cursor oldCursor = getCursor();
super.changeCursor(c);
-
- // We send a special respond to the cursor to tell it to close itself directly because
- // it may not happen correctly for some cursors currently. This was originally
- // included as a fix to http://b/2036290, in which the search dialog was holding
- // on to references to the web search provider unnecessarily. This is being caused by
- // the fact that the cursor is not being correctly closed in
- // BulkCursorToCursorAdapter#close, which remains unfixed (see http://b/2015069).
- //
- // TODO: Remove this hack once http://b/2015069 is fixed.
- if (oldCursor != null && oldCursor != c) {
- oldCursor.respond(mCursorRespondCloseCursorBundle);
- }
-
+
if (c != null) {
- mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+ mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mBackgroundColorCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
+ mBackgroundColorCol =
+ c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
}
} catch (Exception e) {
Log.e(LOG_TAG, "error changing cursor and caching columns", e);
}
}
- @Override
- public void notifyDataSetChanged() {
- if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
- super.notifyDataSetChanged();
-
- callCursorPostRefresh(mCursor);
-
- // look out for the pending item we are supposed to scroll to
- if (mListItemToSelect != NONE) {
- mSearchDialog.setListSelection(mListItemToSelect);
- mListItemToSelect = NONE;
- }
- }
-
- /**
- * Handle sending and receiving information associated with
- * {@link DialogCursorProtocol#POST_REFRESH}.
- *
- * @param cursor The cursor to call.
- */
- private void callCursorPostRefresh(Cursor cursor) {
- if (!mGlobalSearchMode) return;
- final Bundle request = new Bundle();
- request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.POST_REFRESH);
- final Bundle response = cursor.respond(request);
-
- mSearchDialog.setWorking(
- response.getBoolean(DialogCursorProtocol.POST_REFRESH_RECEIVE_ISPENDING, false));
-
- mDisplayNotifyPos =
- response.getInt(DialogCursorProtocol.POST_REFRESH_RECEIVE_DISPLAY_NOTIFY, -1);
- }
-
- /**
- * Tell the cursor which position was clicked, handling sending and receiving information
- * associated with {@link DialogCursorProtocol#CLICK}.
- *
- * @param cursor The cursor
- * @param position The position that was clicked.
- */
- void callCursorOnClick(Cursor cursor, int position, int actionKey, String actionMsg) {
- if (!mGlobalSearchMode) return;
- final Bundle request = new Bundle(5);
- request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK);
- request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position);
- request.putInt(DialogCursorProtocol.CLICK_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- request.putInt(DialogCursorProtocol.CLICK_SEND_ACTION_KEY, actionKey);
- request.putString(DialogCursorProtocol.CLICK_SEND_ACTION_MSG, actionMsg);
- }
- final Bundle response = cursor.respond(request);
- mMaxDisplayed = -1;
- mListItemToSelect = response.getInt(
- DialogCursorProtocol.CLICK_RECEIVE_SELECTED_POS, SuggestionsAdapter.NONE);
- }
-
- /**
- * Tell the cursor that a search was started without using a suggestion.
- *
- * @param query The search query.
- */
- void reportSearch(String query) {
- if (!mGlobalSearchMode) return;
- Cursor cursor = getCursor();
- if (cursor == null) return;
- final Bundle request = new Bundle(3);
- request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.SEARCH);
- request.putString(DialogCursorProtocol.SEARCH_SEND_QUERY, query);
- request.putInt(DialogCursorProtocol.SEARCH_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
- // the response is always empty
- cursor.respond(request);
- }
-
/**
* Tags the view with cached child view look-ups.
*/
@@ -354,20 +282,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
ChildViewCache views = (ChildViewCache) view.getTag();
- final int pos = cursor.getPosition();
-
- // update the maximum position displayed since last refresh
- if (pos > mMaxDisplayed) {
- mMaxDisplayed = pos;
- }
-
- // if the cursor wishes to be notified about this position, send it
- if (mGlobalSearchMode && mDisplayNotifyPos != NONE && pos == mDisplayNotifyPos) {
- final Bundle request = new Bundle();
- request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.THRESH_HIT);
- mCursor.respond(request);
- mDisplayNotifyPos = NONE; // only notify the first time
- }
int backgroundColor = 0;
if (mBackgroundColorCol != -1) {
@@ -376,9 +290,34 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
Drawable background = getItemBackground(backgroundColor);
view.setBackgroundDrawable(background);
- final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol));
- setViewText(cursor, views.mText1, mText1Col, isHtml);
- setViewText(cursor, views.mText2, mText2Col, isHtml);
+ if (views.mText1 != null) {
+ String text1 = getStringOrNull(cursor, mText1Col);
+ setViewText(views.mText1, text1);
+ }
+ if (views.mText2 != null) {
+ // First check TEXT_2_URL
+ CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
+ if (text2 != null) {
+ text2 = formatUrl(text2);
+ } else {
+ text2 = getStringOrNull(cursor, mText2Col);
+ }
+
+ // If no second line of text is indicated, allow the first line of text
+ // to be up to two lines if it wants to be.
+ if (TextUtils.isEmpty(text2)) {
+ if (views.mText1 != null) {
+ views.mText1.setSingleLine(false);
+ views.mText1.setMaxLines(2);
+ }
+ } else {
+ if (views.mText1 != null) {
+ views.mText1.setSingleLine(true);
+ views.mText1.setMaxLines(1);
+ }
+ }
+ setViewText(views.mText2, text2);
+ }
if (views.mIcon1 != null) {
setViewDrawable(views.mIcon1, getIcon1(cursor));
@@ -388,6 +327,21 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
}
+ private CharSequence formatUrl(CharSequence url) {
+ if (mUrlColor == null) {
+ // Lazily get the URL color from the current theme.
+ TypedValue colorValue = new TypedValue();
+ mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
+ mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+ }
+
+ SpannableString text = new SpannableString(url);
+ text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null),
+ 0, url.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return text;
+ }
+
/**
* Gets a drawable with no color when selected or pressed, and the given color when
* neither selected nor pressed.
@@ -415,19 +369,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
}
- private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
- if (v == null) {
- return;
- }
- CharSequence text = null;
- if (textCol >= 0) {
- String str = cursor.getString(textCol);
- if (isHtml && looksLikeHtml(str)) {
- text = Html.fromHtml(str);
- } else {
- text = str;
- }
- }
+ private void setViewText(TextView v, CharSequence text) {
// Set the text even if it's null, since we need to clear any previous text.
v.setText(text);
@@ -438,15 +380,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
}
- private static boolean looksLikeHtml(String str) {
- if (TextUtils.isEmpty(str)) return false;
- for (int i = str.length() - 1; i >= 0; i--) {
- char c = str.charAt(i);
- if (c == '<' || c == '&') return true;
- }
- return false;
- }
-
private Drawable getIcon1(Cursor cursor) {
if (mIconName1Col < 0) {
return null;
@@ -754,6 +687,10 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
*/
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
+ return getStringOrNull(cursor, col);
+ }
+
+ private static String getStringOrNull(Cursor cursor, int col) {
if (col == NONE) {
return null;
}
@@ -766,5 +703,4 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
return null;
}
}
-
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
new file mode 100644
index 0000000..95451d6
--- /dev/null
+++ b/core/java/android/app/UiModeManager.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * This class provides access to the system uimode services. These services
+ * allow applications to control UI modes of the device.
+ * It provides functionality to disable the car mode and it gives access to the
+ * night mode settings.
+ *
+ * <p>These facilities are built on top of the underlying
+ * {@link android.content.Intent#ACTION_DOCK_EVENT} broadcasts that are sent when the user
+ * physical places the device into and out of a dock. When that happens,
+ * the UiModeManager switches the system {@link android.content.res.Configuration}
+ * to the appropriate UI mode, sends broadcasts about the mode switch, and
+ * starts the corresponding mode activity if appropriate. See the
+ * broadcasts {@link #ACTION_ENTER_CAR_MODE} and
+ * {@link #ACTION_ENTER_DESK_MODE} for more information.
+ *
+ * <p>In addition, the user may manually switch the system to car mode without
+ * physically being in a dock. While in car mode -- whether by manual action
+ * from the user or being physically placed in a dock -- a notification is
+ * displayed allowing the user to exit dock mode. Thus the dock mode
+ * represented here may be different than the current state of the underlying
+ * dock event broadcast.
+ *
+ * <p>You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.UI_MODE_SERVICE)}.
+ */
+public class UiModeManager {
+ private static final String TAG = "UiModeManager";
+
+ /**
+ * Broadcast sent when the device's UI has switched to car mode, either
+ * by being placed in a car dock or explicit action of the user. After
+ * sending the broadcast, the system will start the intent
+ * {@link android.content.Intent#ACTION_MAIN} with category
+ * {@link android.content.Intent#CATEGORY_CAR_DOCK}
+ * to display the car UI, which typically what an application would
+ * implement to provide their own interface. However, applications can
+ * also monitor this Intent in order to be informed of mode changes or
+ * prevent the normal car UI from being displayed by setting the result
+ * of the broadcast to {@link Activity#RESULT_CANCELED}.
+ */
+ public static String ACTION_ENTER_CAR_MODE = "android.app.action.ENTER_CAR_MODE";
+
+ /**
+ * Broadcast sent when the device's UI has switch away from car mode back
+ * to normal mode. Typically used by a car mode app, to dismiss itself
+ * when the user exits car mode.
+ */
+ public static String ACTION_EXIT_CAR_MODE = "android.app.action.EXIT_CAR_MODE";
+
+ /**
+ * Broadcast sent when the device's UI has switched to desk mode,
+ * by being placed in a desk dock. After
+ * sending the broadcast, the system will start the intent
+ * {@link android.content.Intent#ACTION_MAIN} with category
+ * {@link android.content.Intent#CATEGORY_DESK_DOCK}
+ * to display the desk UI, which typically what an application would
+ * implement to provide their own interface. However, applications can
+ * also monitor this Intent in order to be informed of mode changes or
+ * prevent the normal desk UI from being displayed by setting the result
+ * of the broadcast to {@link Activity#RESULT_CANCELED}.
+ */
+ public static String ACTION_ENTER_DESK_MODE = "android.app.action.ENTER_DESK_MODE";
+
+ /**
+ * Broadcast sent when the device's UI has switched away from desk mode back
+ * to normal mode. Typically used by a desk mode app, to dismiss itself
+ * when the user exits desk mode.
+ */
+ public static String ACTION_EXIT_DESK_MODE = "android.app.action.EXIT_DESK_MODE";
+
+ /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+ * automatically switch night mode on and off based on the time.
+ */
+ public static final int MODE_NIGHT_AUTO = Configuration.UI_MODE_NIGHT_UNDEFINED >> 4;
+
+ /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+ * never run in night mode.
+ */
+ public static final int MODE_NIGHT_NO = Configuration.UI_MODE_NIGHT_NO >> 4;
+
+ /** Constant for {@link #setNightMode(int)} and {@link #getNightMode()}:
+ * always run in night mode.
+ */
+ public static final int MODE_NIGHT_YES = Configuration.UI_MODE_NIGHT_YES >> 4;
+
+ private IUiModeManager mService;
+
+ /*package*/ UiModeManager() {
+ mService = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService(Context.UI_MODE_SERVICE));
+ }
+
+ /**
+ * Flag for use with {@link #enableCarMode(int)}: go to the car
+ * home activity as part of the enable. Enabling this way ensures
+ * a clean transition between the current activity (in non-car-mode) and
+ * the car home activity that will serve as home while in car mode. This
+ * will switch to the car home activity even if we are already in car mode.
+ */
+ public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 0x0001;
+
+ /**
+ * Force device into car mode, like it had been placed in the car dock.
+ * This will cause the device to switch to the car home UI as part of
+ * the mode switch.
+ * @param flags Must be 0.
+ */
+ public void enableCarMode(int flags) {
+ if (mService != null) {
+ try {
+ mService.enableCarMode(flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "disableCarMode: RemoteException", e);
+ }
+ }
+ }
+
+ /**
+ * Flag for use with {@link #disableCarMode(int)}: go to the normal
+ * home activity as part of the disable. Disabling this way ensures
+ * a clean transition between the current activity (in car mode) and
+ * the original home activity (which was typically last running without
+ * being in car mode).
+ */
+ public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001;
+
+ /**
+ * Turn off special mode if currently in car mode.
+ * @param flags May be 0 or {@link #DISABLE_CAR_MODE_GO_HOME}.
+ */
+ public void disableCarMode(int flags) {
+ if (mService != null) {
+ try {
+ mService.disableCarMode(flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "disableCarMode: RemoteException", e);
+ }
+ }
+ }
+
+ /**
+ * Return the current running mode type. May be one of
+ * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
+ * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or
+ * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR},
+ */
+ public int getCurrentModeType() {
+ if (mService != null) {
+ try {
+ return mService.getCurrentModeType();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getCurrentModeType: RemoteException", e);
+ }
+ }
+ return Configuration.UI_MODE_TYPE_NORMAL;
+ }
+
+ /**
+ * Sets the night mode. Changes to the night mode are only effective when
+ * the car or desk mode is enabled on a device.
+ *
+ * <p>The mode can be one of:
+ * <ul>
+ * <li><em>{@link #MODE_NIGHT_NO}<em> - sets the device into notnight
+ * mode.</li>
+ * <li><em>{@link #MODE_NIGHT_YES}</em> - sets the device into night mode.
+ * </li>
+ * <li><em>{@link #MODE_NIGHT_AUTO}</em> - automatic night/notnight switching
+ * depending on the location and certain other sensors.</li>
+ * </ul>
+ */
+ public void setNightMode(int mode) {
+ if (mService != null) {
+ try {
+ mService.setNightMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setNightMode: RemoteException", e);
+ }
+ }
+ }
+
+ /**
+ * Returns the currently configured night mode.
+ *
+ * @return {@link #MODE_NIGHT_NO}, {@link #MODE_NIGHT_YES}, or
+ * {@link #MODE_NIGHT_AUTO}. When an error occurred -1 is returned.
+ */
+ public int getNightMode() {
+ if (mService != null) {
+ try {
+ return mService.getNightMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getNightMode: RemoteException", e);
+ }
+ }
+ return -1;
+ }
+}
diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java
index 1034fab..7db9fa8 100644
--- a/core/java/android/app/WallpaperInfo.java
+++ b/core/java/android/app/WallpaperInfo.java
@@ -1,3 +1,19 @@
+/*
+ * 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 org.xmlpull.v1.XmlPullParser;
@@ -5,10 +21,13 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -79,6 +98,8 @@ public final class WallpaperInfo implements Parcelable {
+ WallpaperService.SERVICE_META_DATA + " meta-data");
}
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -92,7 +113,7 @@ public final class WallpaperInfo implements Parcelable {
"Meta-data does not start with wallpaper tag");
}
- TypedArray sa = context.getResources().obtainAttributes(attrs,
+ TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.Wallpaper);
settingsActivityComponent = sa.getString(
com.android.internal.R.styleable.Wallpaper_settingsActivity);
@@ -108,6 +129,9 @@ public final class WallpaperInfo implements Parcelable {
-1);
sa.recycle();
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to create context for: " + si.packageName);
} finally {
if (parser != null) parser.close();
}
@@ -188,7 +212,7 @@ public final class WallpaperInfo implements Parcelable {
return pm.getDrawable(mService.serviceInfo.packageName,
mThumbnailResource,
- null);
+ mService.serviceInfo.applicationInfo);
}
/**
@@ -196,25 +220,33 @@ public final class WallpaperInfo implements Parcelable {
*/
public CharSequence loadAuthor(PackageManager pm) throws NotFoundException {
if (mAuthorResource <= 0) throw new NotFoundException();
- return pm.getText(
- (mService.resolvePackageName != null)
- ? mService.resolvePackageName
- : getPackageName(),
- mAuthorResource,
- null);
+ String packageName = mService.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mService.serviceInfo.packageName;
+ applicationInfo = mService.serviceInfo.applicationInfo;
+ }
+ return pm.getText(packageName, mAuthorResource, applicationInfo);
}
/**
* Return a brief summary of this wallpaper's behavior.
*/
public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+ String packageName = mService.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mService.serviceInfo.packageName;
+ applicationInfo = mService.serviceInfo.applicationInfo;
+ }
+ if (mService.serviceInfo.descriptionRes != 0) {
+ return pm.getText(packageName, mService.serviceInfo.descriptionRes,
+ applicationInfo);
+
+ }
if (mDescriptionResource <= 0) throw new NotFoundException();
- return pm.getText(
- (mService.resolvePackageName != null)
- ? mService.resolvePackageName
- : getPackageName(),
- mDescriptionResource,
- null);
+ return pm.getText(packageName, mDescriptionResource,
+ mService.serviceInfo.applicationInfo);
}
/**
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
new file mode 100644
index 0000000..0bcd65c
--- /dev/null
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class is used to specify meta information of a device administrator
+ * component.
+ */
+public final class DeviceAdminInfo implements Parcelable {
+ static final String TAG = "DeviceAdminInfo";
+
+ /**
+ * A type of policy that this device admin can use: limit the passwords
+ * that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
+ * and {@link DevicePolicyManager#setPasswordMinimumLength}.
+ *
+ * <p>To control this policy, the device admin must have a "limit-password"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_LIMIT_PASSWORD = 0;
+
+ /**
+ * A type of policy that this device admin can use: able to watch login
+ * attempts from the user, via {@link DeviceAdminReceiver#ACTION_PASSWORD_FAILED},
+ * {@link DeviceAdminReceiver#ACTION_PASSWORD_SUCCEEDED}, and
+ * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts}.
+ *
+ * <p>To control this policy, the device admin must have a "watch-login"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_WATCH_LOGIN = 1;
+
+ /**
+ * A type of policy that this device admin can use: able to reset the
+ * user's password via
+ * {@link DevicePolicyManager#resetPassword}.
+ *
+ * <p>To control this policy, the device admin must have a "reset-password"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_RESET_PASSWORD = 2;
+
+ /**
+ * A type of policy that this device admin can use: able to force the device
+ * to lock via{@link DevicePolicyManager#lockNow} or limit the
+ * maximum lock timeout for the device via
+ * {@link DevicePolicyManager#setMaximumTimeToLock}.
+ *
+ * <p>To control this policy, the device admin must have a "force-lock"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_FORCE_LOCK = 3;
+
+ /**
+ * A type of policy that this device admin can use: able to factory
+ * reset the device, erasing all of the user's data, via
+ * {@link DevicePolicyManager#wipeData}.
+ *
+ * <p>To control this policy, the device admin must have a "wipe-data"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_WIPE_DATA = 4;
+
+ /** @hide */
+ public static class PolicyInfo {
+ public final int ident;
+ final public String tag;
+ final public int label;
+ final public int description;
+
+ public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) {
+ ident = identIn;
+ tag = tagIn;
+ label = labelIn;
+ description = descriptionIn;
+ }
+ }
+
+ static ArrayList<PolicyInfo> sPoliciesDisplayOrder = new ArrayList<PolicyInfo>();
+ static HashMap<String, Integer> sKnownPolicies = new HashMap<String, Integer>();
+ static SparseArray<PolicyInfo> sRevKnownPolicies = new SparseArray<PolicyInfo>();
+
+ static {
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WIPE_DATA, "wipe-data",
+ com.android.internal.R.string.policylab_wipeData,
+ com.android.internal.R.string.policydesc_wipeData));
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_RESET_PASSWORD, "reset-password",
+ com.android.internal.R.string.policylab_resetPassword,
+ com.android.internal.R.string.policydesc_resetPassword));
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_LIMIT_PASSWORD, "limit-password",
+ com.android.internal.R.string.policylab_limitPassword,
+ com.android.internal.R.string.policydesc_limitPassword));
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WATCH_LOGIN, "watch-login",
+ com.android.internal.R.string.policylab_watchLogin,
+ com.android.internal.R.string.policydesc_watchLogin));
+ sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_FORCE_LOCK, "force-lock",
+ com.android.internal.R.string.policylab_forceLock,
+ com.android.internal.R.string.policydesc_forceLock));
+
+ for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
+ PolicyInfo pi = sPoliciesDisplayOrder.get(i);
+ sRevKnownPolicies.put(pi.ident, pi);
+ sKnownPolicies.put(pi.tag, pi.ident);
+ }
+ }
+
+ /**
+ * The BroadcastReceiver that implements this device admin component.
+ */
+ final ResolveInfo mReceiver;
+
+ /**
+ * Whether this should be visible to the user.
+ */
+ boolean mVisible;
+
+ /**
+ * The policies this administrator needs access to.
+ */
+ int mUsesPolicies;
+
+ /**
+ * Constructor.
+ *
+ * @param context The Context in which we are parsing the device admin.
+ * @param receiver The ResolveInfo returned from the package manager about
+ * this device admin's component.
+ */
+ public DeviceAdminInfo(Context context, ResolveInfo receiver)
+ throws XmlPullParserException, IOException {
+ mReceiver = receiver;
+ ActivityInfo ai = receiver.activityInfo;
+
+ PackageManager pm = context.getPackageManager();
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(pm, DeviceAdminReceiver.DEVICE_ADMIN_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No "
+ + DeviceAdminReceiver.DEVICE_ADMIN_META_DATA + " meta-data");
+ }
+
+ Resources res = pm.getResourcesForApplication(ai.applicationInfo);
+
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!"device-admin".equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with device-admin tag");
+ }
+
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.DeviceAdmin);
+
+ mVisible = sa.getBoolean(
+ com.android.internal.R.styleable.DeviceAdmin_visible, true);
+
+ sa.recycle();
+
+ int outerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("uses-policies")) {
+ int innerDepth = parser.getDepth();
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String policyName = parser.getName();
+ Integer val = sKnownPolicies.get(policyName);
+ if (val != null) {
+ mUsesPolicies |= 1 << val.intValue();
+ } else {
+ Log.w(TAG, "Unknown tag under uses-policies of "
+ + getComponent() + ": " + policyName);
+ }
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to create context for: " + ai.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ DeviceAdminInfo(Parcel source) {
+ mReceiver = ResolveInfo.CREATOR.createFromParcel(source);
+ mUsesPolicies = source.readInt();
+ }
+
+ /**
+ * Return the .apk package that implements this device admin.
+ */
+ public String getPackageName() {
+ return mReceiver.activityInfo.packageName;
+ }
+
+ /**
+ * Return the class name of the receiver component that implements
+ * this device admin.
+ */
+ public String getReceiverName() {
+ return mReceiver.activityInfo.name;
+ }
+
+ /**
+ * Return the raw information about the receiver implementing this
+ * device admin. Do not modify the returned object.
+ */
+ public ActivityInfo getActivityInfo() {
+ return mReceiver.activityInfo;
+ }
+
+ /**
+ * Return the component of the receiver that implements this device admin.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mReceiver.activityInfo.packageName,
+ mReceiver.activityInfo.name);
+ }
+
+ /**
+ * Load the user-displayed label for this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mReceiver.loadLabel(pm);
+ }
+
+ /**
+ * Load user-visible description associated with this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public CharSequence loadDescription(PackageManager pm) throws NotFoundException {
+ if (mReceiver.activityInfo.descriptionRes != 0) {
+ String packageName = mReceiver.resolvePackageName;
+ ApplicationInfo applicationInfo = null;
+ if (packageName == null) {
+ packageName = mReceiver.activityInfo.packageName;
+ applicationInfo = mReceiver.activityInfo.applicationInfo;
+ }
+ return pm.getText(packageName,
+ mReceiver.activityInfo.descriptionRes, applicationInfo);
+ }
+ throw new NotFoundException();
+ }
+
+ /**
+ * Load the user-displayed icon for this device admin.
+ *
+ * @param pm Supply a PackageManager used to load the device admin's
+ * resources.
+ */
+ public Drawable loadIcon(PackageManager pm) {
+ return mReceiver.loadIcon(pm);
+ }
+
+ /**
+ * Returns whether this device admin would like to be visible to the
+ * user, even when it is not enabled.
+ */
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ /**
+ * Return true if the device admin has requested that it be able to use
+ * the given policy control. The possible policy identifier inputs are:
+ * {@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}.
+ */
+ public boolean usesPolicy(int policyIdent) {
+ return (mUsesPolicies & (1<<policyIdent)) != 0;
+ }
+
+ /**
+ * Return the XML tag name for the given policy identifier. Valid identifiers
+ * are as per {@link #usesPolicy(int)}. If the given identifier is not
+ * known, null is returned.
+ */
+ public String getTagForPolicy(int policyIdent) {
+ return sRevKnownPolicies.get(policyIdent).tag;
+ }
+
+ /** @hide */
+ public ArrayList<PolicyInfo> getUsedPolicies() {
+ ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>();
+ for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
+ PolicyInfo pi = sPoliciesDisplayOrder.get(i);
+ if (usesPolicy(pi.ident)) {
+ res.add(pi);
+ }
+ }
+ return res;
+ }
+
+ /** @hide */
+ public void writePoliciesToXml(XmlSerializer out)
+ throws IllegalArgumentException, IllegalStateException, IOException {
+ out.attribute(null, "flags", Integer.toString(mUsesPolicies));
+ }
+
+ /** @hide */
+ public void readPoliciesFromXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ mUsesPolicies = Integer.parseInt(
+ parser.getAttributeValue(null, "flags"));
+ }
+
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "Receiver:");
+ mReceiver.dump(pw, prefix + " ");
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceAdminInfo{" + mReceiver.activityInfo.name + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ mReceiver.writeToParcel(dest, flags);
+ dest.writeInt(mUsesPolicies);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<DeviceAdminInfo> CREATOR =
+ new Parcelable.Creator<DeviceAdminInfo>() {
+ public DeviceAdminInfo createFromParcel(Parcel source) {
+ return new DeviceAdminInfo(source);
+ }
+
+ public DeviceAdminInfo[] newArray(int size) {
+ return new DeviceAdminInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
new file mode 100644
index 0000000..b4dd9e7
--- /dev/null
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Base class for implementing a device administration component. This
+ * class provides a convenience for interpreting the raw intent actions
+ * that are sent by the system.
+ *
+ * <p>The callback methods, like the base
+ * {@link BroadcastReceiver#onReceive(Context, Intent) BroadcastReceiver.onReceive()}
+ * method, happen on the main thread of the process. Thus long running
+ * operations must be done on another thread. Note that because a receiver
+ * is done once returning from its receive function, such long-running operations
+ * should probably be done in a {@link Service}.
+ *
+ * <p>When publishing your DeviceAdmin subclass as a receiver, it must
+ * handle {@link #ACTION_DEVICE_ADMIN_ENABLED} and require the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission. A typical
+ * manifest entry would look like:</p>
+ *
+ * {@sample development/samples/ApiDemos/AndroidManifest.xml device_admin_declaration}
+ *
+ * <p>The meta-data referenced here provides addition information specific
+ * to the device administrator, as parsed by the {@link DeviceAdminInfo} class.
+ * A typical file would be:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/xml/device_admin_sample.xml meta_data}
+ */
+public class DeviceAdminReceiver extends BroadcastReceiver {
+ private static String TAG = "DevicePolicy";
+ private static boolean DEBUG = false;
+ private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+ /**
+ * This is the primary action that a device administrator must implement to be
+ * allowed to manage a device. This will be set to the receiver
+ * when the user enables it for administration. You will generally
+ * handle this in {@link DeviceAdminReceiver#onEnabled(Context, Intent)}. To be
+ * supported, the receiver must also require the
+ * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_ADMIN_ENABLED
+ = "android.app.action.DEVICE_ADMIN_ENABLED";
+
+ /**
+ * Action sent to a device administrator when the user has requested to
+ * disable it, but before this has actually been done. This gives you
+ * a chance to supply a message to the user about the impact of
+ * disabling your admin, by setting the extra field
+ * {@link #EXTRA_DISABLE_WARNING} in the result Intent. If not set,
+ * no warning will be displayed. If set, the given text will be shown
+ * to the user before they disable your admin.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
+ = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
+
+ /**
+ * A CharSequence that can be shown to the user informing them of the
+ * impact of disabling your admin.
+ *
+ * @see #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED
+ */
+ public static final String EXTRA_DISABLE_WARNING = "android.app.extra.DISABLE_WARNING";
+
+ /**
+ * Action sent to a device administrator when the user has disabled
+ * it. Upon return, the application no longer has access to the
+ * protected device policy manager APIs. You will generally
+ * handle this in {@link DeviceAdminReceiver#onDisabled(Context, Intent)}. Note
+ * that this action will be
+ * sent the receiver regardless of whether it is explicitly listed in
+ * its intent filter.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_ADMIN_DISABLED
+ = "android.app.action.DEVICE_ADMIN_DISABLED";
+
+ /**
+ * Action sent to a device administrator when the user has changed the
+ * password of their device. You can at this point check the characteristics
+ * of the new password with {@link DevicePolicyManager#isActivePasswordSufficient()
+ * DevicePolicyManager.isActivePasswordSufficient()}.
+ * You will generally
+ * handle this in {@link DeviceAdminReceiver#onPasswordChanged}.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to receive
+ * this broadcast.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_CHANGED
+ = "android.app.action.ACTION_PASSWORD_CHANGED";
+
+ /**
+ * Action sent to a device administrator when the user has failed at
+ * attempted to enter the password. You can at this point check the
+ * number of failed password attempts there have been with
+ * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts
+ * DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally
+ * handle this in {@link DeviceAdminReceiver#onPasswordFailed}.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+ * this broadcast.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_FAILED
+ = "android.app.action.ACTION_PASSWORD_FAILED";
+
+ /**
+ * Action sent to a device administrator when the user has successfully
+ * entered their password, after failing one or more times.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to receive
+ * this broadcast.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PASSWORD_SUCCEEDED
+ = "android.app.action.ACTION_PASSWORD_SUCCEEDED";
+
+ /**
+ * Name under which an DevicePolicy component publishes information
+ * about itself. This meta-data must reference an XML resource containing
+ * a device-admin tag. XXX TO DO: describe syntax.
+ */
+ public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin";
+
+ private DevicePolicyManager mManager;
+ private ComponentName mWho;
+
+ /**
+ * Retrieve the DevicePolicyManager interface for this administrator to work
+ * with the system.
+ */
+ public DevicePolicyManager getManager(Context context) {
+ if (mManager != null) {
+ return mManager;
+ }
+ mManager = (DevicePolicyManager)context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ return mManager;
+ }
+
+ /**
+ * Retrieve the ComponentName describing who this device administrator is, for
+ * use in {@link DevicePolicyManager} APIs that require the administrator to
+ * identify itself.
+ */
+ public ComponentName getWho(Context context) {
+ if (mWho != null) {
+ return mWho;
+ }
+ mWho = new ComponentName(context, getClass());
+ return mWho;
+ }
+
+ /**
+ * Called after the administrator is first enabled, as a result of
+ * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you
+ * can use {@link DevicePolicyManager} to set your desired policies.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onEnabled(Context context, Intent intent) {
+ }
+
+ /**
+ * Called when the user has asked to disable the administrator, as a result of
+ * receiving {@link #ACTION_DEVICE_ADMIN_DISABLE_REQUESTED}, giving you
+ * a chance to present a warning message to them. The message is returned
+ * as the result; if null is returned (the default implementation), no
+ * message will be displayed.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ * @return Return the warning message to display to the user before
+ * being disabled; if null is returned, no message is displayed.
+ */
+ public CharSequence onDisableRequested(Context context, Intent intent) {
+ return null;
+ }
+
+ /**
+ * Called prior to the administrator being disabled, as a result of
+ * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you
+ * can no longer use the protected parts of the {@link DevicePolicyManager}
+ * API.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onDisabled(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has changed their password, as a result of
+ * receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you
+ * can use {@link DevicePolicyManager#getCurrentFailedPasswordAttempts()
+ * DevicePolicyManager.getCurrentFailedPasswordAttempts()}
+ * to retrieve the active password characteristics.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordChanged(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has failed at entering their current password, as a result of
+ * receiving {@link #ACTION_PASSWORD_FAILED}. At this point you
+ * can use {@link DevicePolicyManager} to retrieve the number of failed
+ * password attempts.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordFailed(Context context, Intent intent) {
+ }
+
+ /**
+ * Called after the user has succeeded at entering their current password,
+ * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will
+ * only be received the first time they succeed after having previously
+ * failed.
+ * @param context The running context as per {@link #onReceive}.
+ * @param intent The received intent as per {@link #onReceive}.
+ */
+ public void onPasswordSucceeded(Context context, Intent intent) {
+ }
+
+ /**
+ * Intercept standard device administrator broadcasts. Implementations
+ * should not override this method; it is better to implement the
+ * convenience callbacks for each action.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_PASSWORD_CHANGED.equals(action)) {
+ onPasswordChanged(context, intent);
+ } else if (ACTION_PASSWORD_FAILED.equals(action)) {
+ onPasswordFailed(context, intent);
+ } else if (ACTION_PASSWORD_SUCCEEDED.equals(action)) {
+ onPasswordSucceeded(context, intent);
+ } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) {
+ onEnabled(context, intent);
+ } else if (ACTION_DEVICE_ADMIN_DISABLE_REQUESTED.equals(action)) {
+ CharSequence res = onDisableRequested(context, intent);
+ if (res != null) {
+ Bundle extras = getResultExtras(true);
+ extras.putCharSequence(EXTRA_DISABLE_WARNING, res);
+ }
+ } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) {
+ onDisabled(context, intent);
+ }
+ }
+}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
new file mode 100644
index 0000000..35e7ee6
--- /dev/null
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Public interface for managing policies enforced on a device. Most clients
+ * of this class must have published a {@link DeviceAdminReceiver} that the user
+ * has currently enabled.
+ */
+public class DevicePolicyManager {
+ private static String TAG = "DevicePolicyManager";
+ private static boolean DEBUG = false;
+ private static boolean localLOGV = DEBUG || android.util.Config.LOGV;
+
+ private final Context mContext;
+ private final IDevicePolicyManager mService;
+
+ private final Handler mHandler;
+
+ private DevicePolicyManager(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ mService = IDevicePolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+ }
+
+ /** @hide */
+ public static DevicePolicyManager create(Context context, Handler handler) {
+ DevicePolicyManager me = new DevicePolicyManager(context, handler);
+ return me.mService != null ? me : null;
+ }
+
+ /**
+ * Activity action: ask the user to add a new device administrator to the system.
+ * The desired policy is the ComponentName of the policy in the
+ * {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to
+ * bring the user through adding the device administrator to the system (or
+ * allowing them to reject it).
+ *
+ * <p>You can optionally include the {@link #EXTRA_ADD_EXPLANATION}
+ * field to provide the user with additional explanation (in addition
+ * to your component's description) about what is being added.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ADD_DEVICE_ADMIN
+ = "android.app.action.ADD_DEVICE_ADMIN";
+
+ /**
+ * The ComponentName of the administrator component.
+ *
+ * @see #ACTION_ADD_DEVICE_ADMIN
+ */
+ public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
+
+ /**
+ * An optional CharSequence providing additional explanation for why the
+ * admin is being added.
+ *
+ * @see #ACTION_ADD_DEVICE_ADMIN
+ */
+ public static final String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+
+ /**
+ * Activity action: have the user enter a new password. This activity
+ * should be launched after using {@link #setPasswordQuality(ComponentName, int)}
+ * or {@link #setPasswordMinimumLength(ComponentName, int)} to have the
+ * user enter a new password that meets the current requirements. You can
+ * use {@link #isActivePasswordSufficient()} to determine whether you need
+ * to have the user select a new password in order to meet the current
+ * constraints. Upon being resumed from this activity,
+ * you can check the new password characteristics to see if they are
+ * sufficient.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SET_NEW_PASSWORD
+ = "android.app.action.SET_NEW_PASSWORD";
+
+ /**
+ * Return true if the given administrator component is currently
+ * active (enabled) in the system.
+ */
+ public boolean isAdminActive(ComponentName who) {
+ if (mService != null) {
+ try {
+ return mService.isAdminActive(who);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return a list of all currently active device administrator's component
+ * names. Note that if there are no administrators than null may be
+ * returned.
+ */
+ public List<ComponentName> getActiveAdmins() {
+ if (mService != null) {
+ try {
+ return mService.getActiveAdmins();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean packageHasActiveAdmins(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.packageHasActiveAdmins(packageName);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove a current administration component. This can only be called
+ * by the application that owns the administration component; if you
+ * try to remove someone else's component, a security exception will be
+ * thrown.
+ */
+ public void removeActiveAdmin(ComponentName who) {
+ if (mService != null) {
+ try {
+ mService.removeActiveAdmin(who);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Constant for {@link #setPasswordQuality}: the policy has no requirements
+ * for the password. Note that quality constants are ordered so that higher
+ * values are more restrictive.
+ */
+ public static final int PASSWORD_QUALITY_UNSPECIFIED = 0;
+
+ /**
+ * Constant for {@link #setPasswordQuality}: the policy requires some kind
+ * of password, but doesn't care what it is. Note that quality constants
+ * are ordered so that higher values are more restrictive.
+ */
+ public static final int PASSWORD_QUALITY_SOMETHING = 0x10000;
+
+ /**
+ * Constant for {@link #setPasswordQuality}: the user must have entered a
+ * password containing at least numeric characters. Note that quality
+ * constants are ordered so that higher values are more restrictive.
+ */
+ public static final int PASSWORD_QUALITY_NUMERIC = 0x20000;
+
+ /**
+ * Constant for {@link #setPasswordQuality}: the user must have entered a
+ * password containing at least alphabetic (or other symbol) characters.
+ * Note that quality constants are ordered so that higher values are more
+ * restrictive.
+ */
+ public static final int PASSWORD_QUALITY_ALPHABETIC = 0x40000;
+
+ /**
+ * Constant for {@link #setPasswordQuality}: the user must have entered a
+ * password containing at least <em>both></em> numeric <em>and</em>
+ * alphabetic (or other symbol) characters. Note that quality constants are
+ * ordered so that higher values are more restrictive.
+ */
+ public static final int PASSWORD_QUALITY_ALPHANUMERIC = 0x50000;
+
+ /**
+ * Called by an application that is administering the device to set the
+ * password restrictions it is imposing. After setting this, the user
+ * will not be able to enter a new password that is not at least as
+ * restrictive as what has been set. Note that the current password
+ * will remain until the user has set a new one, so the change does not
+ * take place immediately. To prompt the user for a new password, use
+ * {@link #ACTION_SET_NEW_PASSWORD} after setting this value.
+ *
+ * <p>Quality constants are ordered so that higher values are more restrictive;
+ * thus the highest requested quality constant (between the policy set here,
+ * the user's preference, and any other considerations) is the one that
+ * is in effect.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} 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 quality The new desired quality. One of
+ * {@link #PASSWORD_QUALITY_UNSPECIFIED}, {@link #PASSWORD_QUALITY_SOMETHING},
+ * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
+ * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}.
+ */
+ public void setPasswordQuality(ComponentName admin, int quality) {
+ if (mService != null) {
+ try {
+ mService.setPasswordQuality(admin, quality);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current minimum password quality for all admins
+ * or a particular one.
+ * @param admin The name of the admin component to check, or null to aggregate
+ * all admins.
+ */
+ public int getPasswordQuality(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getPasswordQuality(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ /**
+ * Called by an application that is administering the device to set the
+ * minimum allowed password length. After setting this, the user
+ * will not be able to enter a new password that is not at least as
+ * restrictive as what has been set. Note that the current password
+ * will remain until the user has set a new one, so the change does not
+ * take place immediately. To prompt the user for a new password, use
+ * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This
+ * constraint is only imposed if the administrator has also requested either
+ * {@link #PASSWORD_QUALITY_NUMERIC}, {@link #PASSWORD_QUALITY_ALPHABETIC},
+ * or {@link #PASSWORD_QUALITY_ALPHANUMERIC}
+ * with {@link #setPasswordQuality}.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} 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 length The new desired minimum password length. A value of 0
+ * means there is no restriction.
+ */
+ public void setPasswordMinimumLength(ComponentName admin, int length) {
+ if (mService != null) {
+ try {
+ mService.setPasswordMinimumLength(admin, length);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current minimum password length for all admins
+ * or a particular one.
+ * @param admin The name of the admin component to check, or null to aggregate
+ * all admins.
+ */
+ public int getPasswordMinimumLength(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getPasswordMinimumLength(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Return the maximum password length that the device supports for a
+ * particular password quality.
+ * @param quality The quality being interrogated.
+ * @return Returns the maximum length that the user can enter.
+ */
+ public int getPasswordMaximumLength(int quality) {
+ // Kind-of arbitrary.
+ return 16;
+ }
+
+ /**
+ * Determine whether the current password the user has set is sufficient
+ * to meet the policy requirements (quality, minimum length) that have been
+ * requested.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
+ * @return Returns true if the password meets the current requirements,
+ * else false.
+ */
+ public boolean isActivePasswordSufficient() {
+ if (mService != null) {
+ try {
+ return mService.isActivePasswordSufficient();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the number of times the user has failed at entering a
+ * password since that last successful password entry.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ */
+ public int getCurrentFailedPasswordAttempts() {
+ if (mService != null) {
+ try {
+ return mService.getCurrentFailedPasswordAttempts();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Setting this to a value greater than zero enables a built-in policy
+ * that will perform a device wipe after too many incorrect
+ * device-unlock passwords have been entered. This built-in policy combines
+ * watching for failed passwords and wiping the device, and requires
+ * that you request both {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and
+ * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}}.
+ *
+ * <p>To implement any other policy (e.g. wiping data for a particular
+ * application only, erasing or revoking credentials, or reporting the
+ * failure to a server), you should implement
+ * {@link DeviceAdminReceiver#onPasswordFailed(Context, android.content.Intent)}
+ * instead. Do not use this API, because if the maximum count is reached,
+ * the device will be wiped immediately, and your callback will not be invoked.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param num The number of failed password attempts at which point the
+ * device will wipe its data.
+ */
+ public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) {
+ if (mService != null) {
+ try {
+ mService.setMaximumFailedPasswordsForWipe(admin, num);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current maximum number of login attempts that are allowed
+ * before the device wipes itself, for all admins
+ * or a particular one.
+ * @param admin The name of the admin component to check, or null to aggregate
+ * all admins.
+ */
+ public int getMaximumFailedPasswordsForWipe(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getMaximumFailedPasswordsForWipe(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Flag for {@link #resetPassword}: don't allow other admins to change
+ * the password again until the user has entered it.
+ */
+ public static final int RESET_PASSWORD_REQUIRE_ENTRY = 0x0001;
+
+ /**
+ * Force a new device unlock password (the password needed to access the
+ * entire device, not for individual accounts) on the user. This takes
+ * effect immediately.
+ * The given password must be sufficient for the
+ * current password quality and length constraints as returned by
+ * {@link #getPasswordQuality(ComponentName)} and
+ * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet
+ * these constraints, then it will be rejected and false returned. Note
+ * that the password may be a stronger quality (containing alphanumeric
+ * characters when the requested quality is only numeric), in which case
+ * the currently active quality will be increased to match.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
+ * @param password The new password for the user.
+ * @param flags May be 0 or {@link #RESET_PASSWORD_REQUIRE_ENTRY}.
+ * @return Returns true if the password was applied, or false if it is
+ * not acceptable for the current constraints.
+ */
+ public boolean resetPassword(String password, int flags) {
+ if (mService != null) {
+ try {
+ return mService.resetPassword(password, flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application that is administering the device to set the
+ * maximum time for user activity until the device will lock. This limits
+ * the length that the user can set. It takes effect immediately.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} 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 timeMs The new desired maximum time to lock in milliseconds.
+ * A value of 0 means there is no restriction.
+ */
+ public void setMaximumTimeToLock(ComponentName admin, long timeMs) {
+ if (mService != null) {
+ try {
+ mService.setMaximumTimeToLock(admin, timeMs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the current maximum time to unlock for all admins
+ * or a particular one.
+ * @param admin The name of the admin component to check, or null to aggregate
+ * all admins.
+ */
+ public long getMaximumTimeToLock(ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getMaximumTimeToLock(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Make the device lock immediately, as if the lock screen timeout has
+ * expired at the point of this call.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ */
+ public void lockNow() {
+ if (mService != null) {
+ try {
+ mService.lockNow();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Ask the user date be wiped. This will cause the device to reboot,
+ * erasing all user data while next booting up. External storage such
+ * as SD cards will not be erased.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
+ * @param flags Bit mask of additional options: currently must be 0.
+ */
+ public void wipeData(int flags) {
+ if (mService != null) {
+ try {
+ mService.wipeData(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setActiveAdmin(ComponentName policyReceiver) {
+ if (mService != null) {
+ try {
+ mService.setActiveAdmin(policyReceiver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public DeviceAdminInfo getAdminInfo(ComponentName cn) {
+ ActivityInfo ai;
+ try {
+ ai = mContext.getPackageManager().getReceiverInfo(cn,
+ PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to retrieve device policy " + cn, e);
+ return null;
+ }
+
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = ai;
+
+ try {
+ return new DeviceAdminInfo(mContext, ri);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to parse device policy " + cn, e);
+ return null;
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to parse device policy " + cn, e);
+ return null;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void getRemoveWarning(ComponentName admin, RemoteCallback result) {
+ if (mService != null) {
+ try {
+ mService.getRemoveWarning(admin, result);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void setActivePasswordState(int quality, int length) {
+ if (mService != null) {
+ try {
+ mService.setActivePasswordState(quality, length);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void reportFailedPasswordAttempt() {
+ if (mService != null) {
+ try {
+ mService.reportFailedPasswordAttempt();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void reportSuccessfulPasswordAttempt() {
+ if (mService != null) {
+ try {
+ mService.reportSuccessfulPasswordAttempt();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
new file mode 100644
index 0000000..6fc4dc5
--- /dev/null
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -0,0 +1,59 @@
+/*
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.app.admin;
+
+import android.content.ComponentName;
+import android.os.RemoteCallback;
+
+/**
+ * Internal IPC interface to the device policy service.
+ * {@hide}
+ */
+interface IDevicePolicyManager {
+ void setPasswordQuality(in ComponentName who, int quality);
+ int getPasswordQuality(in ComponentName who);
+
+ void setPasswordMinimumLength(in ComponentName who, int length);
+ int getPasswordMinimumLength(in ComponentName who);
+
+ boolean isActivePasswordSufficient();
+ int getCurrentFailedPasswordAttempts();
+
+ void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num);
+ int getMaximumFailedPasswordsForWipe(in ComponentName admin);
+
+ boolean resetPassword(String password, int flags);
+
+ void setMaximumTimeToLock(in ComponentName who, long timeMs);
+ long getMaximumTimeToLock(in ComponentName who);
+
+ void lockNow();
+
+ void wipeData(int flags);
+
+ void setActiveAdmin(in ComponentName policyReceiver);
+ boolean isAdminActive(in ComponentName policyReceiver);
+ List<ComponentName> getActiveAdmins();
+ boolean packageHasActiveAdmins(String packageName);
+ void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result);
+ void removeActiveAdmin(in ComponentName policyReceiver);
+
+ void setActivePasswordState(int quality, int length);
+ void reportFailedPasswordAttempt();
+ void reportSuccessfulPasswordAttempt();
+}
diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/app/backup/AbsoluteFileBackupHelper.java
index 1dbccc9..a4d99cf 100644
--- a/core/java/android/backup/AbsoluteFileBackupHelper.java
+++ b/core/java/android/app/backup/AbsoluteFileBackupHelper.java
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
-import java.io.FileDescriptor;
/**
* Like FileBackupHelper, but takes absolute paths for the files instead of
@@ -36,6 +35,13 @@ public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements Ba
Context mContext;
String[] mFiles;
+ /**
+ * Construct a helper for backing up / restoring the files at the given absolute locations
+ * within the file system.
+ *
+ * @param context
+ * @param files
+ */
public AbsoluteFileBackupHelper(Context context, String... files) {
super(context);
@@ -54,6 +60,9 @@ public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements Ba
performBackup_checked(oldState, data, newState, mFiles, mFiles);
}
+ /**
+ * Restore one absolute file entity from the restore stream
+ */
public void restoreEntity(BackupDataInputStream data) {
if (DEBUG) Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
String key = data.getKey();
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
new file mode 100644
index 0000000..5245da5
--- /dev/null
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -0,0 +1,251 @@
+/*
+ * 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.app.IBackupAgent;
+import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Provides the central interface between an
+ * application and Android's data backup infrastructure. An application that wishes
+ * to participate in the backup and restore mechanism will declare a subclass of
+ * {@link android.app.backup.BackupAgent}, implement the
+ * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()}
+ * and {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} methods,
+ * and provide the name of its backup agent class in its {@code AndroidManifest.xml} file via
+ * the <code><a
+ * href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
+ * tag's {@code android:backupAgent} attribute.
+ * <h3>Basic Operation</h3>
+ * <p>
+ * When the application makes changes to data that it wishes to keep backed up,
+ * it should call the
+ * {@link android.app.backup.BackupManager#dataChanged() BackupManager.dataChanged()} method.
+ * This notifies the Android Backup Manager that the application needs an opportunity
+ * to update its backup image. The Backup Manager, in turn, schedules a
+ * backup pass to be performed at an opportune time.
+ * <p>
+ * Restore operations are typically performed only when applications are first
+ * installed on a device. At that time, the operating system checks to see whether
+ * there is a previously-saved data set available for the application being installed, and if so,
+ * begins an immediate restore pass to deliver the backup data as part of the installation
+ * process.
+ * <p>
+ * When a backup or restore pass is run, the application's process is launched
+ * (if not already running), the manifest-declared backup agent class (in the {@code
+ * android:backupAgent} attribute) is instantiated within
+ * that process, and the agent's {@link #onCreate()} method is invoked. This prepares the
+ * agent instance to run the actual backup or restore logic. At this point the
+ * agent's
+ * {@link #onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup()} or
+ * {@link #onRestore(BackupDataInput, int, ParcelFileDescriptor) onRestore()} method will be
+ * invoked as appropriate for the operation being performed.
+ * <p>
+ * A backup data set consists of one or more "entities," flattened binary data
+ * records that are each identified with a key string unique within the data set. Adding a
+ * record to the active data set or updating an existing record is done by simply
+ * writing new entity data under the desired key. Deleting an entity from the data set
+ * is done by writing an entity under that key with header specifying a negative data
+ * size, and no actual entity data.
+ * <p>
+ * <b>Helper Classes</b>
+ * <p>
+ * An extensible agent based on convenient helper classes is available in
+ * {@link android.app.backup.BackupAgentHelper}. That class is particularly
+ * suited to handling of simple file or {@link android.content.SharedPreferences}
+ * backup and restore.
+ *
+ * @see android.app.backup.BackupManager
+ * @see android.app.backup.BackupAgentHelper
+ * @see android.app.backup.BackupDataInput
+ * @see android.app.backup.BackupDataOutput
+ */
+public abstract class BackupAgent extends ContextWrapper {
+ private static final String TAG = "BackupAgent";
+ private static final boolean DEBUG = false;
+
+ public BackupAgent() {
+ super(null);
+ }
+
+ /**
+ * Provided as a convenience for agent implementations that need an opportunity
+ * to do one-time initialization before the actual backup or restore operation
+ * is begun.
+ * <p>
+ * Agents do not need to override this method.
+ */
+ public void onCreate() {
+ }
+
+ /**
+ * Provided as a convenience for agent implementations that need to do some
+ * sort of shutdown process after backup or restore is completed.
+ * <p>
+ * Agents do not need to override this method.
+ */
+ public void onDestroy() {
+ }
+
+ /**
+ * The application is being asked to write any data changed since the last
+ * time it performed a backup operation. The state data recorded during the
+ * last backup pass is provided in the <code>oldState</code> file
+ * descriptor. If <code>oldState</code> is <code>null</code>, no old state
+ * is available and the application should perform a full backup. In both
+ * cases, a representation of the final backup state after this pass should
+ * be written to the file pointed to by the file descriptor wrapped in
+ * <code>newState</code>.
+ * <p>
+ * Each entity written to the {@link android.app.backup.BackupDataOutput}
+ * <code>data</code> stream will be transmitted
+ * over the current backup transport and stored in the remote data set under
+ * the key supplied as part of the entity. Writing an entity with a negative
+ * data size instructs the transport to delete whatever entity currently exists
+ * under that key from the remote data set.
+ *
+ * @param oldState An open, read-only ParcelFileDescriptor pointing to the
+ * last backup state provided by the application. May be
+ * <code>null</code>, in which case no prior state is being
+ * provided and the application should perform a full backup.
+ * @param data A structured wrapper around an open, read/write
+ * file descriptor pointing to the backup data destination.
+ * Typically the application will use backup helper classes to
+ * write to this file.
+ * @param newState An open, read/write ParcelFileDescriptor pointing to an
+ * empty file. The application should record the final backup
+ * state here after writing the requested data to the <code>data</code>
+ * output stream.
+ */
+ public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException;
+
+ /**
+ * The application is being restored from backup and should replace any
+ * existing data with the contents of the backup. The backup data is
+ * provided through the <code>data</code> parameter. Once
+ * the restore is finished, the application should write a representation of
+ * the final state to the <code>newState</code> file descriptor.
+ * <p>
+ * The application is responsible for properly erasing its old data and
+ * replacing it with the data supplied to this method. No "clear user data"
+ * operation will be performed automatically by the operating system. The
+ * exception to this is in the case of a failed restore attempt: if
+ * onRestore() throws an exception, the OS will assume that the
+ * application's data may now be in an incoherent state, and will clear it
+ * before proceeding.
+ *
+ * @param data A structured wrapper around an open, read-only
+ * file descriptor pointing to a full snapshot of the
+ * application's data. The application should consume every
+ * entity represented in this data stream.
+ * @param appVersionCode The
+ * {@link android.R.styleable#AndroidManifest_versionCode android:versionCode}
+ * value of the application that backed up this particular data set. This
+ * makes it possible for an application's agent to distinguish among any
+ * possible older data versions when asked to perform the restore
+ * operation.
+ * @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.
+ */
+ public abstract void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState)
+ throws IOException;
+
+
+ // ----- Core implementation -----
+
+ /** @hide */
+ public final IBinder onBind() {
+ return mBinder;
+ }
+
+ private final IBinder mBinder = new BackupServiceBinder().asBinder();
+
+ /** @hide */
+ public void attach(Context context) {
+ attachBaseContext(context);
+ }
+
+ // ----- IBackupService binder interface -----
+ private class BackupServiceBinder extends IBackupAgent.Stub {
+ private static final String TAG = "BackupServiceBinder";
+
+ public void doBackup(ParcelFileDescriptor oldState,
+ ParcelFileDescriptor data,
+ ParcelFileDescriptor newState,
+ 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());
+ try {
+ BackupAgent.this.onBackup(oldState, output, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ try {
+ callbackBinder.opComplete(token);
+ } catch (RemoteException e) {
+ // we'll time out anyway, so we're safe
+ }
+ }
+ }
+
+ public void doRestore(ParcelFileDescriptor data, int appVersionCode,
+ ParcelFileDescriptor newState,
+ 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, "doRestore() invoked");
+ BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
+ try {
+ BackupAgent.this.onRestore(input, appVersionCode, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw ex;
+ } 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/BackupAgentHelper.java b/core/java/android/app/backup/BackupAgentHelper.java
new file mode 100644
index 0000000..d47ca22
--- /dev/null
+++ b/core/java/android/app/backup/BackupAgentHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * A convenient {@link BackupAgent} wrapper class that automatically manages
+ * heterogeneous data sets within the backup data, each identified by a unique
+ * key prefix. When processing a backup or restore operation, the BackupAgentHelper
+ * dispatches to one or more installed {@link BackupHelper} objects, each
+ * of which is responsible for a defined subset of the data being processed.
+ * <p>
+ * An application will typically extend this class in its own
+ * backup agent. Then, within the agent's {@link BackupAgent#onCreate() onCreate()}
+ * method, it will call {@link #addHelper(String, BackupHelper) addHelper()} one or more times to
+ * install the handlers for each kind of data it wishes to manage within its backups.
+ * <p>
+ * The Android framework currently provides two predefined {@link BackupHelper} classes:</p>
+ * <ul><li>{@link FileBackupHelper} - Manages the backup and restore of entire files
+ * within an application's data directory hierarchy.</li>
+ * <li>{@link SharedPreferencesBackupHelper} - Manages the backup and restore of an
+ * application's {@link android.content.SharedPreferences} data.</li></ul>
+ * <p>
+ * An application can also implement its own helper classes to work within the
+ * {@link BackupAgentHelper} framework. See the {@link BackupHelper} interface
+ * documentation for details.
+ *
+ * @see BackupHelper
+ * @see FileBackupHelper
+ * @see SharedPreferencesBackupHelper
+ */
+public class BackupAgentHelper extends BackupAgent {
+ static final String TAG = "BackupAgentHelper";
+
+ BackupHelperDispatcher mDispatcher = new BackupHelperDispatcher();
+
+ /**
+ * Run the backup process on each of the configured handlers.
+ */
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ mDispatcher.performBackup(oldState, data, newState);
+ }
+
+ /**
+ * Run the restore process on each of the configured handlers.
+ */
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ mDispatcher.performRestore(data, appVersionCode, newState);
+ }
+
+ /** @hide */
+ public BackupHelperDispatcher getDispatcher() {
+ return mDispatcher;
+ }
+
+ /**
+ * Add a helper for a given data subset to the agent's configuration. Each helper
+ * must have a prefix string that is unique within this backup agent's set of
+ * helpers.
+ *
+ * @param keyPrefix A string used to disambiguate the various helpers within this agent
+ * @param helper A backup/restore helper object to be invoked during backup and restore
+ * operations.
+ */
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mDispatcher.addHelper(keyPrefix, helper);
+ }
+}
+
+
diff --git a/core/java/android/app/backup/BackupDataInput.java b/core/java/android/app/backup/BackupDataInput.java
new file mode 100644
index 0000000..43b920a
--- /dev/null
+++ b/core/java/android/app/backup/BackupDataInput.java
@@ -0,0 +1,194 @@
+/*
+ * 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 java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Provides the structured interface through which a {@link BackupAgent} reads
+ * information from the backup data set, via its
+ * {@link BackupAgent#onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}
+ * method. The data is presented as a set of "entities," each
+ * representing one named record as previously stored by the agent's
+ * {@link BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+ * onBackup()} implementation. An entity is composed of a descriptive header plus a
+ * byte array that holds the raw data saved in the remote backup.
+ * <p>
+ * The agent must consume every entity in the data stream, otherwise the
+ * restored state of the application will be incomplete.
+ * <h3>Example</h3>
+ * <p>
+ * A typical
+ * {@link BackupAgent#onRestore(BackupDataInput,int,ParcelFileDescriptor)
+ * onRestore()} implementation might be structured something like this:
+ * <pre>
+ * public void onRestore(BackupDataInput data, int appVersionCode,
+ * ParcelFileDescriptor newState) {
+ * while (data.readNextHeader()) {
+ * String key = data.getKey();
+ * int dataSize = data.getDataSize();
+ *
+ * if (key.equals(MY_BACKUP_KEY_ONE)) {
+ * // process this kind of record here
+ * byte[] buffer = new byte[dataSize];
+ * data.readEntityData(buffer, 0, dataSize); // reads the entire entity at once
+ *
+ * // now 'buffer' holds the raw data and can be processed however
+ * // the agent wishes
+ * processBackupKeyOne(buffer);
+ * } else if (key.equals(MY_BACKUP_KEY_TO_IGNORE) {
+ * // a key we recognize but wish to discard
+ * data.skipEntityData();
+ * } // ... etc.
+ * }
+ * }</pre>
+ */
+public class BackupDataInput {
+ int mBackupReader;
+
+ private EntityHeader mHeader = new EntityHeader();
+ private boolean mHeaderReady;
+
+ private static class EntityHeader {
+ String key;
+ int dataSize;
+ }
+
+ /** @hide */
+ public BackupDataInput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupReader = ctor(fd);
+ if (mBackupReader == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
+ }
+
+ /** @hide */
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupReader);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Extract the next entity header from the restore stream. After this method
+ * return success, the {@link #getKey()} and {@link #getDataSize()} methods can
+ * be used to inspect the entity that is now available for processing.
+ *
+ * @return <code>true</code> when there is an entity ready for consumption from the
+ * restore stream, <code>false</code> if the restore stream has been fully consumed.
+ * @throws IOException if an error occurred while reading the restore stream
+ */
+ public boolean readNextHeader() throws IOException {
+ int result = readNextHeader_native(mBackupReader, mHeader);
+ if (result == 0) {
+ // read successfully
+ mHeaderReady = true;
+ return true;
+ } else if (result > 0) {
+ // done
+ mHeaderReady = false;
+ return false;
+ } else {
+ // error
+ mHeaderReady = false;
+ throw new IOException("failed: 0x" + Integer.toHexString(result));
+ }
+ }
+
+ /**
+ * Report the key associated with the current entity in the restore stream
+ * @return the current entity's key string
+ * @throws IllegalStateException if the next record header has not yet been read
+ */
+ public String getKey() {
+ if (mHeaderReady) {
+ return mHeader.key;
+ } else {
+ throw new IllegalStateException("Entity header not read");
+ }
+ }
+
+ /**
+ * Report the size in bytes of the data associated with the current entity in the
+ * restore stream.
+ *
+ * @return The size of the record's raw data, in bytes
+ * @throws IllegalStateException if the next record header has not yet been read
+ */
+ public int getDataSize() {
+ if (mHeaderReady) {
+ return mHeader.dataSize;
+ } else {
+ throw new IllegalStateException("Entity header not read");
+ }
+ }
+
+ /**
+ * Read a record's raw data from the restore stream. The record's header must first
+ * have been processed by the {@link #readNextHeader()} method. Multiple calls to
+ * this method may be made in order to process the data in chunks; not all of it
+ * must be read in a single call. Once all of the raw data for the current entity
+ * has been read, further calls to this method will simply return zero.
+ *
+ * @param data An allocated byte array of at least 'size' bytes
+ * @param offset Offset within the 'data' array at which the data will be placed
+ * when read from the stream
+ * @param size The number of bytes to read in this pass
+ * @return The number of bytes of data read. Once all of the data for this entity
+ * has been read, further calls to this method will return zero.
+ * @throws IOException if an error occurred when trying to read the restore data stream
+ */
+ public int readEntityData(byte[] data, int offset, int size) throws IOException {
+ if (mHeaderReady) {
+ int result = readEntityData_native(mBackupReader, data, offset, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("Entity header not read");
+ }
+ }
+
+ /**
+ * Consume the current entity's data without extracting it into a buffer
+ * for further processing. This allows a {@link android.app.backup.BackupAgent} to
+ * efficiently discard obsolete or otherwise uninteresting records during the
+ * restore operation.
+ *
+ * @throws IOException if an error occurred when trying to read the restore data stream
+ */
+ public void skipEntityData() throws IOException {
+ if (mHeaderReady) {
+ skipEntityData_native(mBackupReader);
+ } else {
+ throw new IllegalStateException("Entity header not read");
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupReader);
+
+ private native int readNextHeader_native(int mBackupReader, EntityHeader entity);
+ private native int readEntityData_native(int mBackupReader, byte[] data, int offset, int size);
+ private native int skipEntityData_native(int mBackupReader);
+}
diff --git a/core/java/android/app/backup/BackupDataInputStream.java b/core/java/android/app/backup/BackupDataInputStream.java
new file mode 100644
index 0000000..94c7845
--- /dev/null
+++ b/core/java/android/app/backup/BackupDataInputStream.java
@@ -0,0 +1,118 @@
+/*
+ * 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 java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Provides an {@link java.io.InputStream}-like interface for accessing an
+ * entity's data during a restore operation. Used by {@link BackupHelper} classes within the {@link
+ * BackupAgentHelper} mechanism.
+ * <p>
+ * When {@link BackupHelper#restoreEntity(BackupDataInputStream) BackupHelper.restoreEntity()}
+ * is called, the current entity's header has already been read from the underlying
+ * {@link BackupDataInput}. The entity's key string and total data size are available
+ * through this class's {@link #getKey()} and {@link #size()} methods, respectively.
+ * <p class="note">
+ * <strong>Note:</strong> The caller should take care not to seek or close the underlying data
+ * source, nor read more than {@link #size()} bytes from the stream.</p>
+ *
+ * @see BackupAgentHelper
+ * @see BackupHelper
+ */
+public class BackupDataInputStream extends InputStream {
+
+ String key;
+ int dataSize;
+
+ BackupDataInput mData;
+ byte[] mOneByte;
+
+ /** @hide */
+ BackupDataInputStream(BackupDataInput data) {
+ mData = data;
+ }
+
+ /**
+ * Read one byte of entity data from the stream, returning it as
+ * an integer value. If more than {@link #size()} bytes of data
+ * are read from the stream, the output of this method is undefined.
+ *
+ * @return The byte read, or undefined if the end of the stream has been reached.
+ */
+ public int read() throws IOException {
+ byte[] one = mOneByte;
+ if (mOneByte == null) {
+ one = mOneByte = new byte[1];
+ }
+ mData.readEntityData(one, 0, 1);
+ return one[0];
+ }
+
+ /**
+ * Read up to {@code size} bytes of data into a byte array, beginning at position
+ * {@code offset} within the array.
+ *
+ * @param b Byte array into which the data will be read
+ * @param offset The data will be stored in {@code b} beginning at this index
+ * within the array.
+ * @param size The number of bytes to read in this operation. If insufficient
+ * data exists within the entity to fulfill this request, only as much data
+ * will be read as is available.
+ * @return The number of bytes of data read, or zero if all of the entity's
+ * data has already been read.
+ */
+ public int read(byte[] b, int offset, int size) throws IOException {
+ return mData.readEntityData(b, offset, size);
+ }
+
+ /**
+ * Read enough entity data into a byte array to fill the array.
+ *
+ * @param b Byte array to fill with data from the stream. If the stream does not
+ * have sufficient data to fill the array, then the contents of the remainder of
+ * the array will be undefined.
+ * @return The number of bytes of data read, or zero if all of the entity's
+ * data has already been read.
+ */
+ public int read(byte[] b) throws IOException {
+ return mData.readEntityData(b, 0, b.length);
+ }
+
+ /**
+ * Report the key string associated with this entity within the backup data set.
+ *
+ * @return The key string for this entity, equivalent to calling
+ * {@link BackupDataInput#getKey()} on the underlying {@link BackupDataInput}.
+ */
+ public String getKey() {
+ return this.key;
+ }
+
+ /**
+ * Report the total number of bytes of data available for the current entity.
+ *
+ * @return The number of data bytes available, equivalent to calling
+ * {@link BackupDataInput#getDataSize()} on the underlying {@link BackupDataInput}.
+ */
+ public int size() {
+ return this.dataSize;
+ }
+}
+
+
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
new file mode 100644
index 0000000..22668b6
--- /dev/null
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -0,0 +1,131 @@
+/*
+ * 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.os.ParcelFileDescriptor;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Provides the structured interface through which a {@link BackupAgent} commits
+ * information to the backup data set, via its {@link
+ * BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+ * onBackup()} method. Data written for backup is presented
+ * as a set of "entities," key/value pairs in which each binary data record "value" is
+ * named with a string "key."
+ * <p>
+ * To commit a data record to the backup transport, the agent's
+ * {@link BackupAgent#onBackup(ParcelFileDescriptor,BackupDataOutput,ParcelFileDescriptor)
+ * onBackup()} method first writes an "entity header" that supplies the key string for the record
+ * and the total size of the binary value for the record. After the header has been
+ * written, the agent then writes the binary entity value itself. The entity value can
+ * be written in multiple chunks if desired, as long as the total count of bytes written
+ * matches what was supplied to {@link #writeEntityHeader(String, int) writeEntityHeader()}.
+ * <p>
+ * Entity key strings are considered to be unique within a given application's backup
+ * data set. If a backup agent writes a new entity under an existing key string, its value will
+ * replace any previous value in the transport's remote data store. You can remove a record
+ * entirely from the remote data set by writing a new entity header using the
+ * existing record's key, but supplying a negative <code>dataSize</code> parameter.
+ * When you do so, the agent does not need to call {@link #writeEntityData(byte[], int)}.
+ * <h3>Example</h3>
+ * <p>
+ * Here is an example illustrating a way to back up the value of a String variable
+ * called <code>mStringToBackUp</code>:
+ * <pre>
+ * static final String MY_STRING_KEY = "storedstring";
+ *
+ * public void {@link BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor) onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)}
+ * throws IOException {
+ * ...
+ * byte[] stringBytes = mStringToBackUp.getBytes();
+ * data.writeEntityHeader(MY_STRING_KEY, stringBytes.length);
+ * data.writeEntityData(stringBytes, stringBytes.length);
+ * ...
+ * }</pre>
+ *
+ * @see BackupAgent
+ */
+public class BackupDataOutput {
+ int mBackupWriter;
+
+ /** @hide */
+ public BackupDataOutput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupWriter = ctor(fd);
+ if (mBackupWriter == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
+ }
+
+ /**
+ * Mark the beginning of one record in the backup data stream. This must be called before
+ * {@link #writeEntityData}.
+ * @param key A string key that uniquely identifies the data record within the application
+ * @param dataSize The size in bytes of this record's data. Passing a dataSize
+ * of -1 indicates that the record under this key should be deleted.
+ * @return The number of bytes written to the backup stream
+ * @throws IOException if the write failed
+ */
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ /**
+ * Write a chunk of data under the current entity to the backup transport.
+ * @param data A raw data buffer to send
+ * @param size The number of bytes to be sent in this chunk
+ * @return the number of bytes written
+ * @throws IOException if the write failed
+ */
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ int result = writeEntityData_native(mBackupWriter, data, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ /** @hide */
+ public void setKeyPrefix(String keyPrefix) {
+ setKeyPrefix_native(mBackupWriter, keyPrefix);
+ }
+
+ /** @hide */
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupWriter);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupWriter);
+
+ private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize);
+ private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size);
+ private native static void setKeyPrefix_native(int mBackupWriter, String keyPrefix);
+}
+
diff --git a/core/java/android/app/backup/BackupHelper.java b/core/java/android/app/backup/BackupHelper.java
new file mode 100644
index 0000000..e3f0d54
--- /dev/null
+++ b/core/java/android/app/backup/BackupHelper.java
@@ -0,0 +1,105 @@
+/*
+ * 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.os.ParcelFileDescriptor;
+
+/**
+ * Defines the calling interface that {@link BackupAgentHelper} uses
+ * when dispatching backup and restore operations to the installed helpers.
+ * Applications can define and install their own helpers as well as using those
+ * provided as part of the Android framework.
+ * <p>
+ * Although multiple helper objects may be installed simultaneously, each helper
+ * is responsible only for handling its own data, and will not see entities
+ * created by other components within the backup system. Invocations of multiple
+ * helpers are performed sequentially by the {@link BackupAgentHelper}, with each
+ * helper given a chance to access its own saved state from within the state record
+ * produced during the previous backup operation.
+ *
+ * @see BackupAgentHelper
+ * @see FileBackupHelper
+ * @see SharedPreferencesBackupHelper
+ */
+public interface BackupHelper {
+ /**
+ * Based on <code>oldState</code>, determine which of the files from the
+ * application's data directory need to be backed up, write them to
+ * <code>data</code>, and fill in <code>newState</code> with the state as it
+ * exists now.
+ * <p>
+ * Implementing this method is much like implementing
+ * {@link BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)
+ * onBackup()} &mdash; the method parameters are the same. When this method is invoked the
+ * {@code oldState} descriptor points to the beginning of the state data
+ * written during this helper's previous backup operation, and the {@code newState}
+ * descriptor points to the file location at which the helper should write its
+ * new state after performing the backup operation.
+ * <p class="note">
+ * <strong>Note:</strong> The helper should not close or seek either the {@code oldState} or
+ * the {@code newState} file descriptors.</p>
+ *
+ * @param oldState An open, read-only {@link android.os.ParcelFileDescriptor} pointing to the
+ * last backup state provided by the application. May be
+ * <code>null</code>, in which case no prior state is being
+ * provided and the application should perform a full backup.
+ * @param data An open, read/write {@link BackupDataOutput}
+ * pointing to the backup data destination.
+ * Typically the application will use backup helper classes to
+ * write to this file.
+ * @param newState An open, read/write {@link android.os.ParcelFileDescriptor} pointing to an
+ * empty file. The application should record the final backup
+ * state here after writing the requested data to the <code>data</code>
+ * output stream.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState);
+
+ /**
+ * Called by {@link android.app.backup.BackupAgentHelper BackupAgentHelper}
+ * to restore a single entity from the restore data set. This method will be
+ * called for each entity in the data set that belongs to this handler.
+ * <p class="note">
+ * <strong>Note:</strong> Do not close the <code>data</code> stream. Do not read more than
+ * {@link android.app.backup.BackupDataInputStream#size() size()} bytes from
+ * <code>data</code>.</p>
+ *
+ * @param data An open {@link BackupDataInputStream} from which the backup data can be read.
+ */
+ public void restoreEntity(BackupDataInputStream data);
+
+ /**
+ * Called by {@link android.app.backup.BackupAgentHelper BackupAgentHelper}
+ * after a restore operation to write the backup state file corresponding to
+ * the data as processed by the helper. The data written here will be
+ * available to the helper during the next call to its
+ * {@link #performBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)
+ * performBackup()} method.
+ * <p>
+ * This method will be called even if the handler's
+ * {@link #restoreEntity(BackupDataInputStream) restoreEntity()} method was never invoked during
+ * the restore operation.
+ * <p class="note">
+ * <strong>Note:</strong> The helper should not close or seek the {@code newState}
+ * file descriptor.</p>
+ *
+ * @param newState A {@link android.os.ParcelFileDescriptor} to which the new state will be
+ * written.
+ */
+ public void writeNewStateDescription(ParcelFileDescriptor newState);
+}
+
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/app/backup/BackupHelperDispatcher.java
index 6ccb83e..5466db5 100644
--- a/core/java/android/backup/BackupHelperDispatcher.java
+++ b/core/java/android/app/backup/BackupHelperDispatcher.java
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
import java.io.FileDescriptor;
-import java.util.TreeMap;
+import java.io.IOException;
import java.util.Map;
+import java.util.TreeMap;
/** @hide */
public class BackupHelperDispatcher {
@@ -138,7 +136,7 @@ public class BackupHelperDispatcher {
// Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
for (BackupHelper helper: mHelpers.values()) {
- helper.writeRestoreSnapshot(newState);
+ helper.writeNewStateDescription(newState);
}
}
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
new file mode 100644
index 0000000..52dd707
--- /dev/null
+++ b/core/java/android/app/backup/BackupManager.java
@@ -0,0 +1,175 @@
+/*
+ * 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.app.backup.RestoreSession;
+import android.app.backup.IBackupManager;
+import android.app.backup.IRestoreSession;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * The interface through which an application interacts with the Android backup service to
+ * request backup and restore operations.
+ * Applications instantiate it using the constructor and issue calls through that instance.
+ * <p>
+ * When an application has made changes to data which should be backed up, a
+ * call to {@link #dataChanged()} will notify the backup service. The system
+ * will then schedule a backup operation to occur in the near future. Repeated
+ * calls to {@link #dataChanged()} have no further effect until the backup
+ * operation actually occurs.
+ * <p>
+ * A backup or restore operation for your application begins when the system launches the
+ * {@link android.app.backup.BackupAgent} subclass you've declared in your manifest. See the
+ * documentation for {@link android.app.backup.BackupAgent} for a detailed description
+ * of how the operation then proceeds.
+ * <p>
+ * Several attributes affecting the operation of the backup and restore mechanism
+ * can be set on the <code><a
+ * href="{@docRoot}guide/topics/manifest/application-element.html">&lt;application&gt;</a></code>
+ * tag in your application's AndroidManifest.xml file.
+ *
+ * @attr ref android.R.styleable#AndroidManifestApplication_allowBackup
+ * @attr ref android.R.styleable#AndroidManifestApplication_backupAgent
+ * @attr ref android.R.styleable#AndroidManifestApplication_killAfterRestore
+ * @attr ref android.R.styleable#AndroidManifestApplication_restoreAnyVersion
+ */
+public class BackupManager {
+ private static final String TAG = "BackupManager";
+
+ private Context mContext;
+ private static IBackupManager sService;
+
+ private static void checkServiceBinder() {
+ if (sService == null) {
+ sService = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
+ }
+
+ /**
+ * Constructs a BackupManager object through which the application can
+ * communicate with the Android backup system.
+ *
+ * @param context The {@link android.content.Context} that was provided when
+ * one of your application's {@link android.app.Activity Activities}
+ * was created.
+ */
+ public BackupManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Notifies the Android backup system that your application wishes to back up
+ * new changes to its data. A backup operation using your application's
+ * {@link android.app.backup.BackupAgent} subclass will be scheduled when you
+ * call this method.
+ */
+ public void dataChanged() {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(mContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged() couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Convenience method for callers who need to indicate that some other package
+ * needs a backup pass. This can be useful in the case of groups of packages
+ * that share a uid.
+ * <p>
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the argument does not run under the same uid
+ * as the caller.
+ *
+ * @param packageName The package name identifying the application to back up.
+ */
+ public static void dataChanged(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(packageName);
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged(pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Restore the calling application from backup. The data will be restored from the
+ * current backup dataset if the application has stored data there, or from
+ * the dataset used during the last full device setup operation if the current
+ * backup dataset has no matching data. If no backup data exists for this application
+ * in either source, a nonzero value will be returned.
+ *
+ * <p>If this method returns zero (meaning success), the OS will attempt to retrieve
+ * a backed-up dataset from the remote transport, instantiate the application's
+ * backup agent, and pass the dataset to the agent's
+ * {@link android.app.backup.BackupAgent#onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor) onRestore()}
+ * method.
+ *
+ * @param observer The {@link RestoreObserver} to receive callbacks during the restore
+ * operation. This must not be null.
+ *
+ * @return Zero on success; nonzero on error.
+ */
+ public int requestRestore(RestoreObserver observer) {
+ int result = -1;
+ checkServiceBinder();
+ if (sService != null) {
+ RestoreSession session = null;
+ try {
+ String transport = sService.getCurrentTransport();
+ IRestoreSession binder = sService.beginRestoreSession(transport);
+ session = new RestoreSession(mContext, binder);
+ result = session.restorePackage(mContext.getPackageName(), observer);
+ } catch (RemoteException e) {
+ Log.w(TAG, "restoreSelf() unable to contact service");
+ } finally {
+ if (session != null) {
+ session.endRestoreSession();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Begin the process of restoring data from backup. See the
+ * {@link android.app.backup.RestoreSession} class for documentation on that process.
+ * @hide
+ */
+ public RestoreSession beginRestoreSession() {
+ RestoreSession session = null;
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ String transport = sService.getCurrentTransport();
+ IRestoreSession binder = sService.beginRestoreSession(transport);
+ session = new RestoreSession(mContext, binder);
+ } catch (RemoteException e) {
+ Log.w(TAG, "beginRestoreSession() couldn't connect");
+ }
+ }
+ return session;
+ }
+}
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/app/backup/FileBackupHelper.java
index dacfc8f..c6a523a 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/app/backup/FileBackupHelper.java
@@ -14,16 +14,27 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
-import java.io.FileDescriptor;
-/** @hide */
+/**
+ * A helper class that can be used in conjunction with
+ * {@link android.app.backup.BackupAgentHelper} to manage the backup of a set of
+ * files. Whenever backup is performed, all files changed since the last backup
+ * will be saved in their entirety. When backup first occurs,
+ * every file in the list provided to {@link #FileBackupHelper} will be backed up.
+ * <p>
+ * During restore, if the helper encounters data for a file that was not
+ * specified when the FileBackupHelper object was constructed, that data
+ * will be ignored.
+ * <p class="note"><strong>Note:</strong> This should be
+ * used only with small configuration files, not large binary files.
+ */
public class FileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "FileBackupHelper";
private static final boolean DEBUG = false;
@@ -32,6 +43,13 @@ public class FileBackupHelper extends FileBackupHelperBase implements BackupHelp
File mFilesDir;
String[] mFiles;
+ /**
+ * Construct a helper to manage backup/restore of entire files within the
+ * application's data directory hierarchy.
+ *
+ * @param context The backup agent's Context object
+ * @param files A list of the files to be backed up or restored.
+ */
public FileBackupHelper(Context context, String... files) {
super(context);
@@ -41,9 +59,16 @@ public class FileBackupHelper extends FileBackupHelperBase implements BackupHelp
}
/**
- * Based on oldState, determine which of the files from the application's data directory
- * need to be backed up, write them to the data stream, and fill in newState with the
- * state as it exists now.
+ * Based on <code>oldState</code>, determine which of the files from the
+ * application's data directory need to be backed up, write them to the data
+ * stream, and fill in <code>newState</code> with the state as it exists
+ * now. When <code>oldState</code> is <code>null</code>, all the files will
+ * be backed up.
+ * <p>
+ * This should only be called directly from within the {@link BackupAgentHelper}
+ * implementation. See
+ * {@link android.app.backup.BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
+ * for a description of parameter meanings.
*/
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
@@ -60,6 +85,12 @@ public class FileBackupHelper extends FileBackupHelperBase implements BackupHelp
performBackup_checked(oldState, data, newState, fullPaths, files);
}
+ /**
+ * Restore one record [representing a single file] from the restore dataset.
+ * <p>
+ * This should only be called directly from within the {@link BackupAgentHelper}
+ * implementation.
+ */
public void restoreEntity(BackupDataInputStream data) {
if (DEBUG) Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
String key = data.getKey();
diff --git a/core/java/android/backup/FileBackupHelperBase.java b/core/java/android/app/backup/FileBackupHelperBase.java
index 03ae476..1e3158f 100644
--- a/core/java/android/backup/FileBackupHelperBase.java
+++ b/core/java/android/app/backup/FileBackupHelperBase.java
@@ -14,19 +14,20 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import java.io.InputStream;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
+/**
+ * Base class for the {@link android.app.backup.FileBackupHelper} implementation.
+ */
class FileBackupHelperBase {
- private static final String TAG = "RestoreHelperBase";
+ private static final String TAG = "FileBackupHelperBase";
int mPtr;
Context mContext;
@@ -46,8 +47,8 @@ class FileBackupHelperBase {
}
/**
- * Check the parameters so the native code doens't have to throw all the exceptions
- * since it's easier to do that from java.
+ * Check the parameters so the native code doesn't have to throw all the exceptions
+ * since it's easier to do that from Java.
*/
static void performBackup_checked(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState, String[] files, String[] keys) {
@@ -80,18 +81,14 @@ class FileBackupHelperBase {
}
}
- void writeFile(File f, InputStream in) {
- if (!(in instanceof BackupDataInputStream)) {
- throw new IllegalStateException("input stream must be a BackupDataInputStream");
- }
+ void writeFile(File f, BackupDataInputStream in) {
int result = -1;
// Create the enclosing directory.
File parent = f.getParentFile();
parent.mkdirs();
- result = writeFile_native(mPtr, f.getAbsolutePath(),
- ((BackupDataInputStream)in).mData.mBackupReader);
+ result = writeFile_native(mPtr, f.getAbsolutePath(), in.mData.mBackupReader);
if (result != 0) {
// Bail on this entity. Only log one failure per helper object.
if (!mExceptionLogged) {
@@ -103,7 +100,7 @@ class FileBackupHelperBase {
}
}
- public void writeRestoreSnapshot(ParcelFileDescriptor fd) {
+ public void writeNewStateDescription(ParcelFileDescriptor fd) {
int result = writeSnapshot_native(mPtr, fd.getFileDescriptor());
// TODO: Do something with the error.
}
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 9d181be..23d6351 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
-import android.backup.IRestoreSession;
+import android.app.backup.IRestoreSession;
/**
* Direct interface to the Backup Manager Service that applications invoke on. The only
* operation currently needed is a simple notification that the app has made changes to
* data it wishes to back up, so the system should run a backup pass.
*
- * Apps will use the {@link android.backup.BackupManager} class rather than going through
+ * Apps will use the {@link android.app.backup.BackupManager} class rather than going through
* this Binder interface directly.
*
* {@hide}
@@ -62,6 +62,12 @@ interface IBackupManager {
void agentDisconnected(String packageName);
/**
+ * Notify the Backup Manager Service that an application being installed will
+ * need a data-restore pass. This method is only invoked by the Package Manager.
+ */
+ void restoreAtInstall(String packageName, int token);
+
+ /**
* Enable/disable the backup service entirely. When disabled, no backup
* or restore operations will take place. Data-changed notifications will
* still be observed and collected, however, so that changes made while the
@@ -73,6 +79,21 @@ interface IBackupManager {
void setBackupEnabled(boolean isEnabled);
/**
+ * Enable/disable automatic restore of application data at install time. When
+ * enabled, installation of any package will involve the Backup Manager. If data
+ * exists for the newly-installed package, either from the device's current [enabled]
+ * backup dataset or from the restore set used in the last wholesale restore operation,
+ * that data will be supplied to the new package's restore agent before the package
+ * is made generally available for launch.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param doAutoRestore When true, enables the automatic app-data restore facility. When
+ * false, this facility will be disabled.
+ */
+ void setAutoRestore(boolean doAutoRestore);
+
+ /**
* Indicate that any necessary one-time provisioning has occurred.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
@@ -130,4 +151,14 @@ interface IBackupManager {
* @return An interface to the restore session, or null on error.
*/
IRestoreSession beginRestoreSession(String transportID);
+
+ /**
+ * Notify the backup manager that a BackupAgent has completed the operation
+ * corresponding to the given token.
+ *
+ * @param token The transaction token passed to a BackupAgent's doBackup() or
+ * doRestore() method.
+ * {@hide}
+ */
+ void opComplete(int token);
}
diff --git a/core/java/android/backup/IRestoreObserver.aidl b/core/java/android/app/backup/IRestoreObserver.aidl
index 59e59fc..449765e 100644
--- a/core/java/android/backup/IRestoreObserver.aidl
+++ b/core/java/android/app/backup/IRestoreObserver.aidl
@@ -14,14 +14,28 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
+
+import android.app.backup.RestoreSet;
/**
* Callback class for receiving progress reports during a restore operation.
*
* @hide
*/
-interface IRestoreObserver {
+oneway interface IRestoreObserver {
+ /**
+ * Supply a list of the restore datasets available from the current transport. This
+ * method is invoked as a callback following the application's use of the
+ * {@link android.app.backup.IRestoreSession.getAvailableRestoreSets} method.
+ *
+ * @param result An array of {@link android.app.backup.RestoreSet RestoreSet} objects
+ * describing all of the available datasets that are candidates for restoring to
+ * the current device. If no applicable datasets exist, {@code result} will be
+ * {@code null}.
+ */
+ void restoreSetsAvailable(in RestoreSet[] result);
+
/**
* The restore operation has begun.
*
@@ -32,13 +46,14 @@ interface IRestoreObserver {
/**
* An indication of which package is being restored currently, out of the
- * total number provided in the restoreStarting() callback. This method
- * is not guaranteed to be called.
+ * total number provided in the {@link #restoreStarting(int numPackages)} callback.
+ * This method is not guaranteed to be called.
*
* @param nowBeingRestored The index, between 1 and the numPackages parameter
* to the restoreStarting() callback, of the package now being restored.
+ * @param currentPackage The name of the package now being restored.
*/
- void onUpdate(int nowBeingRestored);
+ void onUpdate(int nowBeingRestored, String curentPackage);
/**
* The restore operation has completed.
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/app/backup/IRestoreSession.aidl
index fd40d98..1dddbb0 100644
--- a/core/java/android/backup/IRestoreSession.aidl
+++ b/core/java/android/app/backup/IRestoreSession.aidl
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
-import android.backup.RestoreSet;
-import android.backup.IRestoreObserver;
+import android.app.backup.RestoreSet;
+import android.app.backup.IRestoreObserver;
/**
* Binder interface used by clients who wish to manage a restore operation. Every
@@ -29,17 +29,19 @@ interface IRestoreSession {
/**
* Ask the current transport what the available restore sets are.
*
- * @return A bundle containing two elements: an int array under the key
- * "tokens" whose entries are a transport-private identifier for each backup set;
- * and a String array under the key "names" whose entries are the user-meaningful
- * text corresponding to the backup sets at each index in the tokens array.
+ * @param observer This binder points to an object whose onRestoreSetsAvailable()
+ * method will be called to supply the results of the transport's lookup.
+ * @return Zero on success; nonzero on error. The observer will only receive a
+ * result callback if this method returned zero.
*/
- RestoreSet[] getAvailableRestoreSets();
+ int getAvailableRestoreSets(IRestoreObserver observer);
/**
* Restore the given set onto the device, replacing the current data of any app
* contained in the restore set with the data previously backed up.
*
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
* @return Zero on success; nonzero on error. The observer will only receive
* progress callbacks if this method returned zero.
* @param token The token from {@link getAvailableRestoreSets()} corresponding to
@@ -47,7 +49,24 @@ interface IRestoreSession {
* @param observer If non-null, this binder points to an object that will receive
* progress callbacks during the restore operation.
*/
- int performRestore(long token, IRestoreObserver observer);
+ int restoreAll(long token, IRestoreObserver observer);
+
+ /**
+ * Restore a single application from backup. The data will be restored from the
+ * current backup dataset if the given package has stored data there, or from
+ * the dataset used during the last full device setup operation if the current
+ * backup dataset has no matching data. If no backup data exists for this package
+ * in either source, a nonzero value will be returned.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param packageName The name of the package whose data to restore. If this is
+ * not the name of the caller's own package, then the android.permission.BACKUP
+ * permission must be held.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ int restorePackage(in String packageName, IRestoreObserver observer);
/**
* End this restore session. After this method is called, the IRestoreSession binder
diff --git a/core/java/android/app/backup/RestoreObserver.java b/core/java/android/app/backup/RestoreObserver.java
new file mode 100644
index 0000000..dbddb78
--- /dev/null
+++ b/core/java/android/app/backup/RestoreObserver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import java.lang.String;
+import android.app.backup.RestoreSet;
+
+/**
+ * Callback class for receiving progress reports during a restore operation. These
+ * methods will all be called on your application's main thread.
+ */
+public abstract class RestoreObserver {
+ /**
+ * Supply a list of the restore datasets available from the current transport. This
+ * method is invoked as a callback following the application's use of the
+ * {@link android.app.backup.IRestoreSession.getAvailableRestoreSets} method.
+ *
+ * @param result An array of {@link android.app.backup.RestoreSet RestoreSet} objects
+ * describing all of the available datasets that are candidates for restoring to
+ * the current device. If no applicable datasets exist, {@code result} will be
+ * {@code null}.
+ *
+ * @hide
+ */
+ public void restoreSetsAvailable(RestoreSet[] result) {
+ }
+
+ /**
+ * The restore operation has begun.
+ *
+ * @param numPackages The total number of packages being processed in
+ * this restore operation.
+ */
+ public void restoreStarting(int numPackages) {
+ }
+
+ /**
+ * An indication of which package is being restored currently, out of the
+ * total number provided in the {@link #restoreStarting(int)} callback. This method
+ * is not guaranteed to be called: if the transport is unable to obtain
+ * data for one or more of the requested packages, no onUpdate() call will
+ * occur for those packages.
+ *
+ * @param nowBeingRestored The index, between 1 and the numPackages parameter
+ * to the {@link #restoreStarting(int)} callback, of the package now being
+ * restored. This may be non-monotonic; it is intended purely as a rough
+ * indication of the backup manager's progress through the overall restore process.
+ * @param currentPackage The name of the package now being restored.
+ */
+ public void onUpdate(int nowBeingRestored, String currentPackage) {
+ }
+
+ /**
+ * The restore process has completed. This method will always be called,
+ * even if no individual package restore operations were attempted.
+ *
+ * @param error Zero on success; a nonzero error code if the restore operation
+ * as a whole failed.
+ */
+ public void restoreFinished(int error) {
+ }
+}
diff --git a/core/java/android/app/backup/RestoreSession.java b/core/java/android/app/backup/RestoreSession.java
new file mode 100644
index 0000000..24ddb99
--- /dev/null
+++ b/core/java/android/app/backup/RestoreSession.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.app.backup.RestoreObserver;
+import android.app.backup.RestoreSet;
+import android.app.backup.IRestoreObserver;
+import android.app.backup.IRestoreSession;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Interface for managing a restore session.
+ * @hide
+ */
+public class RestoreSession {
+ static final String TAG = "RestoreSession";
+
+ final Context mContext;
+ IRestoreSession mBinder;
+ RestoreObserverWrapper mObserver = null;
+
+ /**
+ * Ask the current transport what the available restore sets are.
+ *
+ * @param observer a RestoreObserver object whose restoreSetsAvailable() method will
+ * be called on the application's main thread in order to supply the results of
+ * the restore set lookup by the backup transport. This parameter must not be
+ * null.
+ * @return Zero on success, nonzero on error. The observer's restoreSetsAvailable()
+ * method will only be called if this method returned zero.
+ */
+ public int getAvailableRestoreSets(RestoreObserver observer) {
+ int err = -1;
+ RestoreObserverWrapper obsWrapper = new RestoreObserverWrapper(mContext, observer);
+ try {
+ err = mBinder.getAvailableRestoreSets(obsWrapper);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to get available sets");
+ }
+ return err;
+ }
+
+ /**
+ * Restore the given set onto the device, replacing the current data of any app
+ * contained in the restore set with the data previously backed up.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param token The token from {@link #getAvailableRestoreSets()} corresponding to
+ * the restore set that should be used.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ public int restoreAll(long token, RestoreObserver observer) {
+ int err = -1;
+ if (mObserver != null) {
+ Log.d(TAG, "restoreAll() called during active restore");
+ return -1;
+ }
+ mObserver = new RestoreObserverWrapper(mContext, observer);
+ try {
+ err = mBinder.restoreAll(token, mObserver);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to restore");
+ }
+ return err;
+ }
+
+ /**
+ * Restore a single application from backup. The data will be restored from the
+ * current backup dataset if the given package has stored data there, or from
+ * the dataset used during the last full device setup operation if the current
+ * backup dataset has no matching data. If no backup data exists for this package
+ * in either source, a nonzero value will be returned.
+ *
+ * @return Zero on success; nonzero on error. The observer will only receive
+ * progress callbacks if this method returned zero.
+ * @param packageName The name of the package whose data to restore. If this is
+ * not the name of the caller's own package, then the android.permission.BACKUP
+ * permission must be held.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ public int restorePackage(String packageName, RestoreObserver observer) {
+ int err = -1;
+ if (mObserver != null) {
+ Log.d(TAG, "restorePackage() called during active restore");
+ return -1;
+ }
+ mObserver = new RestoreObserverWrapper(mContext, observer);
+ try {
+ err = mBinder.restorePackage(packageName, mObserver);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to restore package");
+ }
+ return err;
+ }
+
+ /**
+ * End this restore session. After this method is called, the RestoreSession
+ * object is no longer valid.
+ *
+ * <p><b>Note:</b> The caller <i>must</i> invoke this method to end the restore session,
+ * even if {@link #restorePackage(String, RestoreObserver)} failed.
+ */
+ public void endRestoreSession() {
+ try {
+ mBinder.endRestoreSession();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Can't contact server to get available sets");
+ } finally {
+ mBinder = null;
+ }
+ }
+
+ /*
+ * Nonpublic implementation here
+ */
+
+ RestoreSession(Context context, IRestoreSession binder) {
+ mContext = context;
+ mBinder = binder;
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This serializes the restore
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ private class RestoreObserverWrapper extends IRestoreObserver.Stub {
+ final Handler mHandler;
+ final RestoreObserver mAppObserver;
+
+ static final int MSG_RESTORE_STARTING = 1;
+ static final int MSG_UPDATE = 2;
+ static final int MSG_RESTORE_FINISHED = 3;
+ static final int MSG_RESTORE_SETS_AVAILABLE = 4;
+
+ RestoreObserverWrapper(Context context, RestoreObserver appObserver) {
+ mHandler = new Handler(context.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RESTORE_STARTING:
+ mAppObserver.restoreStarting(msg.arg1);
+ break;
+ case MSG_UPDATE:
+ mAppObserver.onUpdate(msg.arg1, (String)msg.obj);
+ break;
+ case MSG_RESTORE_FINISHED:
+ mAppObserver.restoreFinished(msg.arg1);
+ break;
+ case MSG_RESTORE_SETS_AVAILABLE:
+ mAppObserver.restoreSetsAvailable((RestoreSet[])msg.obj);
+ break;
+ }
+ }
+ };
+ mAppObserver = appObserver;
+ }
+
+ // Binder calls into this object just enqueue on the main-thread handler
+ public void restoreSetsAvailable(RestoreSet[] result) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_RESTORE_SETS_AVAILABLE, result));
+ }
+
+ public void restoreStarting(int numPackages) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_RESTORE_STARTING, numPackages, 0));
+ }
+
+ public void onUpdate(int nowBeingRestored, String currentPackage) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_UPDATE, nowBeingRestored, 0, currentPackage));
+ }
+
+ public void restoreFinished(int error) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_RESTORE_FINISHED, error, 0));
+ }
+ }
+}
diff --git a/core/java/android/backup/RestoreSet.aidl b/core/java/android/app/backup/RestoreSet.aidl
index 42e77bf..ab1b125 100644
--- a/core/java/android/backup/RestoreSet.aidl
+++ b/core/java/android/app/backup/RestoreSet.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
parcelable RestoreSet; \ No newline at end of file
diff --git a/core/java/android/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index eeca148..0431977 100644
--- a/core/java/android/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.backup;
+package android.app.backup;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
new file mode 100644
index 0000000..23b1703
--- /dev/null
+++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
@@ -0,0 +1,126 @@
+/*
+ * 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.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * A helper class that can be used in conjunction with
+ * {@link android.app.backup.BackupAgentHelper} to manage the backup of
+ * {@link android.content.SharedPreferences}. Whenever a backup is performed, it
+ * will back up all named shared preferences that have changed since the last
+ * backup operation.
+ * <p>
+ * To use this class, the application's backup agent class should extend
+ * {@link android.app.backup.BackupAgentHelper}. Then, in the agent's
+ * {@link BackupAgent#onCreate()} method, an instance of this class should be
+ * allocated and installed as a backup/restore handler within the BackupAgentHelper
+ * framework. For example, an agent supporting backup and restore for
+ * an application with two groups of {@link android.content.SharedPreferences}
+ * data might look something like this:
+ * <pre>
+ * import android.app.backup.BackupAgentHelper;
+ * import android.app.backup.SharedPreferencesBackupHelper;
+ *
+ * public class MyBackupAgent extends BackupAgentHelper {
+ * // The names of the SharedPreferences groups that the application maintains. These
+ * // are the same strings that are passed to {@link Context#getSharedPreferences(String, int)}.
+ * static final String PREFS_DISPLAY = "displayprefs";
+ * static final String PREFS_SCORES = "highscores";
+ *
+ * // An arbitrary string used within the BackupAgentHelper implementation to
+ * // identify the SharedPreferenceBackupHelper's data.
+ * static final String MY_PREFS_BACKUP_KEY = "myprefs";
+ *
+ * // Simply allocate a helper and install it
+ * void onCreate() {
+ * SharedPreferencesBackupHelper helper =
+ * new SharedPreferencesBackupHelper(this, PREFS_DISPLAY, PREFS_SCORES);
+ * addHelper(MY_PREFS_BACKUP_KEY, helper);
+ * }
+ * }</pre>
+ * <p>
+ * No further implementation is needed; the {@link BackupAgentHelper} mechanism automatically
+ * dispatches the
+ * {@link BackupAgent#onBackup(android.os.ParcelFileDescriptor, BackupDataOutput, android.os.ParcelFileDescriptor) BackupAgent.onBackup()}
+ * and
+ * {@link BackupAgent#onRestore(BackupDataInput, int, android.os.ParcelFileDescriptor) BackupAgent.onRestore()}
+ * callbacks to the SharedPreferencesBackupHelper as appropriate.
+ */
+public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
+ private static final String TAG = "SharedPreferencesBackupHelper";
+ private static final boolean DEBUG = false;
+
+ private Context mContext;
+ private String[] mPrefGroups;
+
+ /**
+ * Construct a helper for backing up and restoring the
+ * {@link android.content.SharedPreferences} under the given names.
+ *
+ * @param context The application {@link android.content.Context}
+ * @param prefGroups The names of each {@link android.content.SharedPreferences} file to
+ * back up
+ */
+ public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
+ super(context);
+
+ mContext = context;
+ mPrefGroups = prefGroups;
+ }
+
+ /**
+ * Backs up the configured {@link android.content.SharedPreferences} groups.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ Context context = mContext;
+
+ // make filenames for the prefGroups
+ String[] prefGroups = mPrefGroups;
+ final int N = prefGroups.length;
+ String[] files = new String[N];
+ for (int i=0; i<N; i++) {
+ files[i] = context.getSharedPrefsFile(prefGroups[i]).getAbsolutePath();
+ }
+
+ // go
+ performBackup_checked(oldState, data, newState, files, prefGroups);
+ }
+
+ /**
+ * Restores one entity from the restore data stream to its proper shared
+ * preferences file store.
+ */
+ public void restoreEntity(BackupDataInputStream data) {
+ Context context = mContext;
+
+ String key = data.getKey();
+ if (DEBUG) Log.d(TAG, "got entity '" + key + "' size=" + data.size());
+
+ if (isKeyInList(key, mPrefGroups)) {
+ File f = context.getSharedPrefsFile(key).getAbsoluteFile();
+ writeFile(f, data);
+ }
+ }
+}
+
diff --git a/core/java/android/app/backup/package.html b/core/java/android/app/backup/package.html
new file mode 100644
index 0000000..e2518ec
--- /dev/null
+++ b/core/java/android/app/backup/package.html
@@ -0,0 +1,24 @@
+<HTML>
+<BODY>
+<p>Contains the backup and restore functionality available to
+applications. If a user wipes the data on their device or upgrades to a new Android-powered
+device, all applications that have enabled backup will restore the user's previous data and/or
+preferences. {@more} All backup management is controlled through
+{@link android.app.backup.BackupManager}. Individual backup functionality is
+implemented through a subclass {@link android.app.backup.BackupAgent} and a
+simple and easy-to-use implementation is provided in
+{@link android.app.backup.BackupAgentHelper}.</p>
+
+<p>STOPSHIP: add more documentation and remove Dev Guide link if not written!</p>
+
+<p>The backup APIs let applications:</p>
+<ul>
+ <li>Perform backup of arbitrary data</li>
+ <li>Easily perform backup of Preferences and files</li>
+ <li>Handle restore of data</li>
+</ul>
+
+<p>For a detailed guide to using the backup APIs, see the <a
+href="{@docRoot}guide/topics/backup.html">Backup Dev Guide topic</a>.</p>
+</BODY>
+</HTML>
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 03e8623..88adabd 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -1,4 +1,5 @@
/*
+ * 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.
@@ -230,8 +231,14 @@ public class AppWidgetHost {
/**
* Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
*/
- @SuppressWarnings({"UnusedDeclaration"})
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
+ AppWidgetHostView v;
+ synchronized (mViews) {
+ v = mViews.get(appWidgetId);
+ }
+ if (v != null) {
+ v.updateAppWidget(null, AppWidgetHostView.UPDATE_FLAGS_RESET);
+ }
}
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
@@ -240,7 +247,7 @@ public class AppWidgetHost {
v = mViews.get(appWidgetId);
}
if (v != null) {
- v.updateAppWidget(views);
+ v.updateAppWidget(views, 0);
}
}
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 2f719f3..5375193 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -46,6 +46,8 @@ public class AppWidgetHostView extends FrameLayout {
static final boolean LOGD = false;
static final boolean CROSSFADE = false;
+ static final int UPDATE_FLAGS_RESET = 0x00000001;
+
static final int VIEW_MODE_NOINIT = 0;
static final int VIEW_MODE_CONTENT = 1;
static final int VIEW_MODE_ERROR = 2;
@@ -150,7 +152,16 @@ public class AppWidgetHostView extends FrameLayout {
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
- if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
+ updateAppWidget(remoteViews, 0);
+ }
+
+ void updateAppWidget(RemoteViews remoteViews, int flags) {
+ if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld + " flags=0x"
+ + Integer.toHexString(flags));
+
+ if ((flags & UPDATE_FLAGS_RESET) != 0) {
+ mViewMode = VIEW_MODE_NOINIT;
+ }
boolean recycled = false;
View content = null;
@@ -311,8 +322,8 @@ public class AppWidgetHostView extends FrameLayout {
// Take requested dimensions from child, but apply default gravity.
FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
if (requested == null) {
- requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
- LayoutParams.FILL_PARENT);
+ requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT);
}
requested.gravity = Gravity.CENTER;
@@ -323,6 +334,9 @@ public class AppWidgetHostView extends FrameLayout {
* Inflate and return the default layout requested by AppWidget provider.
*/
protected View getDefaultView() {
+ if (LOGD) {
+ Log.d(TAG, "getDefaultView");
+ }
View defaultView = null;
Exception exception = null;
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 3660001..d4ce6a1 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -183,6 +183,16 @@ public class AppWidgetManager {
* @see AppWidgetProviderInfo
*/
public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
+
+ /**
+ * Field for the manifest meta-data tag used to indicate any previous name for the
+ * app widget receiver.
+ *
+ * @see AppWidgetProviderInfo
+ *
+ * @hide Pending API approval
+ */
+ public static final String META_DATA_APPWIDGET_OLD_NAME = "android.appwidget.oldName";
static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache = new WeakHashMap();
static IAppWidgetService sService;
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index a2e0ba0a..cee2865 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -98,6 +98,18 @@ public class AppWidgetProviderInfo implements Parcelable {
* the <code>&lt;receiver&gt;</code> element in the AndroidManifest.xml file.
*/
public int icon;
+
+
+ /**
+ * The previous name, if any, of the app widget receiver. If not supplied, it will be
+ * ignored.
+ *
+ * <p>This field corresponds to the <code>&lt;meta-data /&gt;</code> with the name
+ * <code>android.appwidget.oldName</code>.
+ *
+ * @hide Pending API approval
+ */
+ public String oldName;
public AppWidgetProviderInfo() {
}
diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
deleted file mode 100644
index e67b0be..0000000
--- a/core/java/android/backup/BackupDataInput.java
+++ /dev/null
@@ -1,112 +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.backup;
-
-import android.content.Context;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/** @hide */
-public class BackupDataInput {
- int mBackupReader;
-
- private EntityHeader mHeader = new EntityHeader();
- private boolean mHeaderReady;
-
- private static class EntityHeader {
- String key;
- int dataSize;
- }
-
- public BackupDataInput(FileDescriptor fd) {
- if (fd == null) throw new NullPointerException();
- mBackupReader = ctor(fd);
- if (mBackupReader == 0) {
- throw new RuntimeException("Native initialization failed with fd=" + fd);
- }
- }
-
- protected void finalize() throws Throwable {
- try {
- dtor(mBackupReader);
- } finally {
- super.finalize();
- }
- }
-
- public boolean readNextHeader() throws IOException {
- int result = readNextHeader_native(mBackupReader, mHeader);
- if (result == 0) {
- // read successfully
- mHeaderReady = true;
- return true;
- } else if (result > 0) {
- // done
- mHeaderReady = false;
- return false;
- } else {
- // error
- mHeaderReady = false;
- throw new IOException("result=0x" + Integer.toHexString(result));
- }
- }
-
- public String getKey() {
- if (mHeaderReady) {
- return mHeader.key;
- } else {
- throw new IllegalStateException("mHeaderReady=false");
- }
- }
-
- public int getDataSize() {
- if (mHeaderReady) {
- return mHeader.dataSize;
- } else {
- throw new IllegalStateException("mHeaderReady=false");
- }
- }
-
- public int readEntityData(byte[] data, int offset, int size) throws IOException {
- if (mHeaderReady) {
- int result = readEntityData_native(mBackupReader, data, offset, size);
- if (result >= 0) {
- return result;
- } else {
- throw new IOException("result=0x" + Integer.toHexString(result));
- }
- } else {
- throw new IllegalStateException("mHeaderReady=false");
- }
- }
-
- public void skipEntityData() throws IOException {
- if (mHeaderReady) {
- skipEntityData_native(mBackupReader);
- } else {
- throw new IllegalStateException("mHeaderReady=false");
- }
- }
-
- private native static int ctor(FileDescriptor fd);
- private native static void dtor(int mBackupReader);
-
- private native int readNextHeader_native(int mBackupReader, EntityHeader entity);
- private native int readEntityData_native(int mBackupReader, byte[] data, int offset, int size);
- private native int skipEntityData_native(int mBackupReader);
-}
diff --git a/core/java/android/backup/BackupDataInputStream.java b/core/java/android/backup/BackupDataInputStream.java
deleted file mode 100644
index b705c4c..0000000
--- a/core/java/android/backup/BackupDataInputStream.java
+++ /dev/null
@@ -1,63 +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.backup;
-
-import android.util.Log;
-
-import java.io.InputStream;
-import java.io.IOException;
-
-/** @hide */
-public class BackupDataInputStream extends InputStream {
-
- String key;
- int dataSize;
-
- BackupDataInput mData;
- byte[] mOneByte;
-
- BackupDataInputStream(BackupDataInput data) {
- mData = data;
- }
-
- public int read() throws IOException {
- byte[] one = mOneByte;
- if (mOneByte == null) {
- one = mOneByte = new byte[1];
- }
- mData.readEntityData(one, 0, 1);
- return one[0];
- }
-
- public int read(byte[] b, int offset, int size) throws IOException {
- return mData.readEntityData(b, offset, size);
- }
-
- public int read(byte[] b) throws IOException {
- return mData.readEntityData(b, 0, b.length);
- }
-
- public String getKey() {
- return this.key;
- }
-
- public int size() {
- return this.dataSize;
- }
-}
-
-
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
deleted file mode 100644
index d29c5ba..0000000
--- a/core/java/android/backup/BackupDataOutput.java
+++ /dev/null
@@ -1,77 +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.backup;
-
-import android.content.Context;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/** @hide */
-public class BackupDataOutput {
- int mBackupWriter;
-
- public static final int OP_UPDATE = 1;
- public static final int OP_DELETE = 2;
-
- public BackupDataOutput(FileDescriptor fd) {
- if (fd == null) throw new NullPointerException();
- mBackupWriter = ctor(fd);
- if (mBackupWriter == 0) {
- throw new RuntimeException("Native initialization failed with fd=" + fd);
- }
- }
-
- // A dataSize of -1 indicates that the record under this key should be deleted
- public int writeEntityHeader(String key, int dataSize) throws IOException {
- int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
- if (result >= 0) {
- return result;
- } else {
- throw new IOException("result=0x" + Integer.toHexString(result));
- }
- }
-
- public int writeEntityData(byte[] data, int size) throws IOException {
- int result = writeEntityData_native(mBackupWriter, data, size);
- if (result >= 0) {
- return result;
- } else {
- throw new IOException("result=0x" + Integer.toHexString(result));
- }
- }
-
- public void setKeyPrefix(String keyPrefix) {
- setKeyPrefix_native(mBackupWriter, keyPrefix);
- }
-
- protected void finalize() throws Throwable {
- try {
- dtor(mBackupWriter);
- } finally {
- super.finalize();
- }
- }
-
- private native static int ctor(FileDescriptor fd);
- private native static void dtor(int mBackupWriter);
-
- private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize);
- private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size);
- private native static void setKeyPrefix_native(int mBackupWriter, String keyPrefix);
-}
-
diff --git a/core/java/android/backup/BackupHelper.java b/core/java/android/backup/BackupHelper.java
deleted file mode 100644
index 3983e28..0000000
--- a/core/java/android/backup/BackupHelper.java
+++ /dev/null
@@ -1,46 +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.backup;
-
-import android.os.ParcelFileDescriptor;
-
-import java.io.InputStream;
-
-/** @hide */
-public interface BackupHelper {
- /**
- * Based on oldState, determine which of the files from the application's data directory
- * need to be backed up, write them to the data stream, and fill in newState with the
- * state as it exists now.
- */
- public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState);
-
- /**
- * Called by BackupHelperDispatcher to dispatch one entity of data.
- * <p class=note>
- * Do not close the <code>data</code> stream. Do not read more than
- * <code>dataSize</code> bytes from <code>data</code>.
- */
- public void restoreEntity(BackupDataInputStream data);
-
- /**
- *
- */
- public void writeRestoreSnapshot(ParcelFileDescriptor fd);
-}
-
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
deleted file mode 100644
index 5d0c4a2..0000000
--- a/core/java/android/backup/BackupHelperAgent.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.backup;
-
-import android.app.BackupAgent;
-import android.backup.BackupHelper;
-import android.backup.BackupHelperDispatcher;
-import android.backup.BackupDataInput;
-import android.backup.BackupDataOutput;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.IOException;
-
-/** @hide */
-public class BackupHelperAgent extends BackupAgent {
- static final String TAG = "BackupHelperAgent";
-
- BackupHelperDispatcher mDispatcher = new BackupHelperDispatcher();
-
- @Override
- public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) throws IOException {
- mDispatcher.performBackup(oldState, data, newState);
- }
-
- @Override
- public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
- throws IOException {
- mDispatcher.performRestore(data, appVersionCode, newState);
- }
-
- public BackupHelperDispatcher getDispatcher() {
- return mDispatcher;
- }
-
- public void addHelper(String keyPrefix, BackupHelper helper) {
- mDispatcher.addHelper(keyPrefix, helper);
- }
-}
-
-
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
deleted file mode 100644
index da1647a..0000000
--- a/core/java/android/backup/BackupManager.java
+++ /dev/null
@@ -1,133 +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.backup;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-/**
- * BackupManager is the interface to the system's backup service.
- * Applications simply instantiate one, and then use that instance
- * to communicate with the backup infrastructure.
- *
- * <p>When your application has made changes to data it wishes to have
- * backed up, call {@link #dataChanged()} to notify the backup service.
- * The system will then schedule a backup operation to occur in the near
- * future. Repeated calls to {@link #dataChanged()} have no further effect
- * until the backup operation actually occurs.
- *
- * <p>The backup operation itself begins with the system launching the
- * {@link android.app.BackupAgent} subclass declared in your manifest. See the
- * documentation for {@link android.app.BackupAgent} for a detailed description
- * of how the backup then proceeds.
- *
- * @hide pending API solidification
- */
-public class BackupManager {
- private static final String TAG = "BackupManager";
-
- /** @hide TODO: REMOVE THIS */
- public static final boolean EVEN_THINK_ABOUT_DOING_RESTORE = true;
-
- private Context mContext;
- private static IBackupManager sService;
-
- private static void checkServiceBinder() {
- if (sService == null) {
- sService = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- }
- }
-
- /**
- * Constructs a BackupManager object through which the application can
- * communicate with the Android backup system.
- *
- * @param context The {@link android.content.Context} that was provided when
- * one of your application's {@link android.app.Activity Activities}
- * was created.
- */
- public BackupManager(Context context) {
- mContext = context;
- }
-
- /**
- * Notifies the Android backup system that your application wishes to back up
- * new changes to its data. A backup operation using your application's
- * {@link android.app.BackupAgent} subclass will be scheduled when you call this method.
- */
- public void dataChanged() {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return;
- }
- checkServiceBinder();
- if (sService != null) {
- try {
- sService.dataChanged(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.d(TAG, "dataChanged() couldn't connect");
- }
- }
- }
-
- /**
- * Convenience method for callers who need to indicate that some other package
- * needs a backup pass. This can be relevant in the case of groups of packages
- * that share a uid, for example.
- *
- * This method requires that the application hold the "android.permission.BACKUP"
- * permission if the package named in the argument is not the caller's own.
- */
- public static void dataChanged(String packageName) {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return;
- }
- checkServiceBinder();
- if (sService != null) {
- try {
- sService.dataChanged(packageName);
- } catch (RemoteException e) {
- Log.d(TAG, "dataChanged(pkg) couldn't connect");
- }
- }
- }
-
- /**
- * Begin the process of restoring system data from backup. This method requires
- * that the application hold the "android.permission.BACKUP" permission, and is
- * not public.
- *
- * {@hide}
- */
- public IRestoreSession beginRestoreSession(String transport) {
- if (!EVEN_THINK_ABOUT_DOING_RESTORE) {
- return null;
- }
- IRestoreSession binder = null;
- checkServiceBinder();
- if (sService != null) {
- try {
- binder = sService.beginRestoreSession(transport);
- } catch (RemoteException e) {
- Log.d(TAG, "beginRestoreSession() couldn't connect");
- }
- }
- return binder;
- }
-}
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
deleted file mode 100644
index 6a0bc96..0000000
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ /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.backup;
-
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileDescriptor;
-
-/** @hide */
-public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
- private static final String TAG = "SharedPreferencesBackupHelper";
- private static final boolean DEBUG = false;
-
- private Context mContext;
- private String[] mPrefGroups;
-
- public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
- super(context);
-
- mContext = context;
- mPrefGroups = prefGroups;
- }
-
- public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) {
- Context context = mContext;
-
- // make filenames for the prefGroups
- String[] prefGroups = mPrefGroups;
- final int N = prefGroups.length;
- String[] files = new String[N];
- for (int i=0; i<N; i++) {
- files[i] = context.getSharedPrefsFile(prefGroups[i]).getAbsolutePath();
- }
-
- // go
- performBackup_checked(oldState, data, newState, files, prefGroups);
- }
-
- public void restoreEntity(BackupDataInputStream data) {
- Context context = mContext;
-
- String key = data.getKey();
- if (DEBUG) Log.d(TAG, "got entity '" + key + "' size=" + data.size());
-
- if (isKeyInList(key, mPrefGroups)) {
- File f = context.getSharedPrefsFile(key).getAbsoluteFile();
- writeFile(f, data);
- }
- }
-}
-
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8eda844..42d87f4 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -63,7 +63,7 @@ import java.util.UUID;
*/
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
- private static final boolean DBG = false;
+ private static final boolean DBG = true; //STOPSHIP: Remove excess logging
/**
* Sentinel error value for this class. Guaranteed to not equal any other
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
index abd7723..bc32060 100644
--- a/core/java/android/bluetooth/BluetoothAudioGateway.java
+++ b/core/java/android/bluetooth/BluetoothAudioGateway.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.bluetooth;
import java.lang.Thread;
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index cf9c58f..e77e76f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -664,6 +664,10 @@ public final class BluetoothDevice implements Parcelable {
* determine which channel to connect to.
* <p>The remote device will be authenticated and communication on this
* socket will be encrypted.
+ * <p>Hint: If you are connecting to a Bluetooth serial board then try
+ * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
+ * However if you are connecting to an Android peer then please generate
+ * your own unique UUID.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param uuid service record uuid to lookup RFCOMM channel
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index b792965..95e61b6 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -73,6 +73,17 @@ public final class BluetoothHeadset {
public static final String EXTRA_AUDIO_STATE =
"android.bluetooth.headset.extra.AUDIO_STATE";
+ /** Extra to be used with the Headset State change intent.
+ * This will be used only when Headset state changes to
+ * {@link #STATE_DISCONNECTED} from any previous state.
+ * This extra field is optional and will be used when
+ * we have deterministic information regarding whether
+ * the disconnect was initiated by the remote device or
+ * by the local adapter.
+ */
+ public static final String EXTRA_DISCONNECT_INITIATOR =
+ "android.bluetooth.headset.extra.DISCONNECT_INITIATOR";
+
/**
* TODO(API release): Consider incorporating as new state in
* HEADSET_STATE_CHANGED
@@ -100,6 +111,11 @@ public final class BluetoothHeadset {
/** Connection canceled before completetion. */
public static final int RESULT_CANCELED = 2;
+ /** Values for {@link #EXTRA_DISCONNECT_INITIATOR} */
+ public static final int REMOTE_DISCONNECT = 0;
+ public static final int LOCAL_DISCONNECT = 1;
+
+
/** Default priority for headsets that for which we will accept
* inconing connections and auto-connect */
public static final int PRIORITY_AUTO_CONNECT = 1000;
@@ -112,12 +128,6 @@ public final class BluetoothHeadset {
/** Default priority when not set or when the device is unpaired */
public static final int PRIORITY_UNDEFINED = -1;
- /** The voice dialer 'works' but the user experience is poor. The voice
- * recognizer has trouble dealing with the 8kHz SCO signal, and it still
- * requires visual confirmation. Disable for cupcake.
- */
- public static final boolean DISABLE_BT_VOICE_DIALING = true;
-
/**
* An interface for notifying BluetoothHeadset IPC clients when they have
* been connected to the BluetoothHeadset service.
@@ -386,6 +396,16 @@ public final class BluetoothHeadset {
return -1;
}
+ /**
+ * Indicates if current platform supports voice dialing over bluetooth SCO.
+ * @return true if voice dialing over bluetooth is supported, false otherwise.
+ * @hide
+ */
+ public static boolean isBluetoothVoiceDialingEnabled(Context context) {
+ return context.getResources().getBoolean(
+ com.android.internal.R.bool.config_bluetooth_sco_off_call);
+ }
+
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
index 116310a..b65a99a 100644
--- a/core/java/android/bluetooth/ScoSocket.java
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -86,14 +86,14 @@ public class ScoSocket {
/** Connect this SCO socket to the given BT address.
* Does not block.
*/
- public synchronized boolean connect(String address) {
+ public synchronized boolean connect(String address, String name) {
if (DBG) log("connect() " + this);
if (mState != STATE_READY) {
if (DBG) log("connect(): Bad state");
return false;
}
acquireWakeLock();
- if (connectNative(address)) {
+ if (connectNative(address, name)) {
mState = STATE_CONNECTING;
return true;
} else {
@@ -102,7 +102,7 @@ public class ScoSocket {
return false;
}
}
- private native boolean connectNative(String address);
+ private native boolean connectNative(String address, String name);
/** Accept incoming SCO connections.
* Does not block.
diff --git a/core/java/android/content/AbstractCursorEntityIterator.java b/core/java/android/content/AbstractCursorEntityIterator.java
deleted file mode 100644
index a804f3c..0000000
--- a/core/java/android/content/AbstractCursorEntityIterator.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package android.content;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.RemoteException;
-
-/**
- * An abstract class that makes it easy to implement an EntityIterator over a cursor.
- * The user must implement {@link #newEntityFromCursorLocked}, which runs inside of a
- * database transaction.
- * @hide
- */
-public abstract class AbstractCursorEntityIterator implements EntityIterator {
- private final Cursor mEntityCursor;
- private final SQLiteDatabase mDb;
- private volatile Entity mNextEntity;
- private volatile boolean mIsClosed;
-
- public AbstractCursorEntityIterator(SQLiteDatabase db, Cursor entityCursor) {
- mEntityCursor = entityCursor;
- mDb = db;
- mNextEntity = null;
- mIsClosed = false;
- }
-
- /**
- * If there are entries left in the cursor then advance the cursor and use the new row to
- * populate mNextEntity. If the cursor is at the end or if advancing it causes the cursor
- * to become at the end then set mEntityCursor to null. If newEntityFromCursor returns null
- * then continue advancing until it either returns a non-null Entity or the cursor reaches
- * the end.
- */
- private void fillEntityIfAvailable() {
- while (mNextEntity == null) {
- if (!mEntityCursor.moveToNext()) {
- // the cursor is at then end, bail out
- return;
- }
- // This may return null if newEntityFromCursor is not able to create an entity
- // from the current cursor position. In that case this method will loop and try
- // the next cursor position
- mNextEntity = newEntityFromCursorLocked(mEntityCursor);
- }
- mDb.beginTransaction();
- try {
- int position = mEntityCursor.getPosition();
- mNextEntity = newEntityFromCursorLocked(mEntityCursor);
- int newPosition = mEntityCursor.getPosition();
- if (newPosition != position) {
- throw new IllegalStateException("the cursor position changed during the call to"
- + "newEntityFromCursorLocked, from " + position + " to " + newPosition);
- }
- } finally {
- mDb.endTransaction();
- }
- }
-
- /**
- * Checks if there are more Entities accessible via this iterator. This may not be called
- * if the iterator is already closed.
- * @return true if the call to next() will return an Entity.
- */
- public boolean hasNext() {
- if (mIsClosed) {
- throw new IllegalStateException("calling hasNext() when the iterator is closed");
- }
- fillEntityIfAvailable();
- return mNextEntity != null;
- }
-
- /**
- * Returns the next Entity that is accessible via this iterator. This may not be called
- * if the iterator is already closed.
- * @return the next Entity that is accessible via this iterator
- */
- public Entity next() {
- if (mIsClosed) {
- throw new IllegalStateException("calling next() when the iterator is closed");
- }
- if (!hasNext()) {
- throw new IllegalStateException("you may only call next() if hasNext() is true");
- }
-
- try {
- return mNextEntity;
- } finally {
- mNextEntity = null;
- }
- }
-
- public void reset() throws RemoteException {
- if (mIsClosed) {
- throw new IllegalStateException("calling reset() when the iterator is closed");
- }
- mEntityCursor.moveToPosition(-1);
- mNextEntity = null;
- }
-
- /**
- * Closes this iterator making it invalid. If is invalid for the user to call any public
- * method on the iterator once it has been closed.
- */
- public void close() {
- if (mIsClosed) {
- throw new IllegalStateException("closing when already closed");
- }
- mIsClosed = true;
- mEntityCursor.close();
- }
-
- /**
- * Returns a new Entity from the current cursor position. This is called from within a
- * database transaction. If a new entity cannot be created from this cursor position (e.g.
- * if the row that is referred to no longer exists) then this may return null. The cursor
- * is guaranteed to be pointing to a valid row when this call is made. The implementation
- * of newEntityFromCursorLocked is not allowed to change the position of the cursor.
- * @param cursor from where to read the data for the Entity
- * @return an Entity that corresponds to the current cursor position or null
- */
- public abstract Entity newEntityFromCursorLocked(Cursor cursor);
-}
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
deleted file mode 100644
index dd89097..0000000
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ /dev/null
@@ -1,755 +0,0 @@
-package android.content;
-
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.Cursor;
-import android.net.Uri;
-import android.accounts.OnAccountsUpdateListener;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.provider.SyncConstValue;
-import android.util.Config;
-import android.util.Log;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Vector;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.HashSet;
-
-import com.google.android.collect.Maps;
-
-/**
- * A specialization of the ContentProvider that centralizes functionality
- * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
- * inside of database transactions.
- *
- * @hide
- */
-public abstract class AbstractSyncableContentProvider extends SyncableContentProvider {
- private static final String TAG = "SyncableContentProvider";
- protected SQLiteOpenHelper mOpenHelper;
- protected SQLiteDatabase mDb;
- private final String mDatabaseName;
- private final int mDatabaseVersion;
- private final Uri mContentUri;
-
- /** the account set in the last call to onSyncStart() */
- private Account mSyncingAccount;
-
- private SyncStateContentProviderHelper mSyncState = null;
-
- private static final String[] sAccountProjection =
- new String[] {SyncConstValue._SYNC_ACCOUNT, SyncConstValue._SYNC_ACCOUNT_TYPE};
-
- private boolean mIsTemporary;
-
- private AbstractTableMerger mCurrentMerger = null;
- private boolean mIsMergeCancelled = false;
-
- private static final String SYNC_ACCOUNT_WHERE_CLAUSE =
- SyncConstValue._SYNC_ACCOUNT + "=? AND " + SyncConstValue._SYNC_ACCOUNT_TYPE + "=?";
-
- protected boolean isTemporary() {
- return mIsTemporary;
- }
-
- private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
- private final ThreadLocal<Set<Uri>> mPendingBatchNotifications = new ThreadLocal<Set<Uri>>();
-
- /**
- * Indicates whether or not this ContentProvider contains a full
- * set of data or just diffs. This knowledge comes in handy when
- * determining how to incorporate the contents of a temporary
- * provider into a real provider.
- */
- private boolean mContainsDiffs;
-
- /**
- * Initializes the AbstractSyncableContentProvider
- * @param dbName the filename of the database
- * @param dbVersion the current version of the database schema
- * @param contentUri The base Uri of the syncable content in this provider
- */
- public AbstractSyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
- super();
-
- mDatabaseName = dbName;
- mDatabaseVersion = dbVersion;
- mContentUri = contentUri;
- mIsTemporary = false;
- setContainsDiffs(false);
- if (Config.LOGV) {
- Log.v(TAG, "created SyncableContentProvider " + this);
- }
- }
-
- /**
- * Close resources that must be closed. You must call this to properly release
- * the resources used by the AbstractSyncableContentProvider.
- */
- public void close() {
- if (mOpenHelper != null) {
- mOpenHelper.close(); // OK to call .close() repeatedly.
- }
- }
-
- /**
- * Override to create your schema and do anything else you need to do with a new database.
- * This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected void bootstrapDatabase(SQLiteDatabase db) {}
-
- /**
- * Override to upgrade your database from an old version to the version you specified.
- * Don't set the DB version; this will automatically be done after the method returns.
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- *
- * @param oldVersion version of the existing database
- * @param newVersion current version to upgrade to
- * @return true if the upgrade was lossless, false if it was lossy
- */
- protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
-
- /**
- * Override to do anything (like cleanups or checks) you need to do after opening a database.
- * Does nothing by default. This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected void onDatabaseOpened(SQLiteDatabase db) {}
-
- private class DatabaseHelper extends SQLiteOpenHelper {
- DatabaseHelper(Context context, String name) {
- // Note: context and name may be null for temp providers
- super(context, name, null, mDatabaseVersion);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- bootstrapDatabase(db);
- mSyncState.createDatabase(db);
- if (!isTemporary()) {
- ContentResolver.requestSync(null /* all accounts */,
- mContentUri.getAuthority(), new Bundle());
- }
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (!upgradeDatabase(db, oldVersion, newVersion)) {
- mSyncState.discardSyncData(db, null /* all accounts */);
- ContentResolver.requestSync(null /* all accounts */,
- mContentUri.getAuthority(), new Bundle());
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- onDatabaseOpened(db);
- mSyncState.onDatabaseOpened(db);
- }
- }
-
- @Override
- public boolean onCreate() {
- if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
- mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(),
- mDatabaseName);
- mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
- AccountManager.get(getContext()).addOnAccountsUpdatedListener(
- new OnAccountsUpdateListener() {
- public void onAccountsUpdated(Account[] accounts) {
- // Some providers override onAccountsChanged(); give them a database to
- // work with.
- mDb = mOpenHelper.getWritableDatabase();
- // Only call onAccountsChanged on GAIA accounts; otherwise, the contacts and
- // calendar providers will choke as they try to sync unknown accounts with
- // AbstractGDataSyncAdapter, which will put acore into a crash loop
- ArrayList<Account> gaiaAccounts = new ArrayList<Account>();
- for (Account acct: accounts) {
- if (acct.type.equals("com.google")) {
- gaiaAccounts.add(acct);
- }
- }
- accounts = new Account[gaiaAccounts.size()];
- int i = 0;
- for (Account acct: gaiaAccounts) {
- accounts[i++] = acct;
- }
- onAccountsChanged(accounts);
- TempProviderSyncAdapter syncAdapter = getTempProviderSyncAdapter();
- if (syncAdapter != null) {
- syncAdapter.onAccountsChanged(accounts);
- }
- }
- }, null /* handler */, true /* updateImmediately */);
-
- return true;
- }
- /**
- * Get a non-persistent instance of this content provider.
- * You must call {@link #close} on the returned
- * SyncableContentProvider when you are done with it.
- *
- * @return a non-persistent content provider with the same layout as this
- * provider.
- */
- public AbstractSyncableContentProvider getTemporaryInstance() {
- AbstractSyncableContentProvider temp;
- try {
- temp = getClass().newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException("unable to instantiate class, "
- + "this should never happen", e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "IllegalAccess while instantiating class, "
- + "this should never happen", e);
- }
-
- // Note: onCreate() isn't run for the temp provider, and it has no Context.
- temp.mIsTemporary = true;
- temp.setContainsDiffs(true);
- temp.mOpenHelper = temp.new DatabaseHelper(null, null);
- temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
- if (!isTemporary()) {
- mSyncState.copySyncState(
- mOpenHelper.getReadableDatabase(),
- temp.mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- return temp;
- }
-
- public SQLiteDatabase getDatabase() {
- if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
- return mDb;
- }
-
- public boolean getContainsDiffs() {
- return mContainsDiffs;
- }
-
- public void setContainsDiffs(boolean containsDiffs) {
- if (containsDiffs && !isTemporary()) {
- throw new IllegalStateException(
- "only a temporary provider can contain diffs");
- }
- mContainsDiffs = containsDiffs;
- }
-
- /**
- * Each subclass of this class should define a subclass of {@link
- * android.content.AbstractTableMerger} for each table they wish to merge. It
- * should then override this method and return one instance of
- * each merger, in sequence. Their {@link
- * android.content.AbstractTableMerger#merge merge} methods will be called, one at a
- * time, in the order supplied.
- *
- * <p>The default implementation returns an empty list, so that no
- * merging will occur.
- * @return A sequence of subclasses of {@link
- * android.content.AbstractTableMerger}, one for each table that should be merged.
- */
- protected Iterable<? extends AbstractTableMerger> getMergers() {
- return Collections.emptyList();
- }
-
- @Override
- public final int update(final Uri url, final ContentValues values,
- final String selection, final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().update(
- url, values, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return numRows;
- }
-
- int result = updateInternal(url, values, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result > 0) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- @Override
- public final int delete(final Uri url, final String selection,
- final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return numRows;
- }
- int result = deleteInternal(url, selection, selectionArgs);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result > 0) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- private boolean applyingBatch() {
- return mApplyingBatch.get() != null && mApplyingBatch.get();
- }
-
- @Override
- public final Uri insert(final Uri url, final ContentValues values) {
- mDb = mOpenHelper.getWritableDatabase();
- final boolean notApplyingBatch = !applyingBatch();
- if (notApplyingBatch) {
- mDb.beginTransaction();
- }
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- Uri result = mSyncState.asContentProvider().insert(url, values);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- return result;
- }
- Uri result = insertInternal(url, values);
- if (notApplyingBatch) {
- mDb.setTransactionSuccessful();
- }
- if (!isTemporary() && result != null) {
- if (notApplyingBatch) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- } else {
- mPendingBatchNotifications.get().add(url);
- }
- }
- return result;
- } finally {
- if (notApplyingBatch) {
- mDb.endTransaction();
- }
- }
- }
-
- @Override
- public final int bulkInsert(final Uri uri, final ContentValues[] values) {
- int size = values.length;
- int completed = 0;
- final boolean isSyncStateUri = mSyncState.matches(uri);
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- for (int i = 0; i < size; i++) {
- Uri result;
- if (isTemporary() && isSyncStateUri) {
- result = mSyncState.asContentProvider().insert(uri, values[i]);
- } else {
- result = insertInternal(uri, values[i]);
- mDb.yieldIfContended();
- }
- if (result != null) {
- completed++;
- }
- }
- mDb.setTransactionSuccessful();
- } finally {
- mDb.endTransaction();
- }
- if (!isTemporary() && completed == size) {
- getContext().getContentResolver().notifyChange(uri, null /* observer */,
- changeRequiresLocalSync(uri));
- }
- return completed;
- }
-
- /**
- * <p>
- * Start batch transaction. {@link #endTransaction} MUST be called after
- * calling this method. Those methods should be used like this:
- * </p>
- *
- * <pre class="prettyprint">
- * boolean successful = false;
- * beginBatch()
- * try {
- * // Do something related to mDb
- * successful = true;
- * return ret;
- * } finally {
- * endBatch(successful);
- * }
- * </pre>
- *
- * @hide This method should be used only when {@link ContentProvider#applyBatch} is not enough and must be
- * used with {@link #endBatch}.
- * e.g. If returned value has to be used during one transaction, this method might be useful.
- */
- public final void beginBatch() {
- // initialize if this is the first time this thread has applied a batch
- if (mApplyingBatch.get() == null) {
- mApplyingBatch.set(false);
- mPendingBatchNotifications.set(new HashSet<Uri>());
- }
-
- if (applyingBatch()) {
- throw new IllegalStateException(
- "applyBatch is not reentrant but mApplyingBatch is already set");
- }
- SQLiteDatabase db = getDatabase();
- db.beginTransaction();
- boolean successful = false;
- try {
- mApplyingBatch.set(true);
- successful = true;
- } finally {
- if (!successful) {
- // Something unexpected happened. We must call endTransaction() at least.
- db.endTransaction();
- }
- }
- }
-
- /**
- * <p>
- * Finish batch transaction. If "successful" is true, try to call
- * mDb.setTransactionSuccessful() before calling mDb.endTransaction().
- * This method MUST be used with {@link #beginBatch()}.
- * </p>
- *
- * @hide This method must be used with {@link #beginTransaction}
- */
- public final void endBatch(boolean successful) {
- try {
- if (successful) {
- // setTransactionSuccessful() must be called just once during opening the
- // transaction.
- mDb.setTransactionSuccessful();
- }
- } finally {
- mApplyingBatch.set(false);
- getDatabase().endTransaction();
- for (Uri url : mPendingBatchNotifications.get()) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- }
- }
-
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws OperationApplicationException {
- boolean successful = false;
- beginBatch();
- try {
- ContentProviderResult[] results = super.applyBatch(operations);
- successful = true;
- return results;
- } finally {
- endBatch(successful);
- }
- }
-
- /**
- * Check if changes to this URI can be syncable changes.
- * @param uri the URI of the resource that was changed
- * @return true if changes to this URI can be syncable changes, false otherwise
- */
- public boolean changeRequiresLocalSync(Uri uri) {
- return true;
- }
-
- @Override
- public final Cursor query(final Uri url, final String[] projection,
- final String selection, final String[] selectionArgs,
- final String sortOrder) {
- mDb = mOpenHelper.getReadableDatabase();
- if (isTemporary() && mSyncState.matches(url)) {
- return mSyncState.asContentProvider().query(
- url, projection, selection, selectionArgs, sortOrder);
- }
- return queryInternal(url, projection, selection, selectionArgs, sortOrder);
- }
-
- /**
- * Called right before a sync is started.
- *
- * @param context the sync context for the operation
- * @param account
- */
- public void onSyncStart(SyncContext context, Account account) {
- if (account == null) {
- throw new IllegalArgumentException("you passed in an empty account");
- }
- mSyncingAccount = account;
- }
-
- /**
- * Called right after a sync is completed
- *
- * @param context the sync context for the operation
- * @param success true if the sync succeeded, false if an error occurred
- */
- public void onSyncStop(SyncContext context, boolean success) {
- }
-
- /**
- * The account of the most recent call to onSyncStart()
- * @return the account
- */
- public Account getSyncingAccount() {
- return mSyncingAccount;
- }
-
- /**
- * Merge diffs from a sync source with this content provider.
- *
- * @param context the SyncContext within which this merge is taking place
- * @param diffs A temporary content provider containing diffs from a sync
- * source.
- * @param result a MergeResult that contains information about the merge, including
- * a temporary content provider with the same layout as this provider containing
- * @param syncResult
- */
- public void merge(SyncContext context, SyncableContentProvider diffs,
- TempProviderSyncResult result, SyncResult syncResult) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- synchronized(this) {
- mIsMergeCancelled = false;
- }
- Iterable<? extends AbstractTableMerger> mergers = getMergers();
- try {
- for (AbstractTableMerger merger : mergers) {
- synchronized(this) {
- if (mIsMergeCancelled) break;
- mCurrentMerger = merger;
- }
- merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
- }
- if (mIsMergeCancelled) return;
- if (diffs != null) {
- mSyncState.copySyncState(
- ((AbstractSyncableContentProvider)diffs).mOpenHelper.getReadableDatabase(),
- mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- } finally {
- synchronized (this) {
- mCurrentMerger = null;
- }
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
-
- /**
- * Invoked when the active sync has been canceled. Sets the sync state of this provider and
- * its merger to canceled.
- */
- public void onSyncCanceled() {
- synchronized (this) {
- mIsMergeCancelled = true;
- if (mCurrentMerger != null) {
- mCurrentMerger.onMergeCancelled();
- }
- }
- }
-
-
- public boolean isMergeCancelled() {
- return mIsMergeCancelled;
- }
-
- /**
- * Subclasses should override this instead of update(). See update()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int updateInternal(Uri url, ContentValues values,
- String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of delete(). See delete()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of insert(). See insert()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract Uri insertInternal(Uri url, ContentValues values);
-
- /**
- * Subclasses should override this instead of query(). See query()
- * for details.
- *
- * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
- * block for performance reasons. If an implementation needs atomic access
- * to the database the lock can be acquired then.
- */
- protected abstract Cursor queryInternal(Uri url, String[] projection,
- String selection, String[] selectionArgs, String sortOrder);
-
- /**
- * Make sure that there are no entries for accounts that no longer exist
- * @param accountsArray the array of currently-existing accounts
- */
- protected void onAccountsChanged(Account[] accountsArray) {
- Map<Account, Boolean> accounts = Maps.newHashMap();
- for (Account account : accountsArray) {
- accounts.put(account, false);
- }
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- Vector<String> tables = new Vector<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
- try {
- mSyncState.onAccountsChanged(accountsArray);
- for (String table : tables) {
- deleteRowsForRemovedAccounts(accounts, table);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * A helper method to delete all rows whose account is not in the accounts
- * map. The accountColumnName is the name of the column that is expected
- * to hold the account. If a row has an empty account it is never deleted.
- *
- * @param accounts a map of existing accounts
- * @param table the table to delete from
- */
- protected void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts, String table) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor c = db.query(table, sAccountProjection, null, null,
- "_sync_account, _sync_account_type", null, null);
- try {
- while (c.moveToNext()) {
- String accountName = c.getString(0);
- String accountType = c.getString(1);
- if ("localhost".equals(accountType)) continue;
- if (TextUtils.isEmpty(accountName)) {
- continue;
- }
- Account account = new Account(accountName, accountType);
- if (!accounts.containsKey(account)) {
- int numDeleted;
- numDeleted = db.delete(table, "_sync_account=? AND _sync_account_type=?",
- new String[]{account.name, account.type});
- if (Config.LOGV) {
- Log.v(TAG, "deleted " + numDeleted
- + " records from table " + table
- + " for account " + account);
- }
- }
- }
- } finally {
- c.close();
- }
- }
-
- /**
- * Called when the sync system determines that this provider should no longer
- * contain records for the specified account.
- */
- public void wipeAccount(Account account) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- ArrayList<String> tables = new ArrayList<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
-
- try {
- // remove the SyncState data
- mSyncState.discardSyncData(db, account);
-
- // remove the data in the synced tables
- for (String table : tables) {
- db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE,
- new String[]{account.name, account.type});
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public byte[] readSyncDataBytes(Account account) {
- return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
- }
-
- /**
- * Sets the SyncData bytes for the given account. The byte array may be null.
- */
- public void writeSyncDataBytes(Account account, byte[] data) {
- mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
- }
-}
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
deleted file mode 100644
index 9545fd7..0000000
--- a/core/java/android/content/AbstractTableMerger.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Debug;
-import android.provider.BaseColumns;
-import static android.provider.SyncConstValue.*;
-import android.text.TextUtils;
-import android.util.Log;
-import android.accounts.Account;
-
-/**
- * @hide
- */
-public abstract class AbstractTableMerger
-{
- private ContentValues mValues;
-
- protected SQLiteDatabase mDb;
- protected String mTable;
- protected Uri mTableURL;
- protected String mDeletedTable;
- protected Uri mDeletedTableURL;
- static protected ContentValues mSyncMarkValues;
- static private boolean TRACE;
-
- static {
- mSyncMarkValues = new ContentValues();
- mSyncMarkValues.put(_SYNC_MARK, 1);
- TRACE = false;
- }
-
- private static final String TAG = "AbstractTableMerger";
- private static final String[] syncDirtyProjection =
- new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
- private static final String[] syncIdAndVersionProjection =
- new String[] {_SYNC_ID, _SYNC_VERSION};
-
- private volatile boolean mIsMergeCancelled;
-
- private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and "
- + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
-
- private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
- _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
- private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
-
- private static final String SELECT_UNSYNCED =
- "(" + _SYNC_ACCOUNT + " IS NULL OR ("
- + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?)) and "
- + "(" + _SYNC_ID + " IS NULL OR (" + _SYNC_DIRTY + " > 0 and "
- + _SYNC_VERSION + " IS NOT NULL))";
-
- public AbstractTableMerger(SQLiteDatabase database,
- String table, Uri tableURL, String deletedTable,
- Uri deletedTableURL)
- {
- mDb = database;
- mTable = table;
- mTableURL = tableURL;
- mDeletedTable = deletedTable;
- mDeletedTableURL = deletedTableURL;
- mValues = new ContentValues();
- }
-
- public abstract void insertRow(ContentProvider diffs,
- Cursor diffsCursor);
- public abstract void updateRow(long localPersonID,
- ContentProvider diffs, Cursor diffsCursor);
- public abstract void resolveRow(long localPersonID,
- String syncID, ContentProvider diffs, Cursor diffsCursor);
-
- /**
- * This is called when it is determined that a row should be deleted from the
- * ContentProvider. The localCursor is on a table from the local ContentProvider
- * and its current position is of the row that should be deleted. The localCursor
- * is only guaranteed to contain the BaseColumns.ID column so the implementation
- * of deleteRow() must query the database directly if other columns are needed.
- * <p>
- * It is the responsibility of the implementation of this method to ensure that the cursor
- * points to the next row when this method returns, either by calling Cursor.deleteRow() or
- * Cursor.next().
- *
- * @param localCursor The Cursor into the local table, which points to the row that
- * is to be deleted.
- */
- public void deleteRow(Cursor localCursor) {
- localCursor.deleteRow();
- }
-
- /**
- * After {@link #merge} has completed, this method is called to send
- * notifications to {@link android.database.ContentObserver}s of changes
- * to the containing {@link ContentProvider}. These notifications likely
- * do not want to request a sync back to the network.
- */
- protected abstract void notifyChanges();
-
- private static boolean findInCursor(Cursor cursor, int column, String id) {
- while (!cursor.isAfterLast() && !cursor.isNull(column)) {
- int comp = id.compareTo(cursor.getString(column));
- if (comp > 0) {
- cursor.moveToNext();
- continue;
- }
- return comp == 0;
- }
- return false;
- }
-
- public void onMergeCancelled() {
- mIsMergeCancelled = true;
- }
-
- /**
- * Carry out a merge of the given diffs, and add the results to
- * the given MergeResult. If we are the first merge to find
- * client-side diffs, we'll use the given ContentProvider to
- * construct a temporary instance to hold them.
- */
- public void merge(final SyncContext context,
- final Account account,
- final SyncableContentProvider serverDiffs,
- TempProviderSyncResult result,
- SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
- mIsMergeCancelled = false;
- if (serverDiffs != null) {
- if (!mDb.isDbLockedByCurrentThread()) {
- throw new IllegalStateException("this must be called from within a DB transaction");
- }
- mergeServerDiffs(context, account, serverDiffs, syncResult);
- notifyChanges();
- }
-
- if (result != null) {
- findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
- }
-
- /**
- * @hide this is public for testing purposes only
- */
- public void mergeServerDiffs(SyncContext context,
- Account account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
- boolean diffsArePartial = serverDiffs.getContainsDiffs();
- // mark the current rows so that we can distinguish these from new
- // inserts that occur during the merge
- mDb.update(mTable, mSyncMarkValues, null, null);
- if (mDeletedTable != null) {
- mDb.update(mDeletedTable, mSyncMarkValues, null, null);
- }
-
- Cursor localCursor = null;
- Cursor deletedCursor = null;
- Cursor diffsCursor = null;
- try {
- // load the local database entries, so we can merge them with the server
- final String[] accountSelectionArgs = new String[]{account.name, account.type};
- localCursor = mDb.query(mTable, syncDirtyProjection,
- SELECT_MARKED, accountSelectionArgs, null, null,
- mTable + "." + _SYNC_ID);
- if (mDeletedTable != null) {
- deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
- SELECT_MARKED, accountSelectionArgs, null, null,
- mDeletedTable + "." + _SYNC_ID);
- } else {
- deletedCursor =
- mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
- }
-
- // Apply updates and insertions from the server
- diffsCursor = serverDiffs.query(mTableURL,
- null, null, null, mTable + "." + _SYNC_ID);
- int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
- int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
- int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
- int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
- int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
-
- String lastSyncId = null;
- int diffsCount = 0;
- int localCount = 0;
- localCursor.moveToFirst();
- deletedCursor.moveToFirst();
- while (diffsCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- mDb.yieldIfContended();
- String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
- String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
- long localRowId = 0;
- String localSyncVersion = null;
-
- diffsCount++;
- context.setStatusText("Processing " + diffsCount + "/"
- + diffsCursor.getCount());
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
- diffsCount + ", " + serverSyncId);
-
- if (TRACE) {
- if (diffsCount == 10) {
- Debug.startMethodTracing("atmtrace");
- }
- if (diffsCount == 20) {
- Debug.stopMethodTracing();
- }
- }
-
- boolean conflict = false;
- boolean update = false;
- boolean insert = false;
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "found event with serverSyncID " + serverSyncId);
- }
- if (TextUtils.isEmpty(serverSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.e(TAG, "server entry doesn't have a serverSyncID");
- }
- continue;
- }
-
- // It is possible that the sync adapter wrote the same record multiple times,
- // e.g. if the same record came via multiple feeds. If this happens just ignore
- // the duplicate records.
- if (serverSyncId.equals(lastSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
- }
- continue;
- }
- lastSyncId = serverSyncId;
-
- String localSyncID = null;
- boolean localSyncDirty = false;
-
- while (!localCursor.isAfterLast()) {
- if (mIsMergeCancelled) {
- return;
- }
- localCount++;
- localSyncID = localCursor.getString(2);
-
- // If the local record doesn't have a _sync_id then
- // it is new. Ignore it for now, we will send an insert
- // the the server later.
- if (TextUtils.isEmpty(localSyncID)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has no _sync_id, ignoring");
- }
- localCursor.moveToNext();
- localSyncID = null;
- continue;
- }
-
- int comp = serverSyncId.compareTo(localSyncID);
-
- // the local DB has a record that the server doesn't have
- if (comp > 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that is < server _sync_id " + serverSyncId);
- }
- if (diffsArePartial) {
- localCursor.moveToNext();
- } else {
- deleteRow(localCursor);
- if (mDeletedTable != null) {
- mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
- }
- syncResult.stats.numDeletes++;
- mDb.yieldIfContended();
- }
- localSyncID = null;
- continue;
- }
-
- // the server has a record that the local DB doesn't have
- if (comp < 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that is > server _sync_id " + serverSyncId);
- }
- localSyncID = null;
- }
-
- // the server and the local DB both have this record
- if (comp == 0) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "local record " +
- localCursor.getLong(1) +
- " has _sync_id " + localSyncID +
- " that matches the server _sync_id");
- }
- localSyncDirty = localCursor.getInt(0) != 0;
- localRowId = localCursor.getLong(1);
- localSyncVersion = localCursor.getString(3);
- localCursor.moveToNext();
- }
-
- break;
- }
-
- // If this record is in the deleted table then update the server version
- // in the deleted table, if necessary, and then ignore it here.
- // We will send a deletion indication to the server down a
- // little further.
- if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
- }
- final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
- if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
- + serverSyncVersion);
- }
- ContentValues values = new ContentValues();
- values.put(_SYNC_VERSION, serverSyncVersion);
- mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
- }
- continue;
- }
-
- // If the _sync_local_id is present in the diffsCursor
- // then this record corresponds to a local record that was just
- // inserted into the server and the _sync_local_id is the row id
- // of the local record. Set these fields so that the next check
- // treats this record as an update, which will allow the
- // merger to update the record with the server's sync id
- if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
- localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "the remote record with sync id " + serverSyncId
- + " has a local sync id, " + localRowId);
- }
- localSyncID = serverSyncId;
- localSyncDirty = false;
- localSyncVersion = null;
- }
-
- if (!TextUtils.isEmpty(localSyncID)) {
- // An existing server item has changed
- // If serverSyncVersion is null, there is no edit URL;
- // server won't let this change be written.
- boolean recordChanged = (localSyncVersion == null) ||
- (serverSyncVersion == null) ||
- !serverSyncVersion.equals(localSyncVersion);
- if (recordChanged) {
- if (localSyncDirty) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId
- + " conflicts with local _sync_id " + localSyncID
- + ", local _id " + localRowId);
- }
- conflict = true;
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "remote record " +
- serverSyncId +
- " updates local _sync_id " +
- localSyncID + ", local _id " +
- localRowId);
- }
- update = true;
- }
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "Skipping update: localSyncVersion: " + localSyncVersion +
- ", serverSyncVersion: " + serverSyncVersion);
- }
- }
- } else {
- // the local db doesn't know about this record so add it
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
- }
- insert = true;
- }
-
- if (update) {
- updateRow(localRowId, serverDiffs, diffsCursor);
- syncResult.stats.numUpdates++;
- } else if (conflict) {
- resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
- syncResult.stats.numUpdates++;
- } else if (insert) {
- insertRow(serverDiffs, diffsCursor);
- syncResult.stats.numInserts++;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "processed " + diffsCount + " server entries");
- }
-
- // If tombstones aren't in use delete any remaining local rows that
- // don't have corresponding server rows. Keep the rows that don't
- // have a sync id since those were created locally and haven't been
- // synced to the server yet.
- if (!diffsArePartial) {
- while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
- if (mIsMergeCancelled) {
- return;
- }
- localCount++;
- final String localSyncId = localCursor.getString(2);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "deleting local record " +
- localCursor.getLong(1) +
- " _sync_id " + localSyncId);
- }
- deleteRow(localCursor);
- if (mDeletedTable != null) {
- mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
- }
- syncResult.stats.numDeletes++;
- mDb.yieldIfContended();
- }
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
- " local entries");
- } finally {
- if (diffsCursor != null) diffsCursor.close();
- if (localCursor != null) localCursor.close();
- if (deletedCursor != null) deletedCursor.close();
- }
-
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
-
- // Apply deletions from the server
- if (mDeletedTableURL != null) {
- diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
- try {
- while (diffsCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- // delete all rows that match each element in the diffsCursor
- fullyDeleteMatchingRows(diffsCursor, account, syncResult);
- mDb.yieldIfContended();
- }
- } finally {
- diffsCursor.close();
- }
- }
- }
-
- private void fullyDeleteMatchingRows(Cursor diffsCursor, Account account,
- SyncResult syncResult) {
- int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
- final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
-
- // delete the rows explicitly so that the delete operation can be overridden
- final String[] selectionArgs;
- Cursor c = null;
- try {
- if (deleteBySyncId) {
- selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn),
- account.name, account.type};
- c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
- selectionArgs, null, null, null);
- } else {
- int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
- selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
- c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
- null, null, null);
- }
- c.moveToFirst();
- while (!c.isAfterLast()) {
- deleteRow(c); // advances the cursor
- syncResult.stats.numDeletes++;
- }
- } finally {
- if (c != null) c.close();
- }
- if (deleteBySyncId && mDeletedTable != null) {
- mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
- }
- }
-
- /**
- * Converts cursor into a Map, using the correct types for the values.
- */
- protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
- DatabaseUtils.cursorRowToContentValues(cursor, map);
- }
-
- /**
- * Finds local changes, placing the results in the given result object.
- * @param temporaryInstanceFactory As an optimization for the case
- * where there are no client-side diffs, mergeResult may initially
- * have no {@link TempProviderSyncResult#tempContentProvider}. If this is
- * the first in the sequence of AbstractTableMergers to find
- * client-side diffs, it will use the given ContentProvider to
- * create a temporary instance and store its {@link
- * android.content.ContentProvider} in the mergeResult.
- * @param account
- * @param syncResult
- */
- private void findLocalChanges(TempProviderSyncResult mergeResult,
- SyncableContentProvider temporaryInstanceFactory, Account account,
- SyncResult syncResult) {
- SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
-
- final String[] accountSelectionArgs = new String[]{account.name, account.type};
-
- // Generate the client updates and insertions
- // Create a cursor for dirty records
- long numInsertsOrUpdates = 0;
- Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
- null, null, null);
- try {
- numInsertsOrUpdates = localChangesCursor.getCount();
- while (localChangesCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- if (clientDiffs == null) {
- clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
- }
- mValues.clear();
- cursorRowToContentValues(localChangesCursor, mValues);
- mValues.remove("_id");
- DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
- _SYNC_LOCAL_ID);
- clientDiffs.insert(mTableURL, mValues);
- }
- } finally {
- localChangesCursor.close();
- }
-
- // Generate the client deletions
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
- long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
- long numDeletedEntries = 0;
- if (mDeletedTable != null) {
- Cursor deletedCursor = mDb.query(mDeletedTable,
- syncIdAndVersionProjection,
- _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=? AND "
- + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
- null, null, mDeletedTable + "." + _SYNC_ID);
- try {
- numDeletedEntries = deletedCursor.getCount();
- while (deletedCursor.moveToNext()) {
- if (mIsMergeCancelled) {
- return;
- }
- if (clientDiffs == null) {
- clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
- }
- mValues.clear();
- DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
- clientDiffs.insert(mDeletedTableURL, mValues);
- }
- } finally {
- deletedCursor.close();
- }
- }
-
- if (clientDiffs != null) {
- mergeResult.tempContentProvider = clientDiffs;
- }
- syncResult.stats.numDeletes += numDeletedEntries;
- syncResult.stats.numUpdates += numInsertsOrUpdates;
- syncResult.stats.numEntries += numEntries;
- }
-}
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index fb6091a..9dd7b9f 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -18,10 +18,9 @@ package android.content;
import android.accounts.Account;
import android.os.Bundle;
-import android.os.Process;
-import android.os.NetStat;
import android.os.IBinder;
-import android.util.EventLog;
+import android.os.Process;
+import android.os.RemoteException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -36,6 +35,12 @@ import java.util.concurrent.atomic.AtomicInteger;
* that the sync has been canceled.
*/
public abstract class AbstractThreadedSyncAdapter {
+ /**
+ * Kernel event log tag. Also listed in data/etc/event-log-tags.
+ * @Deprecated
+ */
+ public static final int LOG_SYNC_DETAILS = 2743;
+
private final Context mContext;
private final AtomicInteger mNumSyncStarts;
private final ISyncAdapterImpl mISyncAdapterImpl;
@@ -44,9 +49,6 @@ public abstract class AbstractThreadedSyncAdapter {
private SyncThread mSyncThread;
private final Object mSyncThreadLock = new Object();
- /** Kernel event log tag. Also listed in data/etc/event-log-tags. */
- public static final int LOG_SYNC_DETAILS = 2743;
- private static final String TAG = "Sync";
private final boolean mAutoInitialize;
/**
@@ -109,14 +111,21 @@ public abstract class AbstractThreadedSyncAdapter {
public void cancelSync(ISyncContext syncContext) {
// synchronize to make sure that mSyncThread doesn't change between when we
// check it and when we use it
+ final SyncThread syncThread;
synchronized (mSyncThreadLock) {
- if (mSyncThread != null
- && mSyncThread.mSyncContext.getSyncContextBinder()
- == syncContext.asBinder()) {
- mSyncThread.interrupt();
- }
+ syncThread = mSyncThread;
+ }
+ if (syncThread != null
+ && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
+ onSyncCanceled();
}
}
+
+ public void initialize(Account account, String authority) throws RemoteException {
+ Bundle extras = new Bundle();
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ startSync(null, authority, account, extras);
+ }
}
/**
@@ -129,8 +138,6 @@ public abstract class AbstractThreadedSyncAdapter {
private final String mAuthority;
private final Account mAccount;
private final Bundle mExtras;
- private long mInitialTxBytes;
- private long mInitialRxBytes;
private SyncThread(String name, SyncContext syncContext, String authority,
Account account, Bundle extras) {
@@ -149,9 +156,6 @@ public abstract class AbstractThreadedSyncAdapter {
}
SyncResult syncResult = new SyncResult();
- int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
ContentProviderClient provider = null;
try {
provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
@@ -168,8 +172,6 @@ public abstract class AbstractThreadedSyncAdapter {
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
- onLogSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, syncResult);
// synchronize so that the assignment will be seen by other threads
// that also synchronize accesses to mSyncThread
synchronized (mSyncThreadLock) {
@@ -206,16 +208,18 @@ public abstract class AbstractThreadedSyncAdapter {
String authority, ContentProviderClient provider, SyncResult syncResult);
/**
- * Logs details on the sync.
- * Normally this will be overridden by a subclass that will provide
- * provider-specific details.
+ * Indicates that a sync operation has been canceled. This will be invoked on a separate
+ * thread than the sync thread and so you must consider the multi-threaded implications
+ * of the work that you do in this method.
*
- * @param bytesSent number of bytes the sync sent over the network
- * @param bytesReceived number of bytes the sync received over the network
- * @param result The SyncResult object holding info on the sync
- * @hide
*/
- protected void onLogSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
- EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
+ public void onSyncCanceled() {
+ final SyncThread syncThread;
+ synchronized (mSyncThreadLock) {
+ syncThread = mSyncThread;
+ }
+ if (syncThread != null) {
+ syncThread.interrupt();
+ }
}
}
diff --git a/core/java/android/content/AsyncQueryHandler.java b/core/java/android/content/AsyncQueryHandler.java
index 0a4a804..882879b 100644
--- a/core/java/android/content/AsyncQueryHandler.java
+++ b/core/java/android/content/AsyncQueryHandler.java
@@ -38,7 +38,6 @@ public abstract class AsyncQueryHandler extends Handler {
private static final int EVENT_ARG_INSERT = 2;
private static final int EVENT_ARG_UPDATE = 3;
private static final int EVENT_ARG_DELETE = 4;
- private static final int EVENT_ARG_QUERY_ENTITIES = 5;
/* package */ final WeakReference<ContentResolver> mResolver;
@@ -93,18 +92,6 @@ public abstract class AsyncQueryHandler extends Handler {
args.result = cursor;
break;
- case EVENT_ARG_QUERY_ENTITIES:
- EntityIterator iterator = null;
- try {
- iterator = resolver.queryEntities(args.uri, args.selection,
- args.selectionArgs, args.orderBy);
- } catch (Exception e) {
- Log.w(TAG, e.toString());
- }
-
- args.result = iterator;
- break;
-
case EVENT_ARG_INSERT:
args.result = resolver.insert(args.uri, args.values);
break;
@@ -195,45 +182,6 @@ public abstract class AsyncQueryHandler extends Handler {
}
/**
- * This method begins an asynchronous query for an {@link EntityIterator}.
- * When the query is done {@link #onQueryEntitiesComplete} is called.
- *
- * @param token A token passed into {@link #onQueryComplete} to identify the
- * query.
- * @param cookie An object that gets passed into {@link #onQueryComplete}
- * @param uri The URI, using the content:// scheme, for the content to
- * retrieve.
- * @param selection A filter declaring which rows to return, formatted as an
- * SQL WHERE clause (excluding the WHERE itself). Passing null
- * will return all rows for the given URI.
- * @param selectionArgs You may include ?s in selection, which will be
- * replaced by the values from selectionArgs, in the order that
- * they appear in the selection. The values will be bound as
- * Strings.
- * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
- * (excluding the ORDER BY itself). Passing null will use the
- * default sort order, which may be unordered.
- * @hide
- */
- public void startQueryEntities(int token, Object cookie, Uri uri, String selection,
- String[] selectionArgs, String orderBy) {
- // Use the token as what so cancelOperations works properly
- Message msg = mWorkerThreadHandler.obtainMessage(token);
- msg.arg1 = EVENT_ARG_QUERY_ENTITIES;
-
- WorkerArgs args = new WorkerArgs();
- args.handler = this;
- args.uri = uri;
- args.selection = selection;
- args.selectionArgs = selectionArgs;
- args.orderBy = orderBy;
- args.cookie = cookie;
- msg.obj = args;
-
- mWorkerThreadHandler.sendMessage(msg);
- }
-
- /**
* Attempts to cancel operation that has not already started. Note that
* there is no guarantee that the operation will be canceled. They still may
* result in a call to on[Query/Insert/Update/Delete]Complete after this
@@ -340,18 +288,6 @@ public abstract class AsyncQueryHandler extends Handler {
}
/**
- * Called when an asynchronous query is completed.
- *
- * @param token The token to identify the query.
- * @param cookie The cookie object.
- * @param iterator The iterator holding the query results.
- * @hide
- */
- protected void onQueryEntitiesComplete(int token, Object cookie, EntityIterator iterator) {
- // Empty
- }
-
- /**
* Called when an asynchronous insert is completed.
*
* @param token the token to identify the query, passed in from
@@ -408,10 +344,6 @@ public abstract class AsyncQueryHandler extends Handler {
onQueryComplete(token, args.cookie, (Cursor) args.result);
break;
- case EVENT_ARG_QUERY_ENTITIES:
- onQueryEntitiesComplete(token, args.cookie, (EntityIterator)args.result);
- break;
-
case EVENT_ARG_INSERT:
onInsertComplete(token, args.cookie, (Uri) args.result);
break;
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 0455202..7ca0f01 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -30,7 +30,7 @@ import java.lang.Comparable;
* name inside of that package.
*
*/
-public final class ComponentName implements Parcelable, Comparable<ComponentName> {
+public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
private final String mPackage;
private final String mClass;
@@ -76,6 +76,10 @@ public final class ComponentName implements Parcelable, Comparable<ComponentName
mClass = cls.getName();
}
+ public ComponentName clone() {
+ return new ComponentName(mPackage, mClass);
+ }
+
/**
* Return the package name of this component.
*/
@@ -122,7 +126,7 @@ public final class ComponentName implements Parcelable, Comparable<ComponentName
}
/**
- * The samee as {@link #flattenToString()}, but abbreviates the class
+ * The same as {@link #flattenToString()}, but abbreviates the class
* name if it is a suffix of the package. The result can still be used
* with {@link #unflattenFromString(String)}.
*
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index a341c9b..5fb2aae 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -29,6 +29,7 @@ import android.database.IContentObserver;
import android.database.SQLException;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -67,6 +68,11 @@ import java.util.ArrayList;
* process a request is coming from.</p>
*/
public abstract class ContentProvider implements ComponentCallbacks {
+ /*
+ * Note: if you add methods to ContentProvider, you must add similar methods to
+ * MockContentProvider.
+ */
+
private Context mContext = null;
private int mMyUid;
private String mReadPermission;
@@ -75,6 +81,33 @@ public abstract class ContentProvider implements ComponentCallbacks {
private Transport mTransport = new Transport();
+ public ContentProvider() {
+ }
+
+ /**
+ * Constructor just for mocking.
+ *
+ * @param context A Context object which should be some mock instance (like the
+ * instance of {@link android.test.mock.MockContext}).
+ * @param readPermission The read permision you want this instance should have in the
+ * test, which is available via {@link #getReadPermission()}.
+ * @param writePermission The write permission you want this instance should have
+ * in the test, which is available via {@link #getWritePermission()}.
+ * @param pathPermissions The PathPermissions you want this instance should have
+ * in the test, which is available via {@link #getPathPermissions()}.
+ * @hide
+ */
+ public ContentProvider(
+ Context context,
+ String readPermission,
+ String writePermission,
+ PathPermission[] pathPermissions) {
+ mContext = context;
+ mReadPermission = readPermission;
+ mWritePermission = writePermission;
+ mPathPermissions = pathPermissions;
+ }
+
/**
* Given an IContentProvider, try to coerce it back to the real
* ContentProvider object if it is running in the local process. This can
@@ -131,15 +164,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
selectionArgs, sortOrder);
}
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) {
- enforceReadPermission(uri);
- return ContentProvider.this.queryEntities(uri, selection, selectionArgs, sortOrder);
- }
-
public String getType(Uri uri) {
return ContentProvider.this.getType(uri);
}
@@ -194,6 +218,13 @@ public abstract class ContentProvider implements ComponentCallbacks {
return ContentProvider.this.openAssetFile(uri, mode);
}
+ /**
+ * @hide
+ */
+ public Bundle call(String method, String request, Bundle args) {
+ return ContentProvider.this.call(method, request, args);
+ }
+
private void enforceReadPermission(Uri uri) {
final int uid = Binder.getCallingUid();
if (uid == mMyUid) {
@@ -445,14 +476,6 @@ public abstract class ContentProvider implements ComponentCallbacks {
String selection, String[] selectionArgs, String sortOrder);
/**
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException();
- }
-
- /**
* Return the MIME type of the data at the given URI. This should start with
* <code>vnd.android.cursor.item</code> for a single record,
* or <code>vnd.android.cursor.dir/</code> for multiple items.
@@ -549,7 +572,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
/**
* Open a file blob associated with a content URI.
* This method can be called from multiple
- * threads, as described inentity
+ * threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
* Processes and Threads</a>.
*
@@ -733,4 +756,18 @@ public abstract class ContentProvider implements ComponentCallbacks {
}
return results;
}
-} \ No newline at end of file
+
+ /**
+ * @hide -- until interface has proven itself
+ *
+ * Call an provider-defined method. This can be used to implement
+ * interfaces that are cheaper than using a Cursor.
+ *
+ * @param method Method name to call. Opaque to framework.
+ * @param request Nullable String argument passed to method.
+ * @param args Nullable Bundle argument passed to method.
+ */
+ public Bundle call(String method, String request, Bundle args) {
+ return null;
+ }
+}
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 403c4d8..0858ea5 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -89,16 +89,7 @@ public class ContentProviderClient {
return mContentProvider.openAssetFile(url, mode);
}
- /**
- * see {@link ContentProvider#queryEntities}
- * @hide
- */
- public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs,
- String sortOrder) throws RemoteException {
- return mContentProvider.queryEntities(uri, selection, selectionArgs, sortOrder);
- }
-
- /** see {@link ContentProvider#applyBatch} */
+ /** see {@link ContentProvider#applyBatch} */
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
return mContentProvider.applyBatch(operations);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index adc3f60..fdb3d20 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -26,6 +26,7 @@ import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
@@ -72,7 +73,10 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case QUERY_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+
Uri url = Uri.CREATOR.createFromParcel(data);
+
+ // String[] projection
int num = data.readInt();
String[] projection = null;
if (num > 0) {
@@ -81,6 +85,8 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
projection[i] = data.readString();
}
}
+
+ // String selection, String[] selectionArgs...
String selection = data.readString();
num = data.readInt();
String[] selectionArgs = null;
@@ -90,33 +96,33 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
selectionArgs[i] = data.readString();
}
}
+
String sortOrder = data.readString();
IContentObserver observer = IContentObserver.Stub.
asInterface(data.readStrongBinder());
CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
+ // Flag for whether caller wants the number of
+ // rows in the cursor and the position of the
+ // "_id" column index (or -1 if non-existent)
+ // Only to be returned if binder != null.
+ boolean wantsCursorMetadata = data.readInt() != 0;
+
IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
selectionArgs, sortOrder, observer, window);
reply.writeNoException();
if (bulkCursor != null) {
reply.writeStrongBinder(bulkCursor.asBinder());
+
+ if (wantsCursorMetadata) {
+ reply.writeInt(bulkCursor.count());
+ reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
+ bulkCursor.getColumnNames()));
+ }
} else {
reply.writeStrongBinder(null);
}
- return true;
- }
- case QUERY_ENTITIES_TRANSACTION:
- {
- data.enforceInterface(IContentProvider.descriptor);
- Uri url = Uri.CREATOR.createFromParcel(data);
- String selection = data.readString();
- String[] selectionArgs = data.readStringArray();
- String sortOrder = data.readString();
- EntityIterator entityIterator = queryEntities(url, selection, selectionArgs,
- sortOrder);
- reply.writeNoException();
- reply.writeStrongBinder(new IEntityIteratorImpl(entityIterator).asBinder());
return true;
}
@@ -236,6 +242,21 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
}
return true;
}
+
+ case CALL_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+
+ String method = data.readString();
+ String stringArg = data.readString();
+ Bundle args = data.readBundle();
+
+ Bundle responseBundle = call(method, stringArg, args);
+
+ reply.writeNoException();
+ reply.writeBundle(responseBundle);
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -245,32 +266,6 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
return super.onTransact(code, data, reply, flags);
}
- /**
- * @hide
- */
- private class IEntityIteratorImpl extends IEntityIterator.Stub {
- private final EntityIterator mEntityIterator;
-
- IEntityIteratorImpl(EntityIterator iterator) {
- mEntityIterator = iterator;
- }
- public boolean hasNext() throws RemoteException {
- return mEntityIterator.hasNext();
- }
-
- public Entity next() throws RemoteException {
- return mEntityIterator.next();
- }
-
- public void reset() throws RemoteException {
- mEntityIterator.reset();
- }
-
- public void close() throws RemoteException {
- mEntityIterator.close();
- }
- }
-
public IBinder asBinder()
{
return this;
@@ -290,9 +285,12 @@ final class ContentProviderProxy implements IContentProvider
return mRemote;
}
- public IBulkCursor bulkQuery(Uri url, String[] projection,
- String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
- CursorWindow window) throws RemoteException {
+ // Like bulkQuery() but sets up provided 'adaptor' if not null.
+ private IBulkCursor bulkQueryInternal(
+ Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder,
+ IContentObserver observer, CursorWindow window,
+ BulkCursorToCursorAdaptor adaptor) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -321,6 +319,12 @@ final class ContentProviderProxy implements IContentProvider
data.writeStrongBinder(observer.asBinder());
window.writeToParcel(data, 0);
+ // Flag for whether or not we want the number of rows in the
+ // cursor and the position of the "_id" column index (or -1 if
+ // non-existent). Only to be returned if binder != null.
+ final boolean wantsCursorMetadata = (adaptor != null);
+ data.writeInt(wantsCursorMetadata ? 1 : 0);
+
mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
@@ -329,87 +333,46 @@ final class ContentProviderProxy implements IContentProvider
IBinder bulkCursorBinder = reply.readStrongBinder();
if (bulkCursorBinder != null) {
bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
+
+ if (wantsCursorMetadata) {
+ int rowCount = reply.readInt();
+ int idColumnPosition = reply.readInt();
+ if (bulkCursor != null) {
+ adaptor.set(bulkCursor, rowCount, idColumnPosition);
+ }
+ }
}
-
+
data.recycle();
reply.recycle();
-
+
return bulkCursor;
}
+ public IBulkCursor bulkQuery(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException {
+ return bulkQueryInternal(
+ url, projection, selection, selectionArgs, sortOrder,
+ observer, window,
+ null /* BulkCursorToCursorAdaptor */);
+ }
+
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException {
//TODO make a pool of windows so we can reuse memory dealers
CursorWindow window = new CursorWindow(false /* window will be used remotely */);
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
- IBulkCursor bulkCursor = bulkQuery(url, projection, selection, selectionArgs, sortOrder,
- adaptor.getObserver(), window);
-
+ IBulkCursor bulkCursor = bulkQueryInternal(
+ url, projection, selection, selectionArgs, sortOrder,
+ adaptor.getObserver(), window,
+ adaptor);
if (bulkCursor == null) {
return null;
}
- adaptor.set(bulkCursor);
return adaptor;
}
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs,
- String sortOrder)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
-
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(selection);
- data.writeStringArray(selectionArgs);
- data.writeString(sortOrder);
-
- mRemote.transact(IContentProvider.QUERY_ENTITIES_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
-
- IBinder entityIteratorBinder = reply.readStrongBinder();
-
- data.recycle();
- reply.recycle();
-
- return new RemoteEntityIterator(IEntityIterator.Stub.asInterface(entityIteratorBinder));
- }
-
- /**
- * @hide
- */
- static class RemoteEntityIterator implements EntityIterator {
- private final IEntityIterator mEntityIterator;
- RemoteEntityIterator(IEntityIterator entityIterator) {
- mEntityIterator = entityIterator;
- }
-
- public boolean hasNext() throws RemoteException {
- return mEntityIterator.hasNext();
- }
-
- public Entity next() throws RemoteException {
- return mEntityIterator.next();
- }
-
- public void reset() throws RemoteException {
- mEntityIterator.reset();
- }
-
- public void close() {
- try {
- mEntityIterator.close();
- } catch (RemoteException e) {
- // doesn't matter
- }
- }
- }
-
public String getType(Uri url) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -583,6 +546,27 @@ final class ContentProviderProxy implements IContentProvider
return fd;
}
+ public Bundle call(String method, String request, Bundle args)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(method);
+ data.writeString(request);
+ data.writeBundle(args);
+
+ mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Bundle bundle = reply.readBundle();
+
+ data.recycle();
+ reply.recycle();
+
+ return bundle;
+ }
+
private IBinder mRemote;
}
-
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index ca36df2..7945f3f 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -21,6 +21,7 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
@@ -46,6 +47,8 @@ public class ContentProviderOperation implements Parcelable {
private final Map<Integer, Integer> mSelectionArgsBackReferences;
private final boolean mYieldAllowed;
+ private final static String TAG = "ContentProviderOperation";
+
/**
* Creates a {@link ContentProviderOperation} by copying the contents of a
* {@link Builder}.
@@ -241,6 +244,7 @@ public class ContentProviderOperation implements Parcelable {
final String expectedValue = values.getAsString(projection[i]);
if (!TextUtils.equals(cursorValue, expectedValue)) {
// Throw exception when expected values don't match
+ Log.e(TAG, this.toString());
throw new OperationApplicationException("Found value " + cursorValue
+ " when expected " + expectedValue + " for column "
+ projection[i]);
@@ -252,10 +256,12 @@ public class ContentProviderOperation implements Parcelable {
cursor.close();
}
} else {
+ Log.e(TAG, this.toString());
throw new IllegalStateException("bad type, " + mType);
}
if (mExpectedCount != null && mExpectedCount != numRows) {
+ Log.e(TAG, this.toString());
throw new OperationApplicationException("wrong number of rows: " + numRows);
}
@@ -289,6 +295,7 @@ public class ContentProviderOperation implements Parcelable {
String key = entry.getKey();
Integer backRefIndex = mValuesBackReferences.getAsInteger(key);
if (backRefIndex == null) {
+ Log.e(TAG, this.toString());
throw new IllegalArgumentException("values backref " + key + " is not an integer");
}
values.put(key, backRefToValue(backRefs, numBackRefs, backRefIndex));
@@ -326,6 +333,17 @@ public class ContentProviderOperation implements Parcelable {
return newArgs;
}
+ @Override
+ public String toString() {
+ return "mType: " + mType + ", mUri: " + mUri +
+ ", mSelection: " + mSelection +
+ ", mExpectedCount: " + mExpectedCount +
+ ", mYieldAllowed: " + mYieldAllowed +
+ ", mValues: " + mValues +
+ ", mValuesBackReferences: " + mValuesBackReferences +
+ ", mSelectionArgsBackReferences: " + mSelectionArgsBackReferences;
+ }
+
/**
* Return the string representation of the requested back reference.
* @param backRefs an array of results
@@ -335,9 +353,10 @@ public class ContentProviderOperation implements Parcelable {
* the numBackRefs
* @return the string representation of the requested back reference.
*/
- private static long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
+ private long backRefToValue(ContentProviderResult[] backRefs, int numBackRefs,
Integer backRefIndex) {
if (backRefIndex >= numBackRefs) {
+ Log.e(TAG, this.toString());
throw new ArrayIndexOutOfBoundsException("asked for back ref " + backRefIndex
+ " but there are only " + numBackRefs + " back refs");
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c4b0807..b4718ab 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -16,6 +16,8 @@
package android.content;
+import android.accounts.Account;
+import android.app.ActivityThread;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -29,9 +31,10 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.text.TextUtils;
-import android.accounts.Account;
import android.util.Config;
+import android.util.EventLog;
import android.util.Log;
import java.io.File;
@@ -41,6 +44,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
+import java.util.Random;
import java.util.ArrayList;
@@ -61,7 +65,31 @@ public abstract class ContentResolver {
*/
@Deprecated
public static final String SYNC_EXTRAS_FORCE = "force";
+
+ /**
+ * If this extra is set to true then the sync settings (like getSyncAutomatically())
+ * are ignored by the sync scheduler.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_SETTINGS = "ignore_settings";
+
+ /**
+ * If this extra is set to true then any backoffs for the initial attempt (e.g. due to retries)
+ * are ignored by the sync scheduler. If this request fails and gets rescheduled then the
+ * retries will still honor the backoff.
+ */
+ public static final String SYNC_EXTRAS_IGNORE_BACKOFF = "ignore_backoff";
+
+ /**
+ * If this extra is set to true then the request will not be retried if it fails.
+ */
+ public static final String SYNC_EXTRAS_DO_NOT_RETRY = "do_not_retry";
+
+ /**
+ * Setting this extra is the equivalent of setting both {@link #SYNC_EXTRAS_IGNORE_SETTINGS}
+ * and {@link #SYNC_EXTRAS_IGNORE_BACKOFF}
+ */
public static final String SYNC_EXTRAS_MANUAL = "force";
+
public static final String SYNC_EXTRAS_UPLOAD = "upload";
public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
@@ -88,11 +116,11 @@ public abstract class ContentResolver {
* <code>content://com.company.provider.imap/inbox/1</code> for a particular
* message in the inbox, whose MIME type would be reported as
* <code>CURSOR_ITEM_BASE_TYPE + "/vnd.company.imap-msg"</code>
- *
+ *
* <p>Compare with {@link #CURSOR_DIR_BASE_TYPE}.
*/
public static final String CURSOR_ITEM_BASE_TYPE = "vnd.android.cursor.item";
-
+
/**
* This is the Android platform's base MIME type for a content: URI
* containing a Cursor of zero or more items. Applications should use this
@@ -102,7 +130,7 @@ public abstract class ContentResolver {
* <code>content://com.company.provider.imap/inbox</code> for all of the
* messages in its inbox, whose MIME type would be reported as
* <code>CURSOR_DIR_BASE_TYPE + "/vnd.company.imap-msg"</code>
- *
+ *
* <p>Note how the base MIME type varies between this and
* {@link #CURSOR_ITEM_BASE_TYPE} depending on whether there is
* one single item or multiple items in the data set, while the sub-type
@@ -128,17 +156,19 @@ public abstract class ContentResolver {
/** @hide */
public static final int SYNC_ERROR_INTERNAL = 8;
- /** @hide */
public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
- /** @hide */
public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
- /** @hide */
public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
/** @hide */
public static final int SYNC_OBSERVER_TYPE_STATUS = 1<<3;
/** @hide */
public static final int SYNC_OBSERVER_TYPE_ALL = 0x7fffffff;
+ // Always log queries which take 500ms+; shorter queries are
+ // sampled accordingly.
+ private static final int SLOW_THRESHOLD_MILLIS = 500;
+ private final Random mRandom = new Random(); // guarded by itself
+
public ContentResolver(Context context) {
mContext = context;
}
@@ -173,13 +203,25 @@ public abstract class ContentResolver {
}
/**
+ * <p>
* Query the given URI, returning a {@link Cursor} over the result set.
+ * </p>
+ * <p>
+ * For best performance, the caller should follow these guidelines:
+ * <ul>
+ * <li>Provide an explicit projection, to prevent
+ * reading data from storage that aren't going to be used.</li>
+ * <li>Use question mark parameter markers such as 'phone=?' instead of
+ * explicit values in the {@code selection} parameter, so that queries
+ * that differ only by those values will be recognized as the same
+ * for caching purposes.</li>
+ * </ul>
+ * </p>
*
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
- * return all columns, which is discouraged to prevent reading data
- * from storage that isn't going to be used.
+ * return all columns, which is inefficient.
* @param selection A filter declaring which rows to return, formatted as an
* SQL WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URI.
@@ -199,12 +241,17 @@ public abstract class ContentResolver {
return null;
}
try {
+ long startTime = SystemClock.uptimeMillis();
Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
- if(qCursor == null) {
+ if (qCursor == null) {
releaseProvider(provider);
return null;
}
- //Wrap the cursor object into CursorWrapperInner object
+ // force query execution
+ qCursor.getCount();
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
+ // Wrap the cursor object into CursorWrapperInner object
return new CursorWrapperInner(qCursor, provider);
} catch (RemoteException e) {
releaseProvider(provider);
@@ -216,96 +263,6 @@ public abstract class ContentResolver {
}
/**
- * EntityIterator wrapper that releases the associated ContentProviderClient when the
- * iterator is closed.
- * @hide
- */
- private class EntityIteratorWrapper implements EntityIterator {
- private final EntityIterator mInner;
- private final ContentProviderClient mClient;
- private volatile boolean mClientReleased;
-
- EntityIteratorWrapper(EntityIterator inner, ContentProviderClient client) {
- mInner = inner;
- mClient = client;
- mClientReleased = false;
- }
-
- public boolean hasNext() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- return mInner.hasNext();
- }
-
- public Entity next() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- return mInner.next();
- }
-
- public void reset() throws RemoteException {
- if (mClientReleased) {
- throw new IllegalStateException("this iterator is already closed");
- }
- mInner.reset();
- }
-
- public void close() {
- mClient.release();
- mInner.close();
- mClientReleased = true;
- }
-
- protected void finalize() throws Throwable {
- if (!mClientReleased) {
- mClient.release();
- }
- super.finalize();
- }
- }
-
- /**
- * Query the given URI, returning an {@link EntityIterator} over the result set.
- *
- * @param uri The URI, using the content:// scheme, for the content to
- * retrieve.
- * @param selection A filter declaring which rows to return, formatted as an
- * SQL WHERE clause (excluding the WHERE itself). Passing null will
- * return all rows for the given URI.
- * @param selectionArgs You may include ?s in selection, which will be
- * replaced by the values from selectionArgs, in the order that they
- * appear in the selection. The values will be bound as Strings.
- * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
- * clause (excluding the ORDER BY itself). Passing null will use the
- * default sort order, which may be unordered.
- * @return An EntityIterator object
- * @throws RemoteException thrown if a RemoteException is encountered while attempting
- * to communicate with a remote provider.
- * @throws IllegalArgumentException thrown if there is no provider that matches the uri
- * @hide
- */
- public final EntityIterator queryEntities(Uri uri,
- String selection, String[] selectionArgs, String sortOrder) throws RemoteException {
- ContentProviderClient provider = acquireContentProviderClient(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URL " + uri);
- }
- try {
- EntityIterator entityIterator =
- provider.queryEntities(uri, selection, selectionArgs, sortOrder);
- return new EntityIteratorWrapper(entityIterator, provider);
- } catch(RuntimeException e) {
- provider.release();
- throw e;
- } catch(RemoteException e) {
- provider.release();
- throw e;
- }
- }
-
- /**
* Open a stream on to the content associated with a content URI. If there
* is no data associated with the URI, FileNotFoundException is thrown.
*
@@ -315,10 +272,10 @@ public abstract class ContentResolver {
* <li>android.resource ({@link #SCHEME_ANDROID_RESOURCE})</li>
* <li>file ({@link #SCHEME_FILE})</li>
* </ul>
- *
+ *
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI.
* @return InputStream
* @throws FileNotFoundException if the provided URI could not be opened.
@@ -373,7 +330,7 @@ public abstract class ContentResolver {
*
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI.
* @param mode May be "w", "wa", "rw", or "rwt".
* @return OutputStream
@@ -408,7 +365,7 @@ public abstract class ContentResolver {
*
* <p>See {@link #openAssetFileDescriptor(Uri, String)} for more information
* on these schemes.
- *
+ *
* @param uri The desired URI to open.
* @param mode The file mode to use, as per {@link ContentProvider#openFile
* ContentProvider.openFile}.
@@ -424,19 +381,19 @@ public abstract class ContentResolver {
if (afd == null) {
return null;
}
-
+
if (afd.getDeclaredLength() < 0) {
// This is a full file!
return afd.getParcelFileDescriptor();
}
-
+
// Client can't handle a sub-section of a file, so close what
// we got and bail with an exception.
try {
afd.close();
} catch (IOException e) {
}
-
+
throw new FileNotFoundException("Not a whole file");
}
@@ -581,7 +538,7 @@ public abstract class ContentResolver {
res.id = id;
return res;
}
-
+
/** @hide */
static public int modeToMode(Uri uri, String mode) throws FileNotFoundException {
int modeBits;
@@ -608,7 +565,7 @@ public abstract class ContentResolver {
}
return modeBits;
}
-
+
/**
* Inserts a row into a table at the given URL.
*
@@ -626,7 +583,11 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
- return provider.insert(url, values);
+ long startTime = SystemClock.uptimeMillis();
+ Uri createdRow = provider.insert(url, values);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
+ return createdRow;
} catch (RemoteException e) {
return null;
} finally {
@@ -681,7 +642,11 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
- return provider.bulkInsert(url, values);
+ long startTime = SystemClock.uptimeMillis();
+ int rowsCreated = provider.bulkInsert(url, values);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
+ return rowsCreated;
} catch (RemoteException e) {
return 0;
} finally {
@@ -706,7 +671,11 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URL " + url);
}
try {
- return provider.delete(url, where, selectionArgs);
+ long startTime = SystemClock.uptimeMillis();
+ int rowsDeleted = provider.delete(url, where, selectionArgs);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
+ return rowsDeleted;
} catch (RemoteException e) {
return -1;
} finally {
@@ -722,7 +691,7 @@ public abstract class ContentResolver {
* @param uri The URI to modify.
* @param values The new field values. The key is the column name for the field.
A null value will remove an existing field value.
- * @param where A filter to apply to rows before deleting, formatted as an SQL WHERE clause
+ * @param where A filter to apply to rows before updating, formatted as an SQL WHERE clause
(excluding the WHERE itself).
* @return The number of rows updated.
* @throws NullPointerException if uri or values are null
@@ -734,7 +703,11 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
- return provider.update(uri, values, where, selectionArgs);
+ long startTime = SystemClock.uptimeMillis();
+ int rowsUpdated = provider.update(uri, values, where, selectionArgs);
+ long durationMillis = SystemClock.uptimeMillis() - startTime;
+ maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
+ return rowsUpdated;
} catch (RemoteException e) {
return -1;
} finally {
@@ -765,7 +738,7 @@ public abstract class ContentResolver {
* @hide
*/
public final IContentProvider acquireProvider(String name) {
- if(name == null) {
+ if (name == null) {
return null;
}
return acquireProvider(mContext, name);
@@ -1044,6 +1017,100 @@ public abstract class ContentResolver {
}
/**
+ * Specifies that a sync should be requested with the specified the account, authority,
+ * and extras at the given frequency. If there is already another periodic sync scheduled
+ * with the account, authority and extras then a new periodic sync won't be added, instead
+ * the frequency of the previous one will be updated.
+ * <p>
+ * These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings.
+ * Although these sync are scheduled at the specified frequency, it may take longer for it to
+ * actually be started if other syncs are ahead of it in the sync operation queue. This means
+ * that the actual start time may drift.
+ * <p>
+ * Periodic syncs are not allowed to have any of {@link #SYNC_EXTRAS_DO_NOT_RETRY},
+ * {@link #SYNC_EXTRAS_IGNORE_BACKOFF}, {@link #SYNC_EXTRAS_IGNORE_SETTINGS},
+ * {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
+ * {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true.
+ * If any are supplied then an {@link IllegalArgumentException} will be thrown.
+ *
+ * @param account the account to specify in the sync
+ * @param authority the provider to specify in the sync request
+ * @param extras extra parameters to go along with the sync request
+ * @param pollFrequency how frequently the sync should be performed, in seconds.
+ * @throws IllegalArgumentException if an illegal extra was set or if any of the parameters
+ * are null.
+ */
+ public static void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ validateSyncExtrasBundle(extras);
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ if (extras.getBoolean(SYNC_EXTRAS_MANUAL, false)
+ || extras.getBoolean(SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || extras.getBoolean(SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || extras.getBoolean(SYNC_EXTRAS_INITIALIZE, false)
+ || extras.getBoolean(SYNC_EXTRAS_FORCE, false)
+ || extras.getBoolean(SYNC_EXTRAS_EXPEDITED, false)) {
+ throw new IllegalArgumentException("illegal extras were set");
+ }
+ try {
+ getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+ } catch (RemoteException e) {
+ // exception ignored; if this is thrown then it means the runtime is in the midst of
+ // being restarted
+ }
+ }
+
+ /**
+ * Remove a periodic sync. Has no affect if account, authority and extras don't match
+ * an existing periodic sync.
+ *
+ * @param account the account of the periodic sync to remove
+ * @param authority the provider of the periodic sync to remove
+ * @param extras the extras of the periodic sync to remove
+ */
+ public static void removePeriodicSync(Account account, String authority, Bundle extras) {
+ validateSyncExtrasBundle(extras);
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ try {
+ getContentService().removePeriodicSync(account, authority, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
+ * Get the list of information about the periodic syncs for the given account and authority.
+ *
+ * @param account the account whose periodic syncs we are querying
+ * @param authority the provider whose periodic syncs we are querying
+ * @return a list of PeriodicSync objects. This list may be empty but will never be null.
+ */
+ public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) {
+ if (account == null) {
+ throw new IllegalArgumentException("account must not be null");
+ }
+ if (authority == null) {
+ throw new IllegalArgumentException("authority must not be null");
+ }
+ try {
+ return getContentService().getPeriodicSyncs(account, authority);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Check if this account/provider is syncable.
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
*/
@@ -1114,12 +1181,11 @@ public abstract class ContentResolver {
/**
* If a sync is active returns the information about it, otherwise returns false.
- * @return the ActiveSyncInfo for the currently active sync or null if one is not active.
- * @hide
+ * @return the SyncInfo for the currently active sync or null if one is not active.
*/
- public static ActiveSyncInfo getActiveSync() {
+ public static SyncInfo getCurrentSync() {
try {
- return getContentService().getActiveSync();
+ return getContentService().getCurrentSync();
} catch (RemoteException e) {
throw new RuntimeException("the ContentService should always be reachable", e);
}
@@ -1154,7 +1220,24 @@ public abstract class ContentResolver {
}
}
+ /**
+ * Request notifications when the different aspects of the SyncManager change. The
+ * different items that can be requested are:
+ * <ul>
+ * <li> {@link #SYNC_OBSERVER_TYPE_PENDING}
+ * <li> {@link #SYNC_OBSERVER_TYPE_ACTIVE}
+ * <li> {@link #SYNC_OBSERVER_TYPE_SETTINGS}
+ * </ul>
+ * The caller can set one or more of the status types in the mask for any
+ * given listener registration.
+ * @param mask the status change types that will cause the callback to be invoked
+ * @param callback observer to be invoked when the status changes
+ * @return a handle that can be used to remove the listener at a later time
+ */
public static Object addStatusChangeListener(int mask, final SyncStatusObserver callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("you passed in a null callback");
+ }
try {
ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
public void onStatusChanged(int which) throws RemoteException {
@@ -1168,7 +1251,14 @@ public abstract class ContentResolver {
}
}
+ /**
+ * Remove a previously registered status change listener.
+ * @param handle the handle that was returned by {@link #addStatusChangeListener}
+ */
public static void removeStatusChangeListener(Object handle) {
+ if (handle == null) {
+ throw new IllegalArgumentException("you passed in a null handle");
+ }
try {
getContentService().removeStatusChangeListener((ISyncStatusObserver.Stub) handle);
} catch (RemoteException e) {
@@ -1177,6 +1267,78 @@ public abstract class ContentResolver {
}
}
+ /**
+ * Returns sampling percentage for a given duration.
+ *
+ * Always returns at least 1%.
+ */
+ private int samplePercentForDuration(long durationMillis) {
+ if (durationMillis >= SLOW_THRESHOLD_MILLIS) {
+ return 100;
+ }
+ return (int) (100 * durationMillis / SLOW_THRESHOLD_MILLIS) + 1;
+ }
+
+ private void maybeLogQueryToEventLog(long durationMillis,
+ Uri uri, String[] projection,
+ String selection, String sortOrder) {
+ int samplePercent = samplePercentForDuration(durationMillis);
+ if (samplePercent < 100) {
+ synchronized (mRandom) {
+ if (mRandom.nextInt(100) >= samplePercent) {
+ return;
+ }
+ }
+ }
+
+ StringBuilder projectionBuffer = new StringBuilder(100);
+ if (projection != null) {
+ for (int i = 0; i < projection.length; ++i) {
+ // Note: not using a comma delimiter here, as the
+ // multiple arguments to EventLog.writeEvent later
+ // stringify with a comma delimiter, which would make
+ // parsing uglier later.
+ if (i != 0) projectionBuffer.append('/');
+ projectionBuffer.append(projection[i]);
+ }
+ }
+
+ // ActivityThread.currentPackageName() only returns non-null if the
+ // current thread is an application main thread. This parameter tells
+ // us whether an event loop is blocked, and if so, which app it is.
+ String blockingPackage = ActivityThread.currentPackageName();
+
+ EventLog.writeEvent(
+ EventLogTags.CONTENT_QUERY_SAMPLE,
+ uri.toString(),
+ projectionBuffer.toString(),
+ selection != null ? selection : "",
+ sortOrder != null ? sortOrder : "",
+ durationMillis,
+ blockingPackage != null ? blockingPackage : "",
+ samplePercent);
+ }
+
+ private void maybeLogUpdateToEventLog(
+ long durationMillis, Uri uri, String operation, String selection) {
+ int samplePercent = samplePercentForDuration(durationMillis);
+ if (samplePercent < 100) {
+ synchronized (mRandom) {
+ if (mRandom.nextInt(100) >= samplePercent) {
+ return;
+ }
+ }
+ }
+ String blockingPackage = ActivityThread.currentPackageName();
+ EventLog.writeEvent(
+ EventLogTags.CONTENT_UPDATE_SAMPLE,
+ uri.toString(),
+ operation,
+ selection != null ? selection : "",
+ durationMillis,
+ blockingPackage != null ? blockingPackage : "",
+ samplePercent);
+ }
private final class CursorWrapperInner extends CursorWrapper {
private IContentProvider mContentProvider;
@@ -1236,7 +1398,7 @@ public abstract class ContentResolver {
/** @hide */
public static final String CONTENT_SERVICE_NAME = "content";
-
+
/** @hide */
public static IContentService getContentService() {
if (sContentService != null) {
@@ -1248,7 +1410,7 @@ public abstract class ContentResolver {
if (Config.LOGV) Log.v("ContentService", "default service = " + sContentService);
return sContentService;
}
-
+
private static IContentService sContentService;
private final Context mContext;
private static final String TAG = "ContentResolver";
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 974a667..377e383 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -32,6 +32,7 @@ import android.Manifest;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
/**
* {@hide}
@@ -102,7 +103,7 @@ public final class ContentService extends IContentService.Stub {
throw new IllegalArgumentException("You must pass a valid uri and observer");
}
synchronized (mRootNode) {
- mRootNode.addObserver(uri, observer, notifyForDescendents);
+ mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode);
if (Config.LOGV) Log.v(TAG, "Registered observer " + observer + " at " + uri +
" with notifyForDescendents " + notifyForDescendents);
}
@@ -113,7 +114,7 @@ public final class ContentService extends IContentService.Stub {
throw new IllegalArgumentException("You must pass a valid observer");
}
synchronized (mRootNode) {
- mRootNode.removeObserver(observer);
+ mRootNode.removeObserverLocked(observer);
if (Config.LOGV) Log.v(TAG, "Unregistered observer " + observer);
}
}
@@ -130,7 +131,7 @@ public final class ContentService extends IContentService.Stub {
try {
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
- mRootNode.collectObservers(uri, 0, observer, observerWantsSelfNotifications,
+ mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
calls);
}
final int numCalls = calls.size();
@@ -273,6 +274,42 @@ public final class ContentService extends IContentService.Stub {
}
}
+ public void addPeriodicSync(Account account, String authority, Bundle extras,
+ long pollFrequency) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().addPeriodicSync(
+ account, authority, extras, pollFrequency);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public void removePeriodicSync(Account account, String authority, Bundle extras) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+ "no permission to read the sync settings");
+ long identityToken = clearCallingIdentity();
+ try {
+ return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+ account, providerName);
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
public int getIsSyncable(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
@@ -349,14 +386,14 @@ public final class ContentService extends IContentService.Stub {
return false;
}
- public ActiveSyncInfo getActiveSync() {
+ public SyncInfo getCurrentSync() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getActiveSync();
+ return syncManager.getSyncStorageEngine().getCurrentSync();
}
} finally {
restoreCallingIdentity(identityToken);
@@ -399,7 +436,7 @@ public final class ContentService extends IContentService.Stub {
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
+ if (syncManager != null && callback != null) {
syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
}
} finally {
@@ -411,7 +448,7 @@ public final class ContentService extends IContentService.Stub {
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
+ if (syncManager != null && callback != null) {
syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
}
} finally {
@@ -432,10 +469,12 @@ public final class ContentService extends IContentService.Stub {
*/
public static final class ObserverNode {
private class ObserverEntry implements IBinder.DeathRecipient {
- public IContentObserver observer;
- public boolean notifyForDescendents;
+ public final IContentObserver observer;
+ public final boolean notifyForDescendents;
+ private final Object observersLock;
- public ObserverEntry(IContentObserver o, boolean n) {
+ public ObserverEntry(IContentObserver o, boolean n, Object observersLock) {
+ this.observersLock = observersLock;
observer = o;
notifyForDescendents = n;
try {
@@ -446,7 +485,9 @@ public final class ContentService extends IContentService.Stub {
}
public void binderDied() {
- removeObserver(observer);
+ synchronized (observersLock) {
+ removeObserverLocked(observer);
+ }
}
}
@@ -481,16 +522,16 @@ public final class ContentService extends IContentService.Stub {
return uri.getPathSegments().size() + 1;
}
- public void addObserver(Uri uri, IContentObserver observer, boolean notifyForDescendents) {
- addObserver(uri, 0, observer, notifyForDescendents);
+ public void addObserverLocked(Uri uri, IContentObserver observer,
+ boolean notifyForDescendents, Object observersLock) {
+ addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock);
}
- private void addObserver(Uri uri, int index, IContentObserver observer,
- boolean notifyForDescendents) {
-
+ private void addObserverLocked(Uri uri, int index, IContentObserver observer,
+ boolean notifyForDescendents, Object observersLock) {
// If this is the leaf node add the observer
if (index == countUriSegments(uri)) {
- mObservers.add(new ObserverEntry(observer, notifyForDescendents));
+ mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock));
return;
}
@@ -500,7 +541,7 @@ public final class ContentService extends IContentService.Stub {
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (node.mName.equals(segment)) {
- node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
return;
}
}
@@ -508,13 +549,13 @@ public final class ContentService extends IContentService.Stub {
// No child found, create one
ObserverNode node = new ObserverNode(segment);
mChildren.add(node);
- node.addObserver(uri, index + 1, observer, notifyForDescendents);
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
}
- public boolean removeObserver(IContentObserver observer) {
+ public boolean removeObserverLocked(IContentObserver observer) {
int size = mChildren.size();
for (int i = 0; i < size; i++) {
- boolean empty = mChildren.get(i).removeObserver(observer);
+ boolean empty = mChildren.get(i).removeObserverLocked(observer);
if (empty) {
mChildren.remove(i);
i--;
@@ -540,10 +581,8 @@ public final class ContentService extends IContentService.Stub {
return false;
}
- private void collectMyObservers(Uri uri,
- boolean leaf, IContentObserver observer, boolean selfNotify,
- ArrayList<ObserverCall> calls)
- {
+ private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+ boolean selfNotify, ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
@@ -562,17 +601,17 @@ public final class ContentService extends IContentService.Stub {
}
}
- public void collectObservers(Uri uri, int index, IContentObserver observer,
+ public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
boolean selfNotify, ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
- collectMyObservers(uri, true, observer, selfNotify, calls);
+ collectMyObserversLocked(true, observer, selfNotify, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
// Notify any observers at this level who are interested in descendents
- collectMyObservers(uri, false, observer, selfNotify, calls);
+ collectMyObserversLocked(false, observer, selfNotify, calls);
}
int N = mChildren.size();
@@ -580,7 +619,7 @@ public final class ContentService extends IContentService.Stub {
ObserverNode node = mChildren.get(i);
if (segment == null || node.mName.equals(segment)) {
// We found the child,
- node.collectObservers(uri, index + 1, observer, selfNotify, calls);
+ node.collectObserversLocked(uri, index + 1, observer, selfNotify, calls);
if (segment != null) {
break;
}
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 532cc03..75787cd 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -39,14 +39,14 @@ public final class ContentValues implements Parcelable {
* Creates an empty set of values using the default initial size
*/
public ContentValues() {
- // Choosing a default size of 8 based on analysis of typical
+ // Choosing a default size of 8 based on analysis of typical
// consumption by applications.
mValues = new HashMap<String, Object>(8);
}
/**
* Creates an empty set of values using the given initial size
- *
+ *
* @param size the initial size of the set of values
*/
public ContentValues(int size) {
@@ -55,7 +55,7 @@ public final class ContentValues implements Parcelable {
/**
* Creates a set of values copied from the given set
- *
+ *
* @param from the values to copy
*/
public ContentValues(ContentValues from) {
@@ -65,8 +65,8 @@ public final class ContentValues implements Parcelable {
/**
* Creates a set of values copied from the given HashMap. This is used
* by the Parcel unmarshalling code.
- *
- * @param from the values to start with
+ *
+ * @param values the values to start with
* {@hide}
*/
private ContentValues(HashMap<String, Object> values) {
@@ -174,7 +174,7 @@ public final class ContentValues implements Parcelable {
public void put(String key, Boolean value) {
mValues.put(key, value);
}
-
+
/**
* Adds a value to the set.
*
@@ -187,7 +187,7 @@ public final class ContentValues implements Parcelable {
/**
* Adds a null value to the set.
- *
+ *
* @param key the name of the value to make null
*/
public void putNull(String key) {
@@ -196,7 +196,7 @@ public final class ContentValues implements Parcelable {
/**
* Returns the number of values.
- *
+ *
* @return the number of values
*/
public int size() {
@@ -223,7 +223,7 @@ public final class ContentValues implements Parcelable {
* Returns true if this object has the named value.
*
* @param key the value to check for
- * @return {@code true} if the value is present, {@code false} otherwise
+ * @return {@code true} if the value is present, {@code false} otherwise
*/
public boolean containsKey(String key) {
return mValues.containsKey(key);
@@ -242,18 +242,18 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a String.
- *
+ *
* @param key the value to get
* @return the String for the value
*/
public String getAsString(String key) {
Object value = mValues.get(key);
- return value != null ? mValues.get(key).toString() : null;
+ return value != null ? value.toString() : null;
}
/**
* Gets a value and converts it to a Long.
- *
+ *
* @param key the value to get
* @return the Long value, or null if the value is missing or cannot be converted
*/
@@ -270,7 +270,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Long");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e);
return null;
}
}
@@ -278,7 +278,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to an Integer.
- *
+ *
* @param key the value to get
* @return the Integer value, or null if the value is missing or cannot be converted
*/
@@ -295,7 +295,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Integer");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e);
return null;
}
}
@@ -303,7 +303,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a Short.
- *
+ *
* @param key the value to get
* @return the Short value, or null if the value is missing or cannot be converted
*/
@@ -320,7 +320,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Short");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Short: " + value, e);
return null;
}
}
@@ -328,7 +328,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a Byte.
- *
+ *
* @param key the value to get
* @return the Byte value, or null if the value is missing or cannot be converted
*/
@@ -345,7 +345,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Byte");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Byte: " + value, e);
return null;
}
}
@@ -353,7 +353,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a Double.
- *
+ *
* @param key the value to get
* @return the Double value, or null if the value is missing or cannot be converted
*/
@@ -370,7 +370,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Double");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Double: " + value, e);
return null;
}
}
@@ -378,7 +378,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a Float.
- *
+ *
* @param key the value to get
* @return the Float value, or null if the value is missing or cannot be converted
*/
@@ -395,7 +395,7 @@ public final class ContentValues implements Parcelable {
return null;
}
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Float");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Float: " + value, e);
return null;
}
}
@@ -403,7 +403,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value and converts it to a Boolean.
- *
+ *
* @param key the value to get
* @return the Boolean value, or null if the value is missing or cannot be converted
*/
@@ -415,7 +415,7 @@ public final class ContentValues implements Parcelable {
if (value instanceof CharSequence) {
return Boolean.valueOf(value.toString());
} else {
- Log.e(TAG, "Cannot cast value for " + key + " to a Boolean");
+ Log.e(TAG, "Cannot cast value for " + key + " to a Boolean: " + value, e);
return null;
}
}
@@ -424,7 +424,7 @@ public final class ContentValues implements Parcelable {
/**
* Gets a value that is a byte array. Note that this method will not convert
* any other types to byte arrays.
- *
+ *
* @param key the value to get
* @return the byte[] value, or null is the value is missing or not a byte[]
*/
@@ -439,13 +439,13 @@ public final class ContentValues implements Parcelable {
/**
* Returns a set of all of the keys and values
- *
+ *
* @return a set of all of the keys and values
*/
public Set<Map.Entry<String, Object>> valueSet() {
return mValues.entrySet();
}
-
+
public static final Parcelable.Creator<ContentValues> CREATOR =
new Parcelable.Creator<ContentValues>() {
@SuppressWarnings({"deprecation", "unchecked"})
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 799bc22..30822d4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -104,6 +104,18 @@ public abstract class Context {
*/
public static final int BIND_DEBUG_UNBIND = 0x0002;
+ /**
+ * Flag for {@link #bindService}: don't allow this binding to raise
+ * the target service's process to the foreground scheduling priority.
+ * It will still be raised to the at least the same memory priority
+ * as the client (so that its process will not be killable in any
+ * situation where the client is not killable), but for CPU scheduling
+ * purposes it may be left in the background. This only has an impact
+ * in the situation where the binding client is a foreground process
+ * and the target service is in a background process.
+ */
+ public static final int BIND_NOT_FOREGROUND = 0x0004;
+
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
@@ -125,7 +137,28 @@ public abstract class Context {
/**
* Return the context of the single, global Application object of the
- * current process.
+ * current process. This generally should only be used if you need a
+ * Context whose lifecycle is separate from the current context, that is
+ * tied to the lifetime of the process rather than the current component.
+ *
+ * <p>Consider for example how this interacts with
+ * {@ #registerReceiver(BroadcastReceiver, IntentFilter)}:
+ * <ul>
+ * <li> <p>If used from an Activity context, the receiver is being registered
+ * within that activity. This means that you are expected to unregister
+ * before the activity is done being destroyed; in fact if you do not do
+ * so, the framework will clean up your leaked registration as it removes
+ * the activity and log an error. Thus, if you use the Activity context
+ * to register a receiver that is static (global to the process, not
+ * associated with an Activity instance) then that registration will be
+ * removed on you at whatever point the activity you used is destroyed.
+ * <li> <p>If used from the Context returned here, the receiver is being
+ * registered with the global state associated with your application. Thus
+ * it will never be unregistered for you. This is necessary if the receiver
+ * is associated with static data, not a particular component. However
+ * using the ApplicationContext elsewhere can easily lead to serious leaks
+ * if you forget to unregister, unbind, etc.
+ * </ul>
*/
public abstract Context getApplicationContext();
@@ -238,22 +271,21 @@ public abstract class Context {
public abstract ApplicationInfo getApplicationInfo();
/**
- * {@hide}
- * Return the full path to this context's resource files. This is the ZIP files
- * containing the application's resources.
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains the application's
+ * primary resources.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*
- *
* @return String Path to the resources.
*/
public abstract String getPackageResourcePath();
/**
- * {@hide}
- * Return the full path to this context's code and asset files. This is the ZIP files
- * containing the application's code and assets.
+ * Return the full path to this context's primary Android package.
+ * The Android package is a ZIP file which contains application's
+ * primary code and assets.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
@@ -382,11 +414,85 @@ public abstract class Context {
public abstract File getFilesDir();
/**
+ * Returns the absolute path to the directory on the external filesystem
+ * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
+ * Environment.getExternalStorageDirectory()}) where the application can
+ * place persistent files it owns. These files are private to the
+ * applications, and not typically visible to the user as media.
+ *
+ * <p>This is like {@link #getFilesDir()} in that these
+ * files will be deleted when the application is uninstalled, however there
+ * are some important differences:
+ *
+ * <ul>
+ * <li>External files are not always available: they will disappear if the
+ * user mounts the external storage on a computer or removes it. See the
+ * APIs on {@link android.os.Environment} for information in the storage state.
+ * <li>There is no security enforced with these files. All applications
+ * can read and write files placed here.
+ * </ul>
+ *
+ * <p>Here is an example of typical code to manipulate a file in
+ * an application's private storage:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * private_file}
+ *
+ * <p>If you supply a non-null <var>type</var> to this function, the returned
+ * file will be a path to a sub-directory of the given type. Though these files
+ * are not automatically scanned by the media scanner, you can explicitly
+ * add them to the media database with
+ * {@link android.media.MediaScannerConnection#scanFile(Context, String[], String[],
+ * OnScanCompletedListener) MediaScannerConnection.scanFile}.
+ * Note that this is not the same as
+ * {@link android.os.Environment#getExternalStoragePublicDirectory
+ * Environment.getExternalStoragePublicDirectory()}, which provides
+ * directories of media shared by all applications. The
+ * directories returned here are
+ * owned by the application, and their contents will be removed when the
+ * application is uninstalled. Unlike
+ * {@link android.os.Environment#getExternalStoragePublicDirectory
+ * Environment.getExternalStoragePublicDirectory()}, the directory
+ * returned here will be automatically created for you.
+ *
+ * <p>Here is an example of typical code to manipulate a picture in
+ * an application's private storage and add it to the media database:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * private_picture}
+ *
+ * @param type The type of files directory to return. May be null for
+ * the root of the files directory or one of
+ * the following Environment constants for a subdirectory:
+ * {@link android.os.Environment#DIRECTORY_MUSIC},
+ * {@link android.os.Environment#DIRECTORY_PODCASTS},
+ * {@link android.os.Environment#DIRECTORY_RINGTONES},
+ * {@link android.os.Environment#DIRECTORY_ALARMS},
+ * {@link android.os.Environment#DIRECTORY_NOTIFICATIONS},
+ * {@link android.os.Environment#DIRECTORY_PICTURES}, or
+ * {@link android.os.Environment#DIRECTORY_MOVIES}.
+ *
+ * @return Returns the path of the directory holding application files
+ * on external storage. Returns null if external storage is not currently
+ * mounted so it could not ensure the path exists; you will need to call
+ * this method again when it is available.
+ *
+ * @see #getFilesDir
+ * @see android.os.Environment#getExternalStoragePublicDirectory
+ */
+ public abstract File getExternalFilesDir(String type);
+
+ /**
* Returns the absolute path to the application specific cache directory
* on the filesystem. These files will be ones that get deleted first when the
- * device runs low on storage
+ * device runs low on storage.
* There is no guarantee when these files will be deleted.
- *
+ *
+ * <strong>Note: you should not <em>rely</em> on the system deleting these
+ * files for you; you should always have a reasonable maximum, such as 1 MB,
+ * for the amount of space you consume with cache files, and prune those
+ * files when exceeding that space.</strong>
+ *
* @return Returns the path of the directory holding application cache files.
*
* @see #openFileOutput
@@ -396,6 +502,37 @@ public abstract class Context {
public abstract File getCacheDir();
/**
+ * Returns the absolute path to the directory on the external filesystem
+ * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory()
+ * Environment.getExternalStorageDirectory()} where the application can
+ * place cache files it owns.
+ *
+ * <p>This is like {@link #getCacheDir()} in that these
+ * files will be deleted when the application is uninstalled, however there
+ * are some important differences:
+ *
+ * <ul>
+ * <li>The platform does not monitor the space available in external storage,
+ * and thus will not automatically delete these files. Note that you should
+ * be managing the maximum space you will use for these anyway, just like
+ * with {@link #getCacheDir()}.
+ * <li>External files are not always available: they will disappear if the
+ * user mounts the external storage on a computer or removes it. See the
+ * APIs on {@link android.os.Environment} for information in the storage state.
+ * <li>There is no security enforced with these files. All applications
+ * can read and write files placed here.
+ * </ul>
+ *
+ * @return Returns the path of the directory holding application cache files
+ * on external storage. Returns null if external storage is not currently
+ * mounted so it could not ensure the path exists; you will need to call
+ * this method again when it is available.
+ *
+ * @see #getCacheDir
+ */
+ public abstract File getExternalCacheDir();
+
+ /**
* Returns an array of strings naming the private files associated with
* this Context's application package.
*
@@ -1067,6 +1204,8 @@ public abstract class Context {
* <dt> {@link #INPUT_METHOD_SERVICE} ("input_method")
* <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager}
* for management of input methods.
+ * <dt> {@link #UI_MODE_SERVICE} ("uimode")
+ * <dd> An {@link android.app.UiModeManager} for controlling UI modes.
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -1098,6 +1237,8 @@ public abstract class Context {
* @see android.app.SearchManager
* @see #SENSOR_SERVICE
* @see android.hardware.SensorManager
+ * @see #STORAGE_SERVICE
+ * @see android.os.storage.StorageManager
* @see #VIBRATOR_SERVICE
* @see android.os.Vibrator
* @see #CONNECTIVITY_SERVICE
@@ -1110,6 +1251,8 @@ public abstract class Context {
* @see android.telephony.TelephonyManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
+ * @see #UI_MODE_SERVICE
+ * @see android.app.UiModeManager
*/
public abstract Object getSystemService(String name);
@@ -1120,6 +1263,7 @@ public abstract class Context {
* you're running long tasks.
*/
public static final String POWER_SERVICE = "power";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.WindowManager} for accessing the system's window
@@ -1129,6 +1273,7 @@ public abstract class Context {
* @see android.view.WindowManager
*/
public static final String WINDOW_SERVICE = "window";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.LayoutInflater} for inflating layout resources in this
@@ -1138,6 +1283,7 @@ public abstract class Context {
* @see android.view.LayoutInflater
*/
public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.accounts.AccountManager} for receiving intents at a
@@ -1147,6 +1293,7 @@ public abstract class Context {
* @see android.accounts.AccountManager
*/
public static final String ACCOUNT_SERVICE = "account";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.ActivityManager} for interacting with the global
@@ -1156,6 +1303,7 @@ public abstract class Context {
* @see android.app.ActivityManager
*/
public static final String ACTIVITY_SERVICE = "activity";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.AlarmManager} for receiving intents at a
@@ -1165,6 +1313,7 @@ public abstract class Context {
* @see android.app.AlarmManager
*/
public static final String ALARM_SERVICE = "alarm";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for informing the user of
@@ -1174,6 +1323,7 @@ public abstract class Context {
* @see android.app.NotificationManager
*/
public static final String NOTIFICATION_SERVICE = "notification";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.accessibility.AccessibilityManager} for giving the user
@@ -1183,6 +1333,7 @@ public abstract class Context {
* @see android.view.accessibility.AccessibilityManager
*/
public static final String ACCESSIBILITY_SERVICE = "accessibility";
+
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
@@ -1191,6 +1342,7 @@ public abstract class Context {
* @see android.app.KeyguardManager
*/
public static final String KEYGUARD_SERVICE = "keyguard";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.location.LocationManager} for controlling location
@@ -1200,6 +1352,7 @@ public abstract class Context {
* @see android.location.LocationManager
*/
public static final String LOCATION_SERVICE = "location";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.app.SearchManager} for handling searches.
@@ -1208,6 +1361,7 @@ public abstract class Context {
* @see android.app.SearchManager
*/
public static final String SEARCH_SERVICE = "search";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.hardware.SensorManager} for accessing sensors.
@@ -1216,6 +1370,18 @@ public abstract class Context {
* @see android.hardware.SensorManager
*/
public static final String SENSOR_SERVICE = "sensor";
+
+ /**
+ * @hide
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.os.storage.StorageManager} for accesssing system storage
+ * functions.
+ *
+ * @see #getSystemService
+ * @see android.os.storage.StorageManager
+ */
+ public static final String STORAGE_SERVICE = "storage";
+
/**
* Use with {@link #getSystemService} to retrieve a
* com.android.server.WallpaperService for accessing wallpapers.
@@ -1223,6 +1389,7 @@ public abstract class Context {
* @see #getSystemService
*/
public static final String WALLPAPER_SERVICE = "wallpaper";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.os.Vibrator} for interacting with the vibration hardware.
@@ -1231,6 +1398,7 @@ public abstract class Context {
* @see android.os.Vibrator
*/
public static final String VIBRATOR_SERVICE = "vibrator";
+
/**
* Use with {@link #getSystemService} to retrieve a {@link
* android.app.StatusBarManager} for interacting with the status bar.
@@ -1253,6 +1421,28 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.ThrottleManager} for handling management of
+ * throttling.
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.net.ThrottleManager
+ */
+ public static final String THROTTLE_SERVICE = "throttle";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.NetworkManagementService} for handling management of
+ * system network services
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.net.NetworkManagementService
+ */
+ public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.wifi.WifiManager} for handling management of
* Wi-Fi access.
*
@@ -1302,23 +1492,48 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@blink android.appwidget.AppWidgetManager} for accessing AppWidgets.
+ * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
*
* @hide
* @see #getSystemService
*/
public static final String APPWIDGET_SERVICE = "appwidget";
-
+
/**
* Use with {@link #getSystemService} to retrieve an
- * {@blink android.backup.IBackupManager IBackupManager} for communicating
+ * {@link android.app.backup.IBackupManager IBackupManager} for communicating
* with the backup mechanism.
* @hide
*
* @see #getSystemService
*/
public static final String BACKUP_SERVICE = "backup";
-
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.os.DropBoxManager} instance for recording
+ * diagnostic logs.
+ * @see #getSystemService
+ */
+ public static final String DROPBOX_SERVICE = "dropbox";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.admin.DevicePolicyManager} for working with global
+ * device policy management.
+ *
+ * @see #getSystemService
+ */
+ public static final String DEVICE_POLICY_SERVICE = "device_policy";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.UiModeManager} for controlling UI modes.
+ *
+ * @see #getSystemService
+ */
+ public static final String UI_MODE_SERVICE = "uimode";
+
/**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 1b34320..a447108 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -179,11 +179,21 @@ public class ContextWrapper extends Context {
}
@Override
+ public File getExternalFilesDir(String type) {
+ return mBase.getExternalFilesDir(type);
+ }
+
+ @Override
public File getCacheDir() {
return mBase.getCacheDir();
}
@Override
+ public File getExternalCacheDir() {
+ return mBase.getExternalCacheDir();
+ }
+
+ @Override
public File getDir(String name, int mode) {
return mBase.getDir(name, mode);
}
diff --git a/core/java/android/content/CursorEntityIterator.java b/core/java/android/content/CursorEntityIterator.java
new file mode 100644
index 0000000..18437e5
--- /dev/null
+++ b/core/java/android/content/CursorEntityIterator.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.database.Cursor;
+import android.os.RemoteException;
+
+/**
+ * Abstract implementation of EntityIterator that makes it easy to wrap a cursor
+ * that can contain several consecutive rows for an entity.
+ * @hide
+ */
+public abstract class CursorEntityIterator implements EntityIterator {
+ private final Cursor mCursor;
+ private boolean mIsClosed;
+
+ /**
+ * Constructor that makes initializes the cursor such that the iterator points to the
+ * first Entity, if there are any.
+ * @param cursor the cursor that contains the rows that make up the entities
+ */
+ public CursorEntityIterator(Cursor cursor) {
+ mIsClosed = false;
+ mCursor = cursor;
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Returns the entity that the cursor is currently pointing to. This must take care to advance
+ * the cursor past this entity. This will never be called if the cursor is at the end.
+ * @param cursor the cursor that contains the entity rows
+ * @return the entity that the cursor is currently pointing to
+ * @throws RemoteException if a RemoteException is caught while attempting to build the Entity
+ */
+ public abstract Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException;
+
+ /**
+ * Returns whether there are more elements to iterate, i.e. whether the
+ * iterator is positioned in front of an element.
+ *
+ * @return {@code true} if there are more elements, {@code false} otherwise.
+ * @see EntityIterator#next()
+ */
+ public final boolean hasNext() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling hasNext() when the iterator is closed");
+ }
+
+ return !mCursor.isAfterLast();
+ }
+
+ /**
+ * Returns the next object in the iteration, i.e. returns the element in
+ * front of the iterator and advances the iterator by one position.
+ *
+ * @return the next object.
+ * @throws java.util.NoSuchElementException
+ * if there are no more elements.
+ * @see EntityIterator#hasNext()
+ */
+ public Entity next() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling next() when the iterator is closed");
+ }
+ if (!hasNext()) {
+ throw new IllegalStateException("you may only call next() if hasNext() is true");
+ }
+
+ try {
+ return getEntityAndIncrementCursor(mCursor);
+ } catch (RemoteException e) {
+ throw new RuntimeException("caught a remote exception, this process will die soon", e);
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove not supported by EntityIterators");
+ }
+
+ public final void reset() {
+ if (mIsClosed) {
+ throw new IllegalStateException("calling reset() when the iterator is closed");
+ }
+ mCursor.moveToFirst();
+ }
+
+ /**
+ * Indicates that this iterator is no longer needed and that any associated resources
+ * may be released (such as a SQLite cursor).
+ */
+ public final void close() {
+ if (mIsClosed) {
+ throw new IllegalStateException("closing when already closed");
+ }
+ mIsClosed = true;
+ mCursor.close();
+ }
+}
diff --git a/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java
index 9f1036e..947eac6 100644
--- a/core/java/android/content/DialogInterface.java
+++ b/core/java/android/content/DialogInterface.java
@@ -94,7 +94,6 @@ public interface DialogInterface {
/**
* Interface used to allow the creator of a dialog to run some code when the
* dialog is shown.
- * @hide Pending API council approval
*/
interface OnShowListener {
/**
diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java
index ee8112e..7842de0 100644
--- a/core/java/android/content/Entity.java
+++ b/core/java/android/content/Entity.java
@@ -24,11 +24,13 @@ import android.util.Log;
import java.util.ArrayList;
/**
- * Objects that pass through the ContentProvider and ContentResolver's methods that deal with
- * Entities must implement this abstract base class and thus themselves be Parcelable.
- * @hide
+ * A representation of a item using ContentValues. It contains one top level ContentValue
+ * plus a collection of Uri, ContentValues tuples as subvalues. One example of its use
+ * is in Contacts, where the top level ContentValue contains the columns from the RawContacts
+ * table and the subvalues contain a ContentValues object for each row from the Data table that
+ * corresponds to that RawContact. The uri refers to the Data table uri for each row.
*/
-public final class Entity implements Parcelable {
+public final class Entity {
final private ContentValues mValues;
final private ArrayList<NamedContentValues> mSubValues;
@@ -49,40 +51,6 @@ public final class Entity implements Parcelable {
mSubValues.add(new Entity.NamedContentValues(uri, values));
}
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- mValues.writeToParcel(dest, 0);
- dest.writeInt(mSubValues.size());
- for (NamedContentValues value : mSubValues) {
- value.uri.writeToParcel(dest, 0);
- value.values.writeToParcel(dest, 0);
- }
- }
-
- private Entity(Parcel source) {
- mValues = ContentValues.CREATOR.createFromParcel(source);
- final int numValues = source.readInt();
- mSubValues = new ArrayList<NamedContentValues>(numValues);
- for (int i = 0; i < numValues; i++) {
- final Uri uri = Uri.CREATOR.createFromParcel(source);
- final ContentValues values = ContentValues.CREATOR.createFromParcel(source);
- mSubValues.add(new NamedContentValues(uri, values));
- }
- }
-
- public static final Creator<Entity> CREATOR = new Creator<Entity>() {
- public Entity createFromParcel(Parcel source) {
- return new Entity(source);
- }
-
- public Entity[] newArray(int size) {
- return new Entity[size];
- }
- };
-
public static class NamedContentValues {
public final Uri uri;
public final ContentValues values;
diff --git a/core/java/android/content/EntityIterator.java b/core/java/android/content/EntityIterator.java
index 1b73439..55c47ba 100644
--- a/core/java/android/content/EntityIterator.java
+++ b/core/java/android/content/EntityIterator.java
@@ -16,35 +16,20 @@
package android.content;
-import android.os.RemoteException;
+import java.util.Iterator;
/**
- * @hide
+ * A specialization of {@link Iterator} that allows iterating over a collection of
+ * {@link Entity} objects. In addition to the iteration functionality it also allows
+ * resetting the iterator back to the beginning and provides for an explicit {@link #close()}
+ * method to indicate that the iterator is no longer needed and that its resources
+ * can be released.
*/
-public interface EntityIterator {
+public interface EntityIterator extends Iterator<Entity> {
/**
- * Returns whether there are more elements to iterate, i.e. whether the
- * iterator is positioned in front of an element.
- *
- * @return {@code true} if there are more elements, {@code false} otherwise.
- * @see #next
- * @since Android 1.0
+ * Reset the iterator back to the beginning.
*/
- public boolean hasNext() throws RemoteException;
-
- /**
- * Returns the next object in the iteration, i.e. returns the element in
- * front of the iterator and advances the iterator by one position.
- *
- * @return the next object.
- * @throws java.util.NoSuchElementException
- * if there are no more elements.
- * @see #hasNext
- * @since Android 1.0
- */
- public Entity next() throws RemoteException;
-
- public void reset() throws RemoteException;
+ public void reset();
/**
* Indicates that this iterator is no longer needed and that any associated resources
diff --git a/core/java/android/content/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags
new file mode 100644
index 0000000..21ea90a
--- /dev/null
+++ b/core/java/android/content/EventLogTags.logtags
@@ -0,0 +1,7 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.content;
+
+52002 content_query_sample (uri|3),(projection|3),(selection|3),(sortorder|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
+52003 content_update_sample (uri|3),(operation|3),(selection|3),(time|1|3),(blocking_package|3),(sample_percent|1|6)
+52004 binder_sample (descriptor|3),(method_num|1|5),(time|1|3),(blocking_package|3),(sample_percent|1|6)
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 0798adf..67e7581 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -22,10 +22,11 @@ import android.database.CursorWindow;
import android.database.IBulkCursor;
import android.database.IContentObserver;
import android.net.Uri;
-import android.os.RemoteException;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import java.io.FileNotFoundException;
import java.util.ArrayList;
@@ -44,12 +45,6 @@ public interface IContentProvider extends IInterface {
CursorWindow window) throws RemoteException;
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws RemoteException;
- /**
- * @hide
- */
- public EntityIterator queryEntities(Uri url, String selection,
- String[] selectionArgs, String sortOrder)
- throws RemoteException;
public String getType(Uri url) throws RemoteException;
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException;
@@ -64,6 +59,17 @@ public interface IContentProvider extends IInterface {
throws RemoteException, FileNotFoundException;
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException;
+ /**
+ * @hide -- until interface has proven itself
+ *
+ * Call an provider-defined method. This can be used to implement
+ * interfaces that are cheaper than using a Cursor.
+ *
+ * @param method Method name to call. Opaque to framework.
+ * @param request Nullable String argument passed to method.
+ * @param args Nullable Bundle argument passed to method.
+ */
+ public Bundle call(String method, String request, Bundle args) throws RemoteException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
@@ -76,9 +82,6 @@ public interface IContentProvider extends IInterface {
static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12;
static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13;
static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14;
- /**
- * @hide
- */
- static final int QUERY_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 18;
static final int APPLY_BATCH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 19;
+ static final int CALL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 20;
}
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index b0f14c1..a6368d5 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -17,10 +17,11 @@
package android.content;
import android.accounts.Account;
-import android.content.ActiveSyncInfo;
+import android.content.SyncInfo;
import android.content.ISyncStatusObserver;
import android.content.SyncAdapterType;
import android.content.SyncStatusInfo;
+import android.content.PeriodicSync;
import android.net.Uri;
import android.os.Bundle;
import android.database.IContentObserver;
@@ -38,11 +39,11 @@ interface IContentService {
void requestSync(in Account account, String authority, in Bundle extras);
void cancelSync(in Account account, String authority);
-
+
/**
* Check if the provider should be synced when a network tickle is received
* @param providerName the provider whose setting we are querying
- * @return true of the provider should be synced when a network tickle is received
+ * @return true if the provider should be synced when a network tickle is received
*/
boolean getSyncAutomatically(in Account account, String providerName);
@@ -55,6 +56,33 @@ interface IContentService {
void setSyncAutomatically(in Account account, String providerName, boolean sync);
/**
+ * Get the frequency of the periodic poll, if any.
+ * @param providerName the provider whose setting we are querying
+ * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs
+ * will take place.
+ */
+ List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName);
+
+ /**
+ * Set whether or not the provider is to be synced on a periodic basis.
+ *
+ * @param providerName the provider whose behavior is being controlled
+ * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+ * zero or less then no periodic syncs will be performed.
+ */
+ void addPeriodicSync(in Account account, String providerName, in Bundle extras,
+ long pollFrequency);
+
+ /**
+ * Set whether or not the provider is to be synced on a periodic basis.
+ *
+ * @param providerName the provider whose behavior is being controlled
+ * @param pollFrequency the period that a sync should be performed, in seconds. If this is
+ * zero or less then no periodic syncs will be performed.
+ */
+ void removePeriodicSync(in Account account, String providerName, in Bundle extras);
+
+ /**
* Check if this account/provider is syncable.
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
*/
@@ -69,15 +97,15 @@ interface IContentService {
void setMasterSyncAutomatically(boolean flag);
boolean getMasterSyncAutomatically();
-
+
/**
* Returns true if there is currently a sync operation for the given
* account or authority in the pending list, or actively being processed.
*/
boolean isSyncActive(in Account account, String authority);
-
- ActiveSyncInfo getActiveSync();
-
+
+ SyncInfo getCurrentSync();
+
/**
* Returns the types of the SyncAdapters that are registered with the system.
* @return Returns the types of the SyncAdapters that are registered with the system.
@@ -96,8 +124,8 @@ interface IContentService {
* Return true if the pending status is true of any matching authorities.
*/
boolean isSyncPending(in Account account, String authority);
-
+
void addStatusChangeListener(int mask, ISyncStatusObserver callback);
-
+
void removeStatusChangeListener(ISyncStatusObserver callback);
}
diff --git a/core/java/android/content/IEntityIterator.java b/core/java/android/content/IEntityIterator.java
deleted file mode 100644
index 068581e..0000000
--- a/core/java/android/content/IEntityIterator.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.Parcelable;
-import android.util.Log;
-
-/**
- * ICPC interface methods for an iterator over Entity objects.
- * @hide
- */
-public interface IEntityIterator extends IInterface {
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends Binder implements IEntityIterator {
- private static final String TAG = "IEntityIterator";
- private static final java.lang.String DESCRIPTOR = "android.content.IEntityIterator";
-
- /** Construct the stub at attach it to the interface. */
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an IEntityIterator interface,
- * generating a proxy if needed.
- */
- public static IEntityIterator asInterface(IBinder obj) {
- if ((obj==null)) {
- return null;
- }
- IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin!=null)&&(iin instanceof IEntityIterator))) {
- return ((IEntityIterator)iin);
- }
- return new IEntityIterator.Stub.Proxy(obj);
- }
-
- public IBinder asBinder() {
- return this;
- }
-
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case INTERFACE_TRANSACTION:
- {
- reply.writeString(DESCRIPTOR);
- return true;
- }
-
- case TRANSACTION_hasNext:
- {
- data.enforceInterface(DESCRIPTOR);
- boolean _result;
- try {
- _result = this.hasNext();
- } catch (Exception e) {
- Log.e(TAG, "caught exception in hasNext()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- reply.writeInt(((_result)?(1):(0)));
- return true;
- }
-
- case TRANSACTION_next:
- {
- data.enforceInterface(DESCRIPTOR);
- Entity entity;
- try {
- entity = this.next();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in next()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- entity.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- return true;
- }
-
- case TRANSACTION_reset:
- {
- data.enforceInterface(DESCRIPTOR);
- try {
- this.reset();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in next()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- return true;
- }
-
- case TRANSACTION_close:
- {
- data.enforceInterface(DESCRIPTOR);
- try {
- this.close();
- } catch (RemoteException e) {
- Log.e(TAG, "caught exception in close()", e);
- reply.writeException(e);
- return true;
- }
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- private static class Proxy implements IEntityIterator {
- private IBinder mRemote;
- Proxy(IBinder remote) {
- mRemote = remote;
- }
- public IBinder asBinder() {
- return mRemote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- }
- public boolean hasNext() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- boolean _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_hasNext, _data, _reply, 0);
- _reply.readException();
- _result = (0!=_reply.readInt());
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
-
- public Entity next() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_next, _data, _reply, 0);
- _reply.readException();
- return Entity.CREATOR.createFromParcel(_reply);
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- public void reset() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_reset, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
-
- public void close() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
- static final int TRANSACTION_hasNext = (IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_next = (IBinder.FIRST_CALL_TRANSACTION + 1);
- static final int TRANSACTION_close = (IBinder.FIRST_CALL_TRANSACTION + 2);
- static final int TRANSACTION_reset = (IBinder.FIRST_CALL_TRANSACTION + 3);
- }
- public boolean hasNext() throws RemoteException;
- public Entity next() throws RemoteException;
- public void reset() throws RemoteException;
- public void close() throws RemoteException;
-}
diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl
index 4660527..dd9d14e 100644
--- a/core/java/android/content/ISyncAdapter.aidl
+++ b/core/java/android/content/ISyncAdapter.aidl
@@ -44,4 +44,12 @@ oneway interface ISyncAdapter {
* @param syncContext the ISyncContext that was passed to {@link #startSync}
*/
void cancelSync(ISyncContext syncContext);
+
+ /**
+ * Initialize the SyncAdapter for this account and authority.
+ *
+ * @param account the account that should be synced
+ * @param authority the authority that should be synced
+ */
+ void initialize(in Account account, String authority);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a96e896..6aac4b9 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -34,6 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
+
import com.android.internal.util.XmlUtils;
import java.io.IOException;
@@ -532,6 +533,7 @@ import java.util.Set;
* <li> {@link #CATEGORY_TEST}
* <li> {@link #CATEGORY_CAR_DOCK}
* <li> {@link #CATEGORY_DESK_DOCK}
+ * <li> {@link #CATEGORY_CAR_MODE}
* </ul>
*
* <h3>Standard Extra Data</h3>
@@ -575,7 +577,7 @@ import java.util.Set;
* {@link #setFlags} and {@link #addFlags}. See {@link #setFlags} for a list
* of all possible flags.
*/
-public class Intent implements Parcelable {
+public class Intent implements Parcelable, Cloneable {
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent activity actions (see action variable).
@@ -1120,7 +1122,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
-
+
/**
* Activity Action: Setup wizard to launch after a platform update. This
* activity should have a string meta-data field associated with it,
@@ -1134,7 +1136,7 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
-
+
/**
* A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
* describing the last run version of the platform that was setup.
@@ -1148,7 +1150,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Sent after the screen turns off.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1156,7 +1158,7 @@ public class Intent implements Parcelable {
public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
/**
* Broadcast Action: Sent after the screen turns on.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1166,12 +1168,12 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Sent when the user is present after device wakes up (e.g when the
* keyguard is gone).
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USER_PRESENT= "android.intent.action.USER_PRESENT";
+ public static final String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
/**
* Broadcast Action: The current time has changed. Sent every
@@ -1179,7 +1181,7 @@ public class Intent implements Parcelable {
* in manifests, only by exlicitly registering for it with
* {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1200,7 +1202,7 @@ public class Intent implements Parcelable {
* <ul>
* <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time zone.</li>
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1228,7 +1230,7 @@ public class Intent implements Parcelable {
* such as installing alarms. You must hold the
* {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission
* in order to receive this broadcast.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1244,7 +1246,7 @@ public class Intent implements Parcelable {
* Broadcast Action: Trigger the download and eventual installation
* of a package.
* <p>Input: {@link #getData} is the URI of the package file to download.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1260,7 +1262,7 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_REPLACING} is set to true if this is following
* an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1274,7 +1276,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1292,7 +1294,7 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_REPLACING} is set to true if this will be followed
* by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1309,13 +1311,28 @@ public class Intent implements Parcelable {
* <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
* default action of restarting the application.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED";
/**
+ * @hide
+ * Broadcast Action: Ask system services if there is any reason to
+ * restart the given package. The data contains the name of the
+ * package.
+ * <ul>
+ * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_PACKAGES} String array of all packages to check.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
+ /**
* Broadcast Action: The user has restarted a package, and all of its
* processes have been killed. All runtime state
* associated with it (processes, alarms, notifications, etc) should
@@ -1325,7 +1342,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1340,7 +1357,7 @@ public class Intent implements Parcelable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1349,12 +1366,64 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: A user ID has been removed from the system. The user
* ID number is stored in the extra data under {@link #EXTRA_UID}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED";
+
+ /**
+ * Broadcast Action: Resources for a set of packages (which were
+ * previously unavailable) are currently
+ * available since the media on which they exist is available.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * Note that the
+ * packages in this list do <em>not</em> receive this broadcast.
+ * The specified set of packages are now available on the system.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources(were previously unavailable) are currently available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of uids of the
+ * packages whose resources(were previously unavailable)
+ * are currently available.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
+
+ /**
+ * Broadcast Action: Resources for a set of packages are currently
+ * unavailable since the media on which they exist is unavailable.
+ * The extra data {@link #EXTRA_CHANGED_PACKAGE_LIST} contains a
+ * list of packages whose availability changed.
+ * The extra data {@link #EXTRA_CHANGED_UID_LIST} contains a
+ * list of uids of packages whose availability changed.
+ * The specified set of packages can no longer be
+ * launched and are practically unavailable on the system.
+ * <p>Inclues the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages
+ * whose resources are no longer available.
+ * {@link #EXTRA_CHANGED_UID_LIST} is the set of packages
+ * whose resources are no longer available.
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE =
+ "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+
/**
* Broadcast Action: The current system wallpaper has changed. See
* {@link android.app.WallpaperManager} for retrieving the new wallpaper.
@@ -1370,13 +1439,13 @@ public class Intent implements Parcelable {
* application to make sure it sees the new changes. Some system code that
* can not be restarted will need to watch for this action and handle it
* appropriately.
- *
+ *
* <p class="note">
* You can <em>not</em> receive this through components declared
* in manifests, only by explicitly registering for it with
* {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
* Context.registerReceiver()}.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
@@ -1386,7 +1455,7 @@ public class Intent implements Parcelable {
public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
/**
* Broadcast Action: The current device's locale has changed.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1407,7 +1476,7 @@ public class Intent implements Parcelable {
* and {@link #ACTION_POWER_DISCONNECTED} for distinct battery-related
* broadcasts that are sent and can be received through manifest
* receivers.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1416,7 +1485,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Indicates low battery condition on the device.
* This broadcast corresponds to the "Low battery warning" system dialog.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1426,7 +1495,7 @@ public class Intent implements Parcelable {
* Broadcast Action: Indicates the battery is now okay after being low.
* This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
* gone back up to an okay state.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1438,7 +1507,7 @@ public class Intent implements Parcelable {
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
* that wait until power is available to trigger.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1450,7 +1519,7 @@ public class Intent implements Parcelable {
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
* that wait until power is available to trigger.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1463,7 +1532,7 @@ public class Intent implements Parcelable {
* off, not sleeping). Once the broadcast is complete, the final shutdown
* will proceed and all unsaved data lost. Apps will not normally need
* to handle this, since the foreground activity will be paused as well.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1483,7 +1552,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: A sticky broadcast that indicates low memory
* condition on the device
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1491,7 +1560,7 @@ public class Intent implements Parcelable {
public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW";
/**
* Broadcast Action: Indicates low memory condition on the device no longer exists
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1562,12 +1631,20 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: External media is unmounted because it is being shared via USB mass storage.
- * The path to the mount point for the removed media is contained in the Intent.mData field.
+ * The path to the mount point for the shared media is contained in the Intent.mData field.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_SHARED = "android.intent.action.MEDIA_SHARED";
/**
+ * Broadcast Action: External media is no longer being shared via USB mass storage.
+ * The path to the mount point for the previously shared media is contained in the Intent.mData field.
+ *
+ * @hide
+ */
+ public static final String ACTION_MEDIA_UNSHARED = "android.intent.action.MEDIA_UNSHARED";
+
+ /**
* Broadcast Action: External media was removed from SD card slot, but mount point was not unmounted.
* The path to the mount point for the removed media is contained in the Intent.mData field.
*/
@@ -1658,7 +1735,7 @@ public class Intent implements Parcelable {
* then cell radio and possibly other radios such as bluetooth or WiFi may have also been
* turned off</li>
* </ul>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1740,7 +1817,7 @@ public class Intent implements Parcelable {
* <p>You must hold the
* {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS}
* permission to receive this Intent.</p>
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1751,7 +1828,7 @@ public class Intent implements Parcelable {
/**
* Broadcast Action: Have the device reboot. This is only for use by
* system code.
- *
+ *
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
@@ -1760,12 +1837,17 @@ public class Intent implements Parcelable {
"android.intent.action.REBOOT";
/**
- * Broadcast Action: A sticky broadcast indicating the phone was docked
- * or undocked. Includes the extra
- * field {@link #EXTRA_DOCK_STATE}, containing the current dock state.
- * This is intended for monitoring the current dock state.
- * To launch an activity from a dock state change, use {@link #CATEGORY_CAR_DOCK}
- * or {@link #CATEGORY_DESK_DOCK} instead.
+ * Broadcast Action: A sticky broadcast for changes in the physical
+ * docking state of the device.
+ *
+ * <p>The intent will have the following extra values:
+ * <ul>
+ * <li><em>{@link #EXTRA_DOCK_STATE}</em> - the current dock
+ * state, indicating which dock the device is physically in.</li>
+ * </ul>
+ * <p>This is intended for monitoring the current physical dock state.
+ * See {@link android.app.UiModeManager} for the normal API dealing with
+ * dock mode changes.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DOCK_EVENT =
@@ -1784,7 +1866,7 @@ public class Intent implements Parcelable {
* @hide
*/
public static final String ACTION_REMOTE_INTENT =
- "android.intent.action.REMOTE_INTENT";
+ "com.google.android.datamessaging.intent.RECEIVE";
/**
* Broadcast Action: hook for permforming cleanup after a system update.
@@ -1927,19 +2009,25 @@ public class Intent implements Parcelable {
"android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST";
/**
* An activity to run when device is inserted into a car dock.
- * Used with {@link #ACTION_MAIN} to launch an activity.
- * To monitor dock state, use {@link #ACTION_DOCK_EVENT} instead.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK";
/**
* An activity to run when device is inserted into a car dock.
- * Used with {@link #ACTION_MAIN} to launch an activity.
- * To monitor dock state, use {@link #ACTION_DOCK_EVENT} instead.
+ * Used with {@link #ACTION_MAIN} to launch an activity. For more
+ * information, see {@link android.app.UiModeManager}.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK";
+ /**
+ * Used to indicate that the activity can be used in a car environment.
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard extra data keys.
@@ -2035,6 +2123,7 @@ public class Intent implements Parcelable {
* number to call in a {@link android.content.Intent#ACTION_CALL}.
*/
public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+
/**
* Used as an int extra field in {@link android.content.Intent#ACTION_UID_REMOVED}
* intents to supply the uid the package had been assigned. Also an optional
@@ -2045,6 +2134,11 @@ public class Intent implements Parcelable {
public static final String EXTRA_UID = "android.intent.extra.UID";
/**
+ * @hide String array of package names.
+ */
+ public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
+
+ /**
* Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
* intents to indicate whether this represents a full uninstall (removing
* both the code and its data) or a partial uninstall (leaving its data,
@@ -2102,7 +2196,7 @@ public class Intent implements Parcelable {
* indicate that the dock should take over the home key when it is active.
*/
public static final String METADATA_DOCK_HOME = "android.dock_home";
-
+
/**
* Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing
* the bug report.
@@ -2136,13 +2230,32 @@ public class Intent implements Parcelable {
"android.intent.extra.changed_component_name";
/**
- * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED}
+ * This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED},
* and contains a string array of all of the components that have changed.
*/
public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
"android.intent.extra.changed_component_name_list";
/**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
+ * and contains a string array of all of the components that have changed.
+ */
+ public static final String EXTRA_CHANGED_PACKAGE_LIST =
+ "android.intent.extra.changed_package_list";
+
+ /**
+ * This field is part of
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE},
+ * {@link android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE}
+ * and contains an integer array of uids of all of the components
+ * that have changed.
+ */
+ public static final String EXTRA_CHANGED_UID_LIST =
+ "android.intent.extra.changed_uid_list";
+
+ /**
* @hide
* Magic extra system code can use when binding, to give a label for
* who it is that has bound to a service. This is an integer giving
@@ -2389,6 +2502,20 @@ public class Intent implements Parcelable {
*/
public static final int FLAG_RECEIVER_REGISTERED_ONLY = 0x40000000;
/**
+ * If set, when sending a broadcast the new broadcast will replace
+ * any existing pending broadcast that matches it. Matching is defined
+ * by {@link Intent#filterEquals(Intent) Intent.filterEquals} returning
+ * true for the intents of the two broadcasts. When a match is found,
+ * the new broadcast (and receivers associated with it) will replace the
+ * existing one in the pending broadcast list, remaining at the same
+ * position in the list.
+ *
+ * <p>This flag is most typically used with sticky broadcasts, which
+ * only care about delivering the most recent values of the broadcast
+ * to their receivers.
+ */
+ public static final int FLAG_RECEIVER_REPLACE_PENDING = 0x20000000;
+ /**
* If set, when sending a broadcast <i>before boot has completed</i> only
* registered receivers will be called -- no BroadcastReceiver components
* will be launched. Sticky intent state will be recorded properly even
@@ -2401,14 +2528,14 @@ public class Intent implements Parcelable {
*
* @hide
*/
- public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
+ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x10000000;
/**
* Set when this broadcast is for a boot upgrade, a special mode that
* allows the broadcast to be sent before the system is ready and launches
* the app process with no providers running in it.
* @hide
*/
- public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x10000000;
+ public static final int FLAG_RECEIVER_BOOT_UPGRADE = 0x08000000;
/**
* @hide Flags that can't be changed with PendingIntent.
@@ -2416,7 +2543,7 @@ public class Intent implements Parcelable {
public static final int IMMUTABLE_FLAGS =
FLAG_GRANT_READ_URI_PERMISSION
| FLAG_GRANT_WRITE_URI_PERMISSION;
-
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -2430,7 +2557,7 @@ public class Intent implements Parcelable {
* VIEW action for that raw URI.
*/
public static final int URI_INTENT_SCHEME = 1<<0;
-
+
// ---------------------------------------------------------------------
private String mAction;
@@ -2589,7 +2716,7 @@ public class Intent implements Parcelable {
public static Intent getIntent(String uri) throws URISyntaxException {
return parseUri(uri, 0);
}
-
+
/**
* Create an intent from a URI. This URI may encode the action,
* category, and other intent fields, if it was returned by
@@ -2608,7 +2735,7 @@ public class Intent implements Parcelable {
* @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
* it bad (as parsed by the Uri class) or the Intent data within the
* URI is invalid.
- *
+ *
* @see #toUri
*/
public static Intent parseUri(String uri, int flags) throws URISyntaxException {
@@ -2626,7 +2753,7 @@ public class Intent implements Parcelable {
return intent;
}
}
-
+
// simple case
i = uri.lastIndexOf("#");
if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
@@ -2718,7 +2845,7 @@ public class Intent implements Parcelable {
data = scheme + ':' + data;
}
}
-
+
if (data.length() > 0) {
try {
intent.mData = Uri.parse(data);
@@ -2727,7 +2854,7 @@ public class Intent implements Parcelable {
}
}
}
-
+
return intent;
} catch (IndexOutOfBoundsException e) {
@@ -2878,7 +3005,7 @@ public class Intent implements Parcelable {
} else {
intent.mData = Uri.parse(uri);
}
-
+
if (intent.mAction == null) {
// By default, if no action is specified, then use VIEW.
intent.mAction = ACTION_VIEW;
@@ -3345,6 +3472,20 @@ public class Intent implements Parcelable {
* @param name The name of the desired item.
*
* @return the value of an item that previously added with putExtra()
+ * or null if no ArrayList<CharSequence> value was found.
+ *
+ * @see #putCharSequenceArrayListExtra(String, ArrayList)
+ */
+ public ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharSequenceArrayList(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item that previously added with putExtra()
* or null if no boolean array value was found.
*
* @see #putExtra(String, boolean[])
@@ -3471,6 +3612,20 @@ public class Intent implements Parcelable {
* @param name The name of the desired item.
*
* @return the value of an item that previously added with putExtra()
+ * or null if no CharSequence array value was found.
+ *
+ * @see #putExtra(String, CharSequence[])
+ */
+ public CharSequence[] getCharSequenceArrayExtra(String name) {
+ return mExtras == null ? null : mExtras.getCharSequenceArray(name);
+ }
+
+ /**
+ * Retrieve extended data from the intent.
+ *
+ * @param name The name of the desired item.
+ *
+ * @return the value of an item that previously added with putExtra()
* or null if no Bundle value was found.
*
* @see #putExtra(String, Bundle)
@@ -4173,6 +4328,29 @@ public class Intent implements Parcelable {
* like "com.android.contacts.ShowAll".
*
* @param name The name of the extra data, with package prefix.
+ * @param value The ArrayList<CharSequence> data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharSequenceArrayListExtra(String)
+ */
+ public Intent putCharSequenceArrayListExtra(String name, ArrayList<CharSequence> value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharSequenceArrayList(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
* @param value The Serializable data value.
*
* @return Returns the same Intent object, for chaining multiple calls
@@ -4403,6 +4581,29 @@ public class Intent implements Parcelable {
* like "com.android.contacts.ShowAll".
*
* @param name The name of the extra data, with package prefix.
+ * @param value The CharSequence array data value.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #putExtras
+ * @see #removeExtra
+ * @see #getCharSequenceArrayExtra(String)
+ */
+ public Intent putExtra(String name, CharSequence[] value) {
+ if (mExtras == null) {
+ mExtras = new Bundle();
+ }
+ mExtras.putCharSequenceArray(name, value);
+ return this;
+ }
+
+ /**
+ * Add extended data to the intent. The name must include a package
+ * prefix, for example the app com.android.contacts would use names
+ * like "com.android.contacts.ShowAll".
+ *
+ * @param name The name of the extra data, with package prefix.
* @param value The Bundle data value.
*
* @return Returns the same Intent object, for chaining multiple calls
@@ -5103,13 +5304,13 @@ public class Intent implements Parcelable {
* used with {@link Uri#parse Uri.parse(String)}. The URI contains the
* Intent's data as the base URI, with an additional fragment describing
* the action, categories, type, flags, package, component, and extras.
- *
+ *
* <p>You can convert the returned string back to an Intent with
* {@link #getIntent}.
- *
+ *
* @param flags Additional operating flags. Either 0 or
* {@link #URI_INTENT_SCHEME}.
- *
+ *
* @return Returns a URI encoding URI string describing the entire contents
* of the Intent.
*/
@@ -5133,13 +5334,13 @@ public class Intent implements Parcelable {
data = data.substring(i+1);
break;
}
-
+
// No scheme.
break;
}
}
uri.append(data);
-
+
} else if ((flags&URI_INTENT_SCHEME) != 0) {
uri.append("intent:");
}
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 365f269..452fd8a 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -34,6 +34,7 @@ import android.util.AndroidException;
import android.util.Config;
import android.util.Log;
import android.util.Printer;
+
import com.android.internal.util.XmlUtils;
/**
diff --git a/core/java/android/content/PeriodicSync.aidl b/core/java/android/content/PeriodicSync.aidl
new file mode 100644
index 0000000..4530591
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+parcelable PeriodicSync;
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
new file mode 100644
index 0000000..17813ec
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcelable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.accounts.Account;
+
+/**
+ * Value type that contains information about a periodic sync. Is parcelable, making it suitable
+ * for passing in an IPC.
+ */
+public class PeriodicSync implements Parcelable {
+ /** The account to be synced */
+ public final Account account;
+ /** The authority of the sync */
+ public final String authority;
+ /** Any extras that parameters that are to be passed to the sync adapter. */
+ public final Bundle extras;
+ /** How frequently the sync should be scheduled, in seconds. */
+ public final long period;
+
+ /** Creates a new PeriodicSync, copying the Bundle */
+ public PeriodicSync(Account account, String authority, Bundle extras, long period) {
+ this.account = account;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ this.period = period;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ account.writeToParcel(dest, flags);
+ dest.writeString(authority);
+ dest.writeBundle(extras);
+ dest.writeLong(period);
+ }
+
+ public static final Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() {
+ public PeriodicSync createFromParcel(Parcel source) {
+ return new PeriodicSync(Account.CREATOR.createFromParcel(source),
+ source.readString(), source.readBundle(), source.readLong());
+ }
+
+ public PeriodicSync[] newArray(int size) {
+ return new PeriodicSync[size];
+ }
+ };
+
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof PeriodicSync)) {
+ return false;
+ }
+
+ final PeriodicSync other = (PeriodicSync) o;
+
+ return account.equals(other.account)
+ && authority.equals(other.authority)
+ && period == other.period
+ && SyncStorageEngine.equals(extras, other.extras);
+ }
+}
diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java
deleted file mode 100644
index 88dc332..0000000
--- a/core/java/android/content/SyncAdapter.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.accounts.Account;
-
-/**
- * @hide
- */
-public abstract class SyncAdapter {
- private static final String TAG = "SyncAdapter";
-
- /** Kernel event log tag. Also listed in data/etc/event-log-tags. */
- public static final int LOG_SYNC_DETAILS = 2743;
-
- class Transport extends ISyncAdapter.Stub {
- public void startSync(ISyncContext syncContext, String authority, Account account,
- Bundle extras) throws RemoteException {
- SyncAdapter.this.startSync(new SyncContext(syncContext), account, authority, extras);
- }
-
- public void cancelSync(ISyncContext syncContext) throws RemoteException {
- SyncAdapter.this.cancelSync();
- }
- }
-
- Transport mTransport = new Transport();
-
- /**
- * Get the Transport object.
- */
- public final ISyncAdapter getISyncAdapter()
- {
- return mTransport;
- }
-
- /**
- * Initiate a sync for this account. SyncAdapter-specific parameters may
- * be specified in extras, which is guaranteed to not be null. IPC invocations
- * of this method and cancelSync() are guaranteed to be serialized.
- *
- * @param syncContext the ISyncContext used to indicate the progress of the sync. When
- * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
- * @param account the account that should be synced
- * @param authority the authority if the sync request
- * @param extras SyncAdapter-specific parameters
- */
- public abstract void startSync(SyncContext syncContext, Account account, String authority,
- Bundle extras);
-
- /**
- * Cancel the most recently initiated sync. Due to race conditions, this may arrive
- * after the ISyncContext.onFinished() for that sync was called. IPC invocations
- * of this method and startSync() are guaranteed to be serialized.
- */
- public abstract void cancelSync();
-}
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 6ade837..98a2595 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -18,6 +18,7 @@ package android.content;
import android.content.pm.RegisteredServicesCache;
import android.content.pm.XmlSerializerAndParser;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import org.xmlpull.v1.XmlPullParser;
@@ -42,8 +43,9 @@ import java.io.IOException;
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
}
- public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) {
- TypedArray sa = mContext.getResources().obtainAttributes(attrs,
+ public SyncAdapterType parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs) {
+ TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.SyncAdapter);
try {
final String authority =
diff --git a/core/java/android/content/SyncContext.java b/core/java/android/content/SyncContext.java
index 587586d..cc914c0 100644
--- a/core/java/android/content/SyncContext.java
+++ b/core/java/android/content/SyncContext.java
@@ -56,7 +56,9 @@ public class SyncContext {
if (now < mLastHeartbeatSendTime + HEARTBEAT_SEND_INTERVAL_IN_MS) return;
try {
mLastHeartbeatSendTime = now;
- mSyncContext.sendHeartbeat();
+ if (mSyncContext != null) {
+ mSyncContext.sendHeartbeat();
+ }
} catch (RemoteException e) {
// this should never happen
}
@@ -64,13 +66,15 @@ public class SyncContext {
public void onFinished(SyncResult result) {
try {
- mSyncContext.onFinished(result);
+ if (mSyncContext != null) {
+ mSyncContext.onFinished(result);
+ }
} catch (RemoteException e) {
// this should never happen
}
}
public IBinder getSyncContextBinder() {
- return mSyncContext.asBinder();
+ return (mSyncContext == null) ? null : mSyncContext.asBinder();
}
}
diff --git a/core/java/android/content/ActiveSyncInfo.aidl b/core/java/android/content/SyncInfo.aidl
index 1142206..0737429 100644
--- a/core/java/android/content/ActiveSyncInfo.aidl
+++ b/core/java/android/content/SyncInfo.aidl
@@ -16,4 +16,4 @@
package android.content;
-parcelable ActiveSyncInfo;
+parcelable SyncInfo;
diff --git a/core/java/android/content/ActiveSyncInfo.java b/core/java/android/content/SyncInfo.java
index 209dffa..616b05f 100644
--- a/core/java/android/content/ActiveSyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -20,25 +20,45 @@ import android.accounts.Account;
import android.os.Parcel;
import android.os.Parcelable.Creator;
-/** @hide */
-public class ActiveSyncInfo {
+/**
+ * Information about the sync operation that is currently underway.
+ */
+public class SyncInfo {
+ /** @hide */
public final int authorityId;
+
+ /**
+ * The {@link Account} that is currently being synced.
+ */
public final Account account;
+
+ /**
+ * The authority of the provider that is currently being synced.
+ */
public final String authority;
+
+ /**
+ * The start time of the current sync operation in milliseconds since boot.
+ * This is represented in elapsed real time.
+ * See {@link android.os.SystemClock#elapsedRealtime()}.
+ */
public final long startTime;
-
- ActiveSyncInfo(int authorityId, Account account, String authority,
+
+ /** @hide */
+ SyncInfo(int authorityId, Account account, String authority,
long startTime) {
this.authorityId = authorityId;
this.account = account;
this.authority = authority;
this.startTime = startTime;
}
-
+
+ /** @hide */
public int describeContents() {
return 0;
}
+ /** @hide */
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(authorityId);
account.writeToParcel(parcel, 0);
@@ -46,20 +66,22 @@ public class ActiveSyncInfo {
parcel.writeLong(startTime);
}
- ActiveSyncInfo(Parcel parcel) {
+ /** @hide */
+ SyncInfo(Parcel parcel) {
authorityId = parcel.readInt();
account = new Account(parcel);
authority = parcel.readString();
startTime = parcel.readLong();
}
-
- public static final Creator<ActiveSyncInfo> CREATOR = new Creator<ActiveSyncInfo>() {
- public ActiveSyncInfo createFromParcel(Parcel in) {
- return new ActiveSyncInfo(in);
+
+ /** @hide */
+ public static final Creator<SyncInfo> CREATOR = new Creator<SyncInfo>() {
+ public SyncInfo createFromParcel(Parcel in) {
+ return new SyncInfo(in);
}
- public ActiveSyncInfo[] newArray(int size) {
- return new ActiveSyncInfo[size];
+ public SyncInfo[] newArray(int size) {
+ return new SyncInfo[size];
}
};
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 33d6159..d0b67cc 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,8 +16,6 @@
package android.content;
-import com.google.android.collect.Maps;
-
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -29,7 +27,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.RegisteredServicesCache;
@@ -46,32 +43,20 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
import java.util.Random;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
@@ -79,15 +64,9 @@ import java.util.concurrent.CountDownLatch;
/**
* @hide
*/
-class SyncManager implements OnAccountsUpdateListener {
+public class SyncManager implements OnAccountsUpdateListener {
private static final String TAG = "SyncManager";
- // used during dumping of the Sync history
- private static final long MILLIS_IN_HOUR = 1000 * 60 * 60;
- private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24;
- private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
- private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4;
-
/** Delay a sync due to local changes this long. In milliseconds */
private static final long LOCAL_SYNC_DELAY;
@@ -136,19 +115,23 @@ class SyncManager implements OnAccountsUpdateListener {
private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
/**
+ * How long to wait before retrying a sync that failed due to one already being in progress.
+ */
+ private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
+
+ /**
* An error notification is sent if sync of any of the providers has been failing for this long.
*/
private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
+ private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
+
private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
private Context mContext;
- private String mStatusText = "";
- private long mHeartbeatTime = 0;
-
- private volatile Account[] mAccounts = null;
+ private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
volatile private PowerManager.WakeLock mSyncWakeLock;
volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
@@ -157,12 +140,9 @@ class SyncManager implements OnAccountsUpdateListener {
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
- private HandlerThread mSyncThread;
-
- private volatile IPackageManager mPackageManager;
private final SyncStorageEngine mSyncStorageEngine;
- private final SyncQueue mSyncQueue;
+ public final SyncQueue mSyncQueue;
private ActiveSyncContext mActiveSyncContext = null;
@@ -171,9 +151,7 @@ class SyncManager implements OnAccountsUpdateListener {
// set if the sync active indicator should be reported
private boolean mNeedSyncActiveNotification = false;
- private volatile boolean mSyncPollInitialized;
private final PendingIntent mSyncAlarmIntent;
- private final PendingIntent mSyncPollAlarmIntent;
// Synchronized on "this". Instead of using this directly one should instead call
// its accessor, getConnManager().
private ConnectivityManager mConnManagerDoNotUseDirectly;
@@ -215,9 +193,11 @@ class SyncManager implements OnAccountsUpdateListener {
}
};
+ private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
+
public void onAccountsUpdated(Account[] accounts) {
// remember if this was the first time this was called after an update
- final boolean justBootedUp = mAccounts == null;
+ final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
mAccounts = accounts;
// if a sync is in progress yet it is no longer in the accounts list,
@@ -235,7 +215,9 @@ class SyncManager implements OnAccountsUpdateListener {
// the accounts are not set yet
sendCheckAlarmsMessage();
- mSyncStorageEngine.doDatabaseCleanup(accounts);
+ if (mBootCompleted) {
+ mSyncStorageEngine.doDatabaseCleanup(accounts);
+ }
if (accounts.length > 0) {
// If this is the first time this was called after a bootup then
@@ -290,7 +272,6 @@ class SyncManager implements OnAccountsUpdateListener {
// ignore the rest of the states -- leave our boolean alone.
}
if (mDataConnectionIsConnected) {
- initializeSyncPoll();
sendCheckAlarmsMessage();
}
}
@@ -305,15 +286,8 @@ class SyncManager implements OnAccountsUpdateListener {
};
private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
- private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM";
private final SyncHandler mSyncHandler;
-
- private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours
- private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours
-
- private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
-
- private final boolean mFactoryTest;
+ private final Handler mMainHandler;
private volatile boolean mBootCompleted = false;
@@ -328,8 +302,6 @@ class SyncManager implements OnAccountsUpdateListener {
}
public SyncManager(Context context, boolean factoryTest) {
- mFactoryTest = factoryTest;
-
// Initialize the SyncStorageEngine first, before registering observers
// and creating threads and so on; it may fail if the disk is full.
SyncStorageEngine.init(context);
@@ -338,11 +310,11 @@ class SyncManager implements OnAccountsUpdateListener {
mContext = context;
- mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
- mSyncThread.start();
- mSyncHandler = new SyncHandler(mSyncThread.getLooper());
-
- mPackageManager = null;
+ HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ syncThread.start();
+ mSyncHandler = new SyncHandler(syncThread.getLooper());
+ mMainHandler = new Handler(mContext.getMainLooper());
mSyncAdapters = new SyncAdaptersCache(mContext);
mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
@@ -357,9 +329,6 @@ class SyncManager implements OnAccountsUpdateListener {
mSyncAlarmIntent = PendingIntent.getBroadcast(
mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
- mSyncPollAlarmIntent = PendingIntent.getBroadcast(
- mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);
-
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
@@ -415,49 +384,6 @@ class SyncManager implements OnAccountsUpdateListener {
}
}
- private synchronized void initializeSyncPoll() {
- if (mSyncPollInitialized) return;
- mSyncPollInitialized = true;
-
- mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));
-
- // load the next poll time from shared preferences
- long absoluteAlarmTime = readSyncPollTime();
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
- }
-
- // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
- // schedule the poll immediately, if it is too far in the future then cap it at
- // MAX_SYNC_POLL_DELAY_SECONDS.
- long absoluteNow = System.currentTimeMillis();
- long relativeNow = SystemClock.elapsedRealtime();
- long relativeAlarmTime = relativeNow;
- if (absoluteAlarmTime > absoluteNow) {
- long delayInMs = absoluteAlarmTime - absoluteNow;
- final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
- if (delayInMs > maxDelayInMs) {
- delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
- }
- relativeAlarmTime += delayInMs;
- }
-
- // schedule an alarm for the next poll time
- scheduleSyncPollAlarm(relativeAlarmTime);
- }
-
- private void scheduleSyncPollAlarm(long relativeAlarmTime) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
- + ", now is " + SystemClock.elapsedRealtime()
- + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
- }
- ensureAlarmService();
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
- mSyncPollAlarmIntent);
- }
-
/**
* Return a random value v that satisfies minValue <= v < maxValue. The difference between
* maxValue and minValue must be less than Integer.MAX_VALUE.
@@ -472,96 +398,89 @@ class SyncManager implements OnAccountsUpdateListener {
return minValue + random.nextInt((int)spread);
}
- private void handleSyncPollAlarm() {
- // determine the next poll time
- long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
- long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);
-
- // write the absolute time to shared preferences
- writeSyncPollTime(System.currentTimeMillis() + delayMs);
-
- // schedule an alarm for the next poll time
- scheduleSyncPollAlarm(nextRelativePollTimeMs);
-
- // perform a poll
- scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
- new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
+ public SyncStorageEngine getSyncStorageEngine() {
+ return mSyncStorageEngine;
}
- private void writeSyncPollTime(long when) {
- File f = new File(SYNCMANAGER_PREFS_FILENAME);
- DataOutputStream str = null;
- try {
- str = new DataOutputStream(new FileOutputStream(f));
- str.writeLong(when);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "error writing to file " + f, e);
- } catch (IOException e) {
- Log.w(TAG, "error writing to file " + f, e);
- } finally {
- if (str != null) {
- try {
- str.close();
- } catch (IOException e) {
- Log.w(TAG, "error closing file " + f, e);
- }
- }
+ private void ensureAlarmService() {
+ if (mAlarmService == null) {
+ mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
}
}
- private long readSyncPollTime() {
- File f = new File(SYNCMANAGER_PREFS_FILENAME);
-
- DataInputStream str = null;
- try {
- str = new DataInputStream(new FileInputStream(f));
- return str.readLong();
- } catch (FileNotFoundException e) {
- writeSyncPollTime(0);
- } catch (IOException e) {
- Log.w(TAG, "error reading file " + f, e);
- } finally {
- if (str != null) {
- try {
- str.close();
- } catch (IOException e) {
- Log.w(TAG, "error closing file " + f, e);
- }
- }
+ private void initializeSyncAdapter(Account account, String authority) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority);
+ }
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
+ RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+ mSyncAdapters.getServiceInfo(syncAdapterType);
+ if (syncAdapterInfo == null) {
+ Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing");
+ mSyncStorageEngine.removeAuthority(account, authority);
+ return;
}
- return 0;
- }
- public ActiveSyncContext getActiveSyncContext() {
- return mActiveSyncContext;
+ Intent intent = new Intent();
+ intent.setAction("android.content.SyncAdapter");
+ intent.setComponent(syncAdapterInfo.componentName);
+ if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
+ mMainHandler),
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
+ Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
+ }
}
- public SyncStorageEngine getSyncStorageEngine() {
- return mSyncStorageEngine;
- }
+ private static class InitializerServiceConnection implements ServiceConnection {
+ private final Account mAccount;
+ private final String mAuthority;
+ private final Handler mHandler;
+ private volatile Context mContext;
+ private volatile boolean mInitialized;
- private void ensureAlarmService() {
- if (mAlarmService == null) {
- mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ public InitializerServiceConnection(Account account, String authority, Context context,
+ Handler handler) {
+ mAccount = account;
+ mAuthority = authority;
+ mContext = context;
+ mHandler = handler;
+ mInitialized = false;
}
- }
- public Account getSyncingAccount() {
- ActiveSyncContext activeSyncContext = mActiveSyncContext;
- return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
- }
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ if (!mInitialized) {
+ mInitialized = true;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "calling initialize: " + mAccount + ", authority " + mAuthority);
+ }
+ ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
+ }
+ } catch (RemoteException e) {
+ // doesn't matter, we will retry again later
+ Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority,
+ e);
+ } finally {
+ // give the sync adapter time to initialize before unbinding from it
+ // TODO: change this API to not rely on this timing, http://b/2500805
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ if (mContext != null) {
+ mContext.unbindService(InitializerServiceConnection.this);
+ mContext = null;
+ }
+ }
+ }, INITIALIZATION_UNBIND_DELAY_MS);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ if (mContext != null) {
+ mContext.unbindService(InitializerServiceConnection.this);
+ mContext = null;
+ }
+ }
- /**
- * Returns whether or not sync is enabled. Sync can be enabled by
- * setting the system property "ro.config.sync" to the value "yes".
- * This is normally done at boot time on builds that support sync.
- * @return true if sync is enabled
- */
- private boolean isSyncEnabled() {
- // Require the precise value "yes" to discourage accidental activation.
- return "yes".equals(SystemProperties.get("ro.config.sync"));
}
/**
@@ -595,25 +514,9 @@ class SyncManager implements OnAccountsUpdateListener {
Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (mAccounts == null) {
- Log.e(TAG, "scheduleSync: the accounts aren't known yet, this should never happen");
- return;
- }
-
- if (!isSyncEnabled()) {
- if (isLoggable) {
- Log.v(TAG, "not syncing because sync is disabled");
- }
- setStatusText("Sync is disabled.");
- return;
- }
-
final boolean backgroundDataUsageAllowed = !mBootCompleted ||
getConnectivityManager().getBackgroundDataSetting();
- if (!mDataConnectionIsConnected) setStatusText("No data connection");
- if (mStorageIsLow) setStatusText("Memory low");
-
if (extras == null) extras = new Bundle();
Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
@@ -628,24 +531,22 @@ class SyncManager implements OnAccountsUpdateListener {
// if the accounts aren't configured yet then we can't support an account-less
// sync request
accounts = mAccounts;
- if (accounts == null) {
- // not ready yet
- if (isLoggable) {
- Log.v(TAG, "scheduleSync: no accounts yet, dropping");
- }
- return;
- }
if (accounts.length == 0) {
if (isLoggable) {
Log.v(TAG, "scheduleSync: no accounts configured, dropping");
}
- setStatusText("No accounts are configured.");
return;
}
}
final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ final boolean ignoreSettings =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
int source;
if (uploadOnly) {
@@ -695,45 +596,35 @@ class SyncManager implements OnAccountsUpdateListener {
continue;
}
- // make this an initialization sync if the isSyncable state is unknown
- Bundle extrasCopy = extras;
- long delayCopy = delay;
- if (isSyncable < 0) {
- extrasCopy = new Bundle(extras);
- extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- delayCopy = -1; // expedite this
- } else {
- final boolean syncAutomatically = masterSyncAutomatically
- && mSyncStorageEngine.getSyncAutomatically(account, authority);
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
+ // always allow if the isSyncable state is unknown
+ boolean syncAllowed =
+ (isSyncable < 0)
+ || ignoreSettings
+ || (backgroundDataUsageAllowed && masterSyncAutomatically
+ && mSyncStorageEngine.getSyncAutomatically(account, authority));
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
}
+ continue;
}
+
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
- + " delay " + delayCopy
+ + " delay " + delay
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
- + ", extras " + extrasCopy);
+ + ", extras " + extras);
}
scheduleSyncOperation(
- new SyncOperation(account, source, authority, extrasCopy, delayCopy));
+ new SyncOperation(account, source, authority, extras, delay));
}
}
}
}
- private void setStatusText(String message) {
- mStatusText = message;
- }
-
public void scheduleLocalSync(Account account, String authority) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
@@ -741,17 +632,6 @@ class SyncManager implements OnAccountsUpdateListener {
false /* onlyThoseWithUnkownSyncableState */);
}
- private IPackageManager getPackageManager() {
- // Don't bother synchronizing on this. The worst that can happen is that two threads
- // can try to get the package manager at the same time but only one result gets
- // used. Since there is only one package manager in the system this doesn't matter.
- if (mPackageManager == null) {
- IBinder b = ServiceManager.getService("package");
- mPackageManager = IPackageManager.Stub.asInterface(b);
- }
- return mPackageManager;
- }
-
public SyncAdapterType[] getSyncAdapterTypes() {
final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
mSyncAdapters.getAllServices();
@@ -764,11 +644,6 @@ class SyncManager implements OnAccountsUpdateListener {
return types;
}
- public void updateHeartbeatTime() {
- mHeartbeatTime = SystemClock.elapsedRealtime();
- mSyncStorageEngine.reportActiveChange();
- }
-
private void sendSyncAlarmMessage() {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
@@ -805,42 +680,48 @@ class SyncManager implements OnAccountsUpdateListener {
}
}
- class SyncPollAlarmReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- handleSyncPollAlarm();
- }
+ private void clearBackoffSetting(SyncOperation op) {
+ mSyncStorageEngine.setBackoff(op.account, op.authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
- private void rescheduleImmediately(SyncOperation syncOperation) {
- SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
- rescheduledSyncOperation.setDelay(0);
- scheduleSyncOperation(rescheduledSyncOperation);
- }
+ private void increaseBackoffSetting(SyncOperation op) {
+ final long now = SystemClock.elapsedRealtime();
- private long rescheduleWithDelay(SyncOperation syncOperation) {
+ final Pair<Long, Long> previousSettings =
+ mSyncStorageEngine.getBackoff(op.account, op.authority);
long newDelayInMs;
-
- if (syncOperation.delay <= 0) {
+ if (previousSettings == null || previousSettings.second <= 0) {
// The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
(long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
} else {
// Subsequent delays are the double of the previous delay
- newDelayInMs = syncOperation.delay * 2;
+ newDelayInMs = previousSettings.second * 2;
}
// Cap the delay
- long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContext.getContentResolver(),
- Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(),
+ Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
}
- SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
- rescheduledSyncOperation.setDelay(newDelayInMs);
- scheduleSyncOperation(rescheduledSyncOperation);
- return newDelayInMs;
+ mSyncStorageEngine.setBackoff(op.account, op.authority,
+ now + newDelayInMs, newDelayInMs);
+ }
+
+ private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
+ final long delayUntil = delayUntilSeconds * 1000;
+ final long absoluteNow = System.currentTimeMillis();
+ long newDelayUntilTime;
+ if (delayUntil > absoluteNow) {
+ newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
+ } else {
+ newDelayUntilTime = 0;
+ }
+ mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
}
/**
@@ -876,28 +757,26 @@ class SyncManager implements OnAccountsUpdateListener {
public void scheduleSyncOperation(SyncOperation syncOperation) {
// If this operation is expedited and there is a sync in progress then
// reschedule the current operation and send a cancel for it.
- final boolean expedited = syncOperation.delay < 0;
final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (expedited && activeSyncContext != null) {
- final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
+ if (syncOperation.expedited && activeSyncContext != null) {
final boolean hasSameKey =
activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
// This request is expedited and there is a sync in progress.
// Interrupt the current sync only if it is not expedited and if it has a different
// key than the one we are scheduling.
- if (!activeIsExpedited && !hasSameKey) {
- rescheduleImmediately(activeSyncContext.mSyncOperation);
+ if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
+ scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
sendSyncFinishedOrCanceledMessage(activeSyncContext,
null /* no result since this is a cancel */);
}
}
- boolean operationEnqueued;
+ boolean queueChanged;
synchronized (mSyncQueue) {
- operationEnqueued = mSyncQueue.add(syncOperation);
+ queueChanged = mSyncQueue.add(syncOperation);
}
- if (operationEnqueued) {
+ if (queueChanged) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
}
@@ -916,16 +795,26 @@ class SyncManager implements OnAccountsUpdateListener {
* @param authority limit the removals to operations with this authority, if non-null
*/
public void clearScheduledSyncOperations(Account account, String authority) {
+ mSyncStorageEngine.setBackoff(account, authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
- mSyncQueue.clear(account, authority);
+ mSyncQueue.remove(account, authority);
}
}
- void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) {
+ void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
if (isLoggable) {
- Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
- + previousSyncOperation);
+ Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
+ }
+
+ operation = new SyncOperation(operation);
+
+ // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+ // request. Retries of the request will always honor the backoff, so clear the
+ // flag in case we retry this request.
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
}
// If this sync aborted because the internal sync loop retried too many times then
@@ -934,119 +823,40 @@ class SyncManager implements OnAccountsUpdateListener {
// If this was a two-way sync then retry soft errors with an exponential backoff.
// If this was an upward sync then schedule a two-way sync immediately.
// Otherwise do not reschedule.
- if (syncResult.tooManyRetries) {
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
+ Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+ + operation);
+ } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+ operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ + "encountered an error: " + operation);
+ scheduleSyncOperation(operation);
+ } else if (syncResult.tooManyRetries) {
Log.d(TAG, "not retrying sync operation because it retried too many times: "
- + previousSyncOperation);
+ + operation);
} else if (syncResult.madeSomeProgress()) {
if (isLoggable) {
- Log.d(TAG, "retrying sync operation immediately because "
- + "even though it had an error it achieved some success");
- }
- rescheduleImmediately(previousSyncOperation);
- } else if (previousSyncOperation.extras.getBoolean(
- ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
- final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
- newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
- newSyncOperation.setDelay(0);
- if (Config.LOGD) {
- Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
- + "encountered an error: " + previousSyncOperation);
- }
- scheduleSyncOperation(newSyncOperation);
- } else if (syncResult.hasSoftError()) {
- long delay = rescheduleWithDelay(previousSyncOperation);
- if (delay >= 0) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation in " + delay + " ms because "
- + "it encountered a soft error: " + previousSyncOperation);
- }
- }
- } else {
- if (Config.LOGD) {
- Log.d(TAG, "not retrying sync operation because the error is a hard error: "
- + previousSyncOperation);
+ Log.d(TAG, "retrying sync operation because even though it had an error "
+ + "it achieved some success");
}
- }
- }
-
- /**
- * Value type that represents a sync operation.
- */
- static class SyncOperation implements Comparable {
- final Account account;
- int syncSource;
- String authority;
- Bundle extras;
- final String key;
- long earliestRunTime;
- long delay;
- SyncStorageEngine.PendingOperation pendingOperation;
-
- SyncOperation(Account account, int source, String authority, Bundle extras, long delay) {
- this.account = account;
- this.syncSource = source;
- this.authority = authority;
- this.extras = new Bundle(extras);
- this.setDelay(delay);
- this.key = toKey();
- }
-
- SyncOperation(SyncOperation other) {
- this.account = other.account;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = new Bundle(other.extras);
- this.delay = other.delay;
- this.earliestRunTime = other.earliestRunTime;
- this.key = toKey();
- }
-
- public void setDelay(long delay) {
- this.delay = delay;
- if (delay >= 0) {
- this.earliestRunTime = SystemClock.elapsedRealtime() + delay;
- } else {
- this.earliestRunTime = 0;
- }
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- sb.append(" syncSource: ").append(syncSource);
- sb.append(" when: ").append(earliestRunTime);
- sb.append(" delay: ").append(delay);
- sb.append(" key: {").append(key).append("}");
- if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation);
- return sb.toString();
- }
-
- private String toKey() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- return sb.toString();
- }
-
- private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
- sb.append("[");
- for (String key : bundle.keySet()) {
- sb.append(key).append("=").append(bundle.get(key)).append(" ");
+ scheduleSyncOperation(operation);
+ } else if (syncResult.syncAlreadyInProgress) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation that failed because there was already a "
+ + "sync in progress: " + operation);
}
- sb.append("]");
- }
-
- public int compareTo(Object o) {
- SyncOperation other = (SyncOperation)o;
- if (earliestRunTime == other.earliestRunTime) {
- return 0;
+ scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
+ operation.authority, operation.extras,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
+ } else if (syncResult.hasSoftError()) {
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ + operation);
}
- return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+ scheduleSyncOperation(operation);
+ } else {
+ Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ + operation);
}
}
@@ -1059,6 +869,7 @@ class SyncManager implements OnAccountsUpdateListener {
ISyncAdapter mSyncAdapter;
final long mStartTime;
long mTimeoutStartTime;
+ boolean mBound;
public ActiveSyncContext(SyncOperation syncOperation,
long historyRowId) {
@@ -1071,10 +882,7 @@ class SyncManager implements OnAccountsUpdateListener {
}
public void sendHeartbeat() {
- // ignore this call if it corresponds to an old sync session
- if (mActiveSyncContext == this) {
- SyncManager.this.updateHeartbeatTime();
- }
+ // heartbeats are no longer used
}
public void onFinished(SyncResult result) {
@@ -1116,14 +924,23 @@ class SyncManager implements OnAccountsUpdateListener {
com.android.internal.R.string.sync_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
- return mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ mBound = true;
+ final boolean bindResult = mContext.bindService(intent, this,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+ if (!bindResult) {
+ mBound = false;
+ }
+ return bindResult;
}
- void unBindFromSyncAdapter() {
+ protected void close() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
}
- mContext.unbindService(this);
+ if (mBound) {
+ mBound = false;
+ mContext.unbindService(this);
+ }
}
@Override
@@ -1137,9 +954,7 @@ class SyncManager implements OnAccountsUpdateListener {
protected void dump(FileDescriptor fd, PrintWriter pw) {
StringBuilder sb = new StringBuilder();
dumpSyncState(pw, sb);
- if (isSyncEnabled()) {
- dumpSyncHistory(pw, sb);
- }
+ dumpSyncHistory(pw, sb);
pw.println();
pw.println("SyncAdapters:");
@@ -1155,19 +970,19 @@ class SyncManager implements OnAccountsUpdateListener {
}
protected void dumpSyncState(PrintWriter pw, StringBuilder sb) {
- pw.print("sync enabled: "); pw.println(isSyncEnabled());
pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
pw.print("memory low: "); pw.println(mStorageIsLow);
final Account[] accounts = mAccounts;
pw.print("accounts: ");
- if (accounts != null) {
+ if (accounts != INITIAL_ACCOUNTS_ARRAY) {
pw.println(accounts.length);
} else {
- pw.println("none");
+ pw.println("not known yet");
}
final long now = SystemClock.elapsedRealtime();
- pw.print("now: "); pw.println(now);
+ pw.print("now: "); pw.print(now);
+ pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
pw.println(" (HH:MM:SS)");
pw.print("time spent syncing: ");
@@ -1185,7 +1000,9 @@ class SyncManager implements OnAccountsUpdateListener {
pw.println("no alarm is scheduled (there had better not be any pending syncs)");
}
- pw.print("active sync: "); pw.println(mActiveSyncContext);
+ final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
+
+ pw.print("active sync: "); pw.println(activeSyncContext);
pw.print("notification info: ");
sb.setLength(0);
@@ -1199,7 +1016,7 @@ class SyncManager implements OnAccountsUpdateListener {
pw.println(sb.toString());
}
- ActiveSyncInfo active = mSyncStorageEngine.getActiveSync();
+ SyncInfo active = mSyncStorageEngine.getCurrentSync();
if (active != null) {
SyncStorageEngine.AuthorityInfo authority
= mSyncStorageEngine.getAuthority(active.authorityId);
@@ -1208,6 +1025,11 @@ class SyncManager implements OnAccountsUpdateListener {
pw.print(authority != null ? authority.account : "<no account>");
pw.print(" ");
pw.print(authority != null ? authority.authority : "<no account>");
+ if (activeSyncContext != null) {
+ pw.print(" ");
+ pw.print(SyncStorageEngine.SOURCES[
+ activeSyncContext.mSyncOperation.syncSource]);
+ }
pw.print(", duration is ");
pw.println(DateUtils.formatElapsedTime(durationInSeconds));
} else {
@@ -1225,71 +1047,86 @@ class SyncManager implements OnAccountsUpdateListener {
pw.print(" #"); pw.print(i); pw.print(": account=");
pw.print(op.account.name); pw.print(":");
pw.print(op.account.type); pw.print(" authority=");
- pw.println(op.authority);
+ pw.print(op.authority); pw.print(" expedited=");
+ pw.println(op.expedited);
if (op.extras != null && op.extras.size() > 0) {
sb.setLength(0);
- SyncOperation.extrasToStringBuilder(op.extras, sb);
+ SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
pw.print(" extras: "); pw.println(sb.toString());
}
}
}
- HashSet<Account> processedAccounts = new HashSet<Account>();
- ArrayList<SyncStatusInfo> statuses
- = mSyncStorageEngine.getSyncStatus();
- if (statuses != null && statuses.size() > 0) {
- pw.println();
- pw.println("Sync Status");
- final int N = statuses.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo status = statuses.get(i);
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(status.authorityId);
- if (authority != null) {
- Account curAccount = authority.account;
-
- if (processedAccounts.contains(curAccount)) {
- continue;
- }
-
- processedAccounts.add(curAccount);
+ // join the installed sync adapter with the accounts list and emit for everything
+ pw.println();
+ pw.println("Sync Status");
+ for (Account account : accounts) {
+ pw.print(" Account "); pw.print(account.name);
+ pw.print(" "); pw.print(account.type);
+ pw.println(":");
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
+ mSyncAdapters.getAllServices()) {
+ if (!syncAdapterType.type.accountType.equals(account.type)) {
+ continue;
+ }
- pw.print(" Account "); pw.print(authority.account.name);
- pw.print(" "); pw.print(authority.account.type);
- pw.println(":");
- for (int j=i; j<N; j++) {
- status = statuses.get(j);
- authority = mSyncStorageEngine.getAuthority(status.authorityId);
- if (!curAccount.equals(authority.account)) {
- continue;
- }
- pw.print(" "); pw.print(authority.authority);
- pw.println(":");
- pw.print(" count: local="); pw.print(status.numSourceLocal);
- pw.print(" poll="); pw.print(status.numSourcePoll);
- pw.print(" server="); pw.print(status.numSourceServer);
- pw.print(" user="); pw.print(status.numSourceUser);
- pw.print(" total="); pw.println(status.numSyncs);
- pw.print(" total duration: ");
- pw.println(DateUtils.formatElapsedTime(
- status.totalElapsedTime/1000));
- if (status.lastSuccessTime != 0) {
- pw.print(" SUCCESS: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastSuccessSource]);
- pw.print(" time=");
- pw.println(formatTime(status.lastSuccessTime));
- } else {
- pw.print(" FAILURE: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastFailureSource]);
- pw.print(" initialTime=");
- pw.print(formatTime(status.initialFailureTime));
- pw.print(" lastTime=");
- pw.println(formatTime(status.lastFailureTime));
- pw.print(" message: "); pw.println(status.lastFailureMesg);
- }
- }
+ SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getOrCreateAuthority(
+ account, syncAdapterType.type.authority);
+ SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
+ pw.print(" "); pw.print(settings.authority);
+ pw.println(":");
+ pw.print(" settings:");
+ pw.print(" " + (settings.syncable > 0
+ ? "syncable"
+ : (settings.syncable == 0 ? "not syncable" : "not initialized")));
+ pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
+ if (settings.delayUntil > now) {
+ pw.print(", delay for "
+ + ((settings.delayUntil - now) / 1000) + " sec");
+ }
+ if (settings.backoffTime > now) {
+ pw.print(", backoff for "
+ + ((settings.backoffTime - now) / 1000) + " sec");
+ }
+ if (settings.backoffDelay > 0) {
+ pw.print(", the backoff increment is " + settings.backoffDelay / 1000
+ + " sec");
+ }
+ pw.println();
+ for (int periodicIndex = 0;
+ periodicIndex < settings.periodicSyncs.size();
+ periodicIndex++) {
+ Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
+ long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
+ long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
+ pw.println(" periodic period=" + info.second
+ + ", extras=" + info.first
+ + ", next=" + formatTime(nextPeriodicTime));
+ }
+ pw.print(" count: local="); pw.print(status.numSourceLocal);
+ pw.print(" poll="); pw.print(status.numSourcePoll);
+ pw.print(" periodic="); pw.print(status.numSourcePeriodic);
+ pw.print(" server="); pw.print(status.numSourceServer);
+ pw.print(" user="); pw.print(status.numSourceUser);
+ pw.print(" total="); pw.print(status.numSyncs);
+ pw.println();
+ pw.print(" total duration: ");
+ pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000));
+ if (status.lastSuccessTime != 0) {
+ pw.print(" SUCCESS: source=");
+ pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]);
+ pw.print(" time=");
+ pw.println(formatTime(status.lastSuccessTime));
+ }
+ if (status.lastFailureTime != 0) {
+ pw.print(" FAILURE: source=");
+ pw.print(SyncStorageEngine.SOURCES[
+ status.lastFailureSource]);
+ pw.print(" initialTime=");
+ pw.print(formatTime(status.initialFailureTime));
+ pw.print(" lastTime=");
+ pw.println(formatTime(status.lastFailureTime));
+ pw.print(" message: "); pw.println(status.lastFailureMesg);
}
}
}
@@ -1470,9 +1307,9 @@ class SyncManager implements OnAccountsUpdateListener {
// it if sync is still failing
private boolean mErrorNotificationInstalled = false;
private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
-
public void onBootCompleted() {
mBootCompleted = true;
+ mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts());
if (mReadyToRunLatch != null) {
mReadyToRunLatch.countDown();
}
@@ -1529,8 +1366,14 @@ class SyncManager implements OnAccountsUpdateListener {
}
public void handleMessage(Message msg) {
+ Long earliestFuturePollTime = null;
try {
waitUntilReadyToRun();
+ // Always do this first so that we be sure that any periodic syncs that
+ // are ready to run have been converted into pending syncs. This allows the
+ // logic that considers the next steps to take based on the set of pending syncs
+ // to also take into account the periodic syncs.
+ earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
case SyncHandler.MESSAGE_SYNC_FINISHED:
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1538,11 +1381,9 @@ class SyncManager implements OnAccountsUpdateListener {
}
SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
if (mActiveSyncContext != payload.activeSyncContext) {
- if (Config.LOGD) {
- Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
- + "dropping: mActiveSyncContext " + mActiveSyncContext
- + " != " + payload.activeSyncContext);
- }
+ Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
+ + "dropping: mActiveSyncContext " + mActiveSyncContext
+ + " != " + payload.activeSyncContext);
return;
}
runSyncFinishedOrCanceled(payload.syncResult);
@@ -1641,28 +1482,89 @@ class SyncManager implements OnAccountsUpdateListener {
}
manageSyncNotification();
manageErrorNotification();
- manageSyncAlarm();
+ manageSyncAlarm(earliestFuturePollTime);
mSyncTimeTracker.update();
}
}
+ /**
+ * Turn any periodic sync operations that are ready to run into pending sync operations.
+ * @return the desired start time of the earliest future periodic sync operation,
+ * in milliseconds since boot
+ */
+ private Long scheduleReadyPeriodicSyncs() {
+ final boolean backgroundDataUsageAllowed =
+ getConnectivityManager().getBackgroundDataSetting();
+ Long earliestFuturePollTime = null;
+ if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
+ return earliestFuturePollTime;
+ }
+ final long nowAbsolute = System.currentTimeMillis();
+ ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
+ for (SyncStorageEngine.AuthorityInfo info : infos) {
+ // skip the sync if the account of this operation no longer exists
+ if (!ArrayUtils.contains(mAccounts, info.account)) {
+ continue;
+ }
+
+ if (!mSyncStorageEngine.getSyncAutomatically(info.account, info.authority)) {
+ continue;
+ }
+
+ if (mSyncStorageEngine.getIsSyncable(info.account, info.authority) == 0) {
+ continue;
+ }
+
+ SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
+ for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
+ final Bundle extras = info.periodicSyncs.get(i).first;
+ final Long periodInSeconds = info.periodicSyncs.get(i).second;
+ // find when this periodic sync was last scheduled to run
+ final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
+ // compute when this periodic sync should next run
+ long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
+ // if it is ready to run then schedule it and mark it as having been scheduled
+ if (nextPollTimeAbsolute <= nowAbsolute) {
+ scheduleSyncOperation(
+ new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
+ info.authority, extras, 0 /* delay */));
+ status.setPeriodicSyncTime(i, nowAbsolute);
+ } else {
+ // it isn't ready to run, remember this time if it is earlier than
+ // earliestFuturePollTime
+ if (earliestFuturePollTime == null
+ || nextPollTimeAbsolute < earliestFuturePollTime) {
+ earliestFuturePollTime = nextPollTimeAbsolute;
+ }
+ }
+ }
+ }
+
+ if (earliestFuturePollTime == null) {
+ return null;
+ }
+
+ // convert absolute time to elapsed time
+ return SystemClock.elapsedRealtime()
+ + ((earliestFuturePollTime < nowAbsolute)
+ ? 0
+ : (earliestFuturePollTime - nowAbsolute));
+ }
+
private void runStateSyncing() {
// if the sync timeout has been reached then cancel it
-
ActiveSyncContext activeSyncContext = mActiveSyncContext;
final long now = SystemClock.elapsedRealtime();
if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
- SyncOperation nextSyncOperation;
+ Pair<SyncOperation, Long> nextOpAndRunTime;
synchronized (mSyncQueue) {
- nextSyncOperation = mSyncQueue.head();
+ nextOpAndRunTime = mSyncQueue.nextOperation();
}
- if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) {
- if (Config.LOGD) {
- Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
- + activeSyncContext.mSyncOperation);
- }
- rescheduleImmediately(activeSyncContext.mSyncOperation);
+ if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
+ Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
+ + activeSyncContext.mSyncOperation);
+ scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
sendSyncFinishedOrCanceledMessage(activeSyncContext,
null /* no result since this is a cancel */);
} else {
@@ -1682,7 +1584,6 @@ class SyncManager implements OnAccountsUpdateListener {
if (isLoggable) {
Log.v(TAG, "runStateIdle: no data connection, skipping");
}
- setStatusText("No data connection");
return;
}
@@ -1690,18 +1591,16 @@ class SyncManager implements OnAccountsUpdateListener {
if (isLoggable) {
Log.v(TAG, "runStateIdle: memory low, skipping");
}
- setStatusText("Memory low");
return;
}
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
Account[] accounts = mAccounts;
- if (accounts == null) {
+ if (accounts == INITIAL_ACCOUNTS_ARRAY) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: accounts not known, skipping");
}
- setStatusText("Accounts not known yet");
return;
}
@@ -1709,85 +1608,72 @@ class SyncManager implements OnAccountsUpdateListener {
// found that is runnable (not disabled, etc). If that one is ready to run then
// start it, otherwise just get out.
SyncOperation op;
+ int syncableState;
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
+ final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
+
synchronized (mSyncQueue) {
+ final long now = SystemClock.elapsedRealtime();
while (true) {
- op = mSyncQueue.head();
- if (op == null) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
+ if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
if (isLoggable) {
- Log.v(TAG, "runStateIdle: no more sync operations, returning");
+ Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
}
return;
}
+ op = nextOpAndRunTime.first;
- // Sync is disabled, drop this operation.
- if (!isSyncEnabled()) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
- }
- mSyncQueue.popHead();
+ // we are either going to run this sync or drop it so go ahead and
+ // remove it from the queue now
+ mSyncQueue.remove(op);
+
+ // drop the sync if the account of this operation no longer exists
+ if (!ArrayUtils.contains(mAccounts, op.account)) {
continue;
}
- // skip the sync if it isn't manual and auto sync is disabled
- final boolean manualSync = op.extras.getBoolean(
- ContentResolver.SYNC_EXTRAS_MANUAL, false);
- final boolean syncAutomatically =
- mSyncStorageEngine.getSyncAutomatically(op.account, op.authority)
- && mSyncStorageEngine.getMasterSyncAutomatically();
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- int isSyncable = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
- if (isSyncable == 0) {
- // if not syncable, don't allow
- syncAllowed = false;
- } else if (isSyncable < 0) {
- // if the syncable state is unknown, only allow initialization syncs
- syncAllowed =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- }
- if (!syncAllowed) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync off, dropping " + op);
- }
- mSyncQueue.popHead();
+
+ // drop this sync request if it isn't syncable, intializing the sync adapter
+ // if the syncable state is set to "unknown"
+ syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+ if (syncableState == 0) {
continue;
}
- // skip the sync if the account of this operation no longer exists
- if (!ArrayUtils.contains(accounts, op.account)) {
- mSyncQueue.popHead();
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: account not present, dropping " + op);
- }
+ // skip the sync if it isn't manual and auto sync or
+ // background data usage is disabled
+ if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ && (syncableState > 0)
+ && (!masterSyncAutomatically
+ || !backgroundDataUsageAllowed
+ || !mSyncStorageEngine.getSyncAutomatically(
+ op.account, op.authority))) {
continue;
}
// go ahead and try to sync this syncOperation
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: found sync candidate: " + op);
- }
break;
}
- // If the first SyncOperation isn't ready to run schedule a wakeup and
- // get out.
- final long now = SystemClock.elapsedRealtime();
- if (op.earliestRunTime > now) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
- + "sync operation is for " + op.earliestRunTime + ": " + op);
- }
- return;
- }
-
- // We will do this sync. Remove it from the queue and run it outside of the
- // synchronized block.
+ // We will do this sync. Run it outside of the synchronized block.
if (isLoggable) {
Log.v(TAG, "runStateIdle: we are going to sync " + op);
}
- mSyncQueue.popHead();
+ }
+
+ // convert the op into an initialization sync if the syncable state is "unknown" and
+ // op isn't already an initialization sync. If it is marked syncable then convert
+ // this into a regular sync
+ final boolean initializeIsSet =
+ op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ if (syncableState < 0 && !initializeIsSet) {
+ op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ op = new SyncOperation(op);
+ } else if (syncableState > 0 && initializeIsSet) {
+ op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ op = new SyncOperation(op);
}
// connect to the sync adapter
@@ -1795,9 +1681,9 @@ class SyncManager implements OnAccountsUpdateListener {
RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
mSyncAdapters.getServiceInfo(syncAdapterType);
if (syncAdapterInfo == null) {
- if (Config.LOGD) {
- Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
- }
+ Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+ + ", removing settings for it");
+ mSyncStorageEngine.removeAuthority(op.account, op.authority);
runStateIdle();
return;
}
@@ -1811,6 +1697,7 @@ class SyncManager implements OnAccountsUpdateListener {
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+ mActiveSyncContext.close();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
runStateIdle();
@@ -1831,25 +1718,22 @@ class SyncManager implements OnAccountsUpdateListener {
syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
syncOperation.account, syncOperation.extras);
} catch (RemoteException remoteExc) {
- if (Config.LOGD) {
- Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
- }
- mActiveSyncContext.unBindFromSyncAdapter();
+ Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
+ mActiveSyncContext.close();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- rescheduleWithDelay(syncOperation);
+ increaseBackoffSetting(syncOperation);
+ scheduleSyncOperation(new SyncOperation(syncOperation));
} catch (RuntimeException exc) {
- mActiveSyncContext.unBindFromSyncAdapter();
+ mActiveSyncContext.close();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
- exc);
+ Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
}
}
private void runSyncFinishedOrCanceled(SyncResult syncResult) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
final ActiveSyncContext activeSyncContext = mActiveSyncContext;
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
@@ -1863,32 +1747,43 @@ class SyncManager implements OnAccountsUpdateListener {
int upstreamActivity;
if (syncResult != null) {
if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation "
+ Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
+ syncOperation + ", result " + syncResult);
}
if (!syncResult.hasError()) {
- if (isLoggable) {
- Log.v(TAG, "finished sync operation " + syncOperation);
- }
historyMessage = SyncStorageEngine.MESG_SUCCESS;
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
+ clearBackoffSetting(syncOperation);
+ // if this was an initialization sync and the sync adapter is now
+ // marked syncable then reschedule the sync. The next time it runs it
+ // will be made into a regular sync.
+ if (syncOperation.extras.getBoolean(
+ ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+ && mSyncStorageEngine.getIsSyncable(
+ syncOperation.account, syncOperation.authority) > 0) {
+ scheduleSyncOperation(new SyncOperation(syncOperation));
+ }
} else {
- maybeRescheduleSync(syncResult, syncOperation);
- if (Config.LOGD) {
- Log.d(TAG, "failed sync operation " + syncOperation);
+ Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+ // the operation failed so increase the backoff time
+ if (!syncResult.syncAlreadyInProgress) {
+ increaseBackoffSetting(syncOperation);
}
+ // reschedule the sync if so indicated by the syncResult
+ maybeRescheduleSync(syncResult, syncOperation);
historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
}
+
+ setDelayUntilTime(syncOperation, syncResult.delayUntil);
} else {
if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
- + syncOperation);
+ Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
}
if (activeSyncContext.mSyncAdapter != null) {
try {
@@ -1905,7 +1800,7 @@ class SyncManager implements OnAccountsUpdateListener {
stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
upstreamActivity, downstreamActivity, elapsedTime);
- activeSyncContext.unBindFromSyncAdapter();
+ activeSyncContext.close();
if (syncResult != null && syncResult.tooManyDeletions) {
installHandleTooManyDeletesNotification(syncOperation.account,
@@ -2033,26 +1928,32 @@ class SyncManager implements OnAccountsUpdateListener {
}
}
- private void manageSyncAlarm() {
+ private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
// in each of these cases the sync loop will be kicked, which will cause this
// method to be called again
if (!mDataConnectionIsConnected) return;
- if (mAccounts == null) return;
if (mStorageIsLow) return;
+ final long now = SystemClock.elapsedRealtime();
+
// Compute the alarm fire time:
// - not syncing: time of the next sync operation
// - syncing, no notification: time from sync start to notification create time
// - syncing, with notification: time till timeout of the active sync operation
- Long alarmTime = null;
+ Long alarmTime;
ActiveSyncContext activeSyncContext = mActiveSyncContext;
if (activeSyncContext == null) {
- SyncOperation syncOperation;
synchronized (mSyncQueue) {
- syncOperation = mSyncQueue.head();
- }
- if (syncOperation != null) {
- alarmTime = syncOperation.earliestRunTime;
+ final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
+ if (earliestFuturePollElapsedTime == null && candidate == null) {
+ alarmTime = null;
+ } else if (earliestFuturePollElapsedTime == null) {
+ alarmTime = candidate.second;
+ } else if (candidate == null) {
+ alarmTime = earliestFuturePollElapsedTime;
+ } else {
+ alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
+ }
}
} else {
final long notificationTime =
@@ -2074,7 +1975,7 @@ class SyncManager implements OnAccountsUpdateListener {
when += ERROR_NOTIFICATION_DELAY_MS;
// convert when fron absolute time to elapsed run time
long delay = when - System.currentTimeMillis();
- when = SystemClock.elapsedRealtime() + delay;
+ when = now + delay;
alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
}
}
@@ -2193,226 +2094,8 @@ class SyncManager implements OnAccountsUpdateListener {
SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
syncOperation.account.name.hashCode());
- mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
- downstreamActivity, upstreamActivity);
- }
- }
-
- static class SyncQueue {
- private SyncStorageEngine mSyncStorageEngine;
-
- private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
-
- // A priority queue of scheduled SyncOperations that is designed to make it quick
- // to find the next SyncOperation that should be considered for running.
- private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>();
-
- // A Map of SyncOperations operationKey -> SyncOperation that is designed for
- // quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap();
-
- public SyncQueue(SyncStorageEngine syncStorageEngine) {
- mSyncStorageEngine = syncStorageEngine;
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- final int N = ops.size();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, 0);
- syncOperation.pendingOperation = op;
- add(syncOperation, op);
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public boolean add(SyncOperation operation) {
- return add(new SyncOperation(operation),
- null /* this is not coming from the database */);
- }
-
- private boolean add(SyncOperation operation,
- SyncStorageEngine.PendingOperation pop) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
-
- // If this operation is expedited then set its earliestRunTime to be immediately
- // before the head of the list, or not if none are in the list.
- if (operation.delay < 0) {
- SyncOperation headOperation = head();
- if (headOperation != null) {
- operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(),
- headOperation.earliestRunTime - 1);
- } else {
- operation.earliestRunTime = SystemClock.elapsedRealtime();
- }
- }
-
- // - if an operation with the same key exists and this one should run earlier,
- // delete the old one and add the new one
- // - if an operation with the same key exists and if this one should run
- // later, ignore it
- // - if no operation exists then add the new one
- final String operationKey = operation.key;
- SyncOperation existingOperation = mOpsByKey.get(operationKey);
-
- // if this operation matches an existing operation that is being retried (delay > 0)
- // and this isn't a manual sync operation, ignore this operation
- if (existingOperation != null && existingOperation.delay > 0) {
- if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
- return false;
- }
- }
-
- if (existingOperation != null
- && operation.earliestRunTime >= existingOperation.earliestRunTime) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
- return false;
- }
-
- if (existingOperation != null) {
- removeByKey(operationKey);
- }
-
- operation.pendingOperation = pop;
- if (operation.pendingOperation == null) {
- pop = new SyncStorageEngine.PendingOperation(
- operation.account, operation.syncSource,
- operation.authority, operation.extras);
- pop = mSyncStorageEngine.insertIntoPending(pop);
- if (pop == null) {
- throw new IllegalStateException("error adding pending sync operation "
- + operation);
- }
- operation.pendingOperation = pop;
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) {
- debugCheckDataStructures(
- false /* don't compare with the DB, since we know
- it is inconsistent right now */ );
- }
- mOpsByKey.put(operationKey, operation);
- mOpsByWhen.add(operation);
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
- return true;
- }
-
- public void removeByKey(String operationKey) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- SyncOperation operationToRemove = mOpsByKey.remove(operationKey);
- if (!mOpsByWhen.remove(operationToRemove)) {
- throw new IllegalStateException(
- "unable to find " + operationToRemove + " in mOpsByWhen");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operationToRemove;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public SyncOperation head() {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- return mOpsByWhen.peek();
- }
-
- public void popHead() {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- SyncOperation operation = mOpsByWhen.remove();
- if (mOpsByKey.remove(operation.key) == null) {
- throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public void clear(Account account, String authority) {
- Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.entrySet().iterator();
- while (entries.hasNext()) {
- Map.Entry<String, SyncOperation> entry = entries.next();
- SyncOperation syncOperation = entry.getValue();
- if (account != null && !syncOperation.account.equals(account)) continue;
- if (authority != null && !syncOperation.authority.equals(authority)) continue;
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- entries.remove();
- if (!mOpsByWhen.remove(syncOperation)) {
- throw new IllegalStateException(
- "unable to find " + syncOperation + " in mOpsByWhen");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + syncOperation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
- }
-
- public void dump(StringBuilder sb) {
- sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n");
- for (SyncOperation operation : mOpsByWhen) {
- sb.append(operation).append("\n");
- }
- }
-
- private void debugCheckDataStructures(boolean checkDatabase) {
- if (mOpsByKey.size() != mOpsByWhen.size()) {
- throw new IllegalStateException("size mismatch: "
- + mOpsByKey .size() + " != " + mOpsByWhen.size());
- }
- for (SyncOperation operation : mOpsByWhen) {
- if (!mOpsByKey.containsKey(operation.key)) {
- throw new IllegalStateException(
- "operation " + operation + " is in mOpsByWhen but not mOpsByKey");
- }
- }
- for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) {
- final SyncOperation operation = entry.getValue();
- final String key = entry.getKey();
- if (!key.equals(operation.key)) {
- throw new IllegalStateException(
- "operation " + operation + " in mOpsByKey doesn't match key " + key);
- }
- if (!mOpsByWhen.contains(operation)) {
- throw new IllegalStateException(
- "operation " + operation + " is in mOpsByKey but not mOpsByWhen");
- }
- }
-
- if (checkDatabase) {
- final int N = mSyncStorageEngine.getPendingOperationCount();
- if (mOpsByKey.size() != N) {
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- StringBuilder sb = new StringBuilder();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- sb.append("#");
- sb.append(i);
- sb.append(": account=");
- sb.append(op.account);
- sb.append(" syncSource=");
- sb.append(op.syncSource);
- sb.append(" authority=");
- sb.append(op.authority);
- sb.append("\n");
- }
- dump(sb);
- throw new IllegalStateException("DB size mismatch: "
- + mOpsByKey.size() + " != " + N + "\n"
- + sb.toString());
- }
- }
+ mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
+ resultMessage, downstreamActivity, upstreamActivity);
}
}
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
new file mode 100644
index 0000000..a7d036f
--- /dev/null
+++ b/core/java/android/content/SyncOperation.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Value type that represents a sync operation.
+ * @hide
+ */
+public class SyncOperation implements Comparable {
+ public final Account account;
+ public int syncSource;
+ public String authority;
+ public Bundle extras;
+ public final String key;
+ public long earliestRunTime;
+ public boolean expedited;
+ public SyncStorageEngine.PendingOperation pendingOperation;
+
+ public SyncOperation(Account account, int source, String authority, Bundle extras,
+ long delayInMs) {
+ this.account = account;
+ this.syncSource = source;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ final long now = SystemClock.elapsedRealtime();
+ if (delayInMs < 0) {
+ this.expedited = true;
+ this.earliestRunTime = now;
+ } else {
+ this.expedited = false;
+ this.earliestRunTime = now + delayInMs;
+ }
+ this.key = toKey();
+ }
+
+ private void removeFalseExtra(String extraName) {
+ if (!extras.getBoolean(extraName, false)) {
+ extras.remove(extraName);
+ }
+ }
+
+ SyncOperation(SyncOperation other) {
+ this.account = other.account;
+ this.syncSource = other.syncSource;
+ this.authority = other.authority;
+ this.extras = new Bundle(other.extras);
+ this.expedited = other.expedited;
+ this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.key = toKey();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb, false /* asKey */);
+ sb.append(" syncSource: ").append(syncSource);
+ sb.append(" when: ").append(earliestRunTime);
+ sb.append(" expedited: ").append(expedited);
+ return sb.toString();
+ }
+
+ private String toKey() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb, true /* asKey */);
+ return sb.toString();
+ }
+
+ public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb, boolean asKey) {
+ sb.append("[");
+ for (String key : bundle.keySet()) {
+ // if we are writing this as a key don't consider whether this
+ // is an initialization sync or not when computing the key since
+ // we set this flag appropriately when dispatching the sync request.
+ if (asKey && ContentResolver.SYNC_EXTRAS_INITIALIZE.equals(key)) {
+ continue;
+ }
+ sb.append(key).append("=").append(bundle.get(key)).append(" ");
+ }
+ sb.append("]");
+ }
+
+ public int compareTo(Object o) {
+ SyncOperation other = (SyncOperation)o;
+ if (earliestRunTime == other.earliestRunTime) {
+ return 0;
+ }
+ return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+ }
+}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
new file mode 100644
index 0000000..28baa0d
--- /dev/null
+++ b/core/java/android/content/SyncQueue.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.util.Pair;
+import android.util.Log;
+import android.accounts.Account;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ *
+ * @hide
+ */
+public class SyncQueue {
+ private static final String TAG = "SyncManager";
+ private SyncStorageEngine mSyncStorageEngine;
+
+ // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+ // quick lookup of an enqueued SyncOperation.
+ private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+
+ public SyncQueue(SyncStorageEngine syncStorageEngine) {
+ mSyncStorageEngine = syncStorageEngine;
+ ArrayList<SyncStorageEngine.PendingOperation> ops
+ = mSyncStorageEngine.getPendingOperations();
+ final int N = ops.size();
+ for (int i=0; i<N; i++) {
+ SyncStorageEngine.PendingOperation op = ops.get(i);
+ SyncOperation syncOperation = new SyncOperation(
+ op.account, op.syncSource, op.authority, op.extras, 0 /* delay */);
+ syncOperation.expedited = op.expedited;
+ syncOperation.pendingOperation = op;
+ add(syncOperation, op);
+ }
+ }
+
+ public boolean add(SyncOperation operation) {
+ return add(operation, null /* this is not coming from the database */);
+ }
+
+ private boolean add(SyncOperation operation,
+ SyncStorageEngine.PendingOperation pop) {
+ // - if an operation with the same key exists and this one should run earlier,
+ // update the earliestRunTime of the existing to the new time
+ // - if an operation with the same key exists and if this one should run
+ // later, ignore it
+ // - if no operation exists then add the new one
+ final String operationKey = operation.key;
+ final SyncOperation existingOperation = mOperationsMap.get(operationKey);
+
+ if (existingOperation != null) {
+ boolean changed = false;
+ if (existingOperation.expedited == operation.expedited) {
+ final long newRunTime =
+ Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
+ if (existingOperation.earliestRunTime != newRunTime) {
+ existingOperation.earliestRunTime = newRunTime;
+ changed = true;
+ }
+ } else {
+ if (operation.expedited) {
+ existingOperation.expedited = true;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ operation.pendingOperation = pop;
+ if (operation.pendingOperation == null) {
+ pop = new SyncStorageEngine.PendingOperation(
+ operation.account, operation.syncSource,
+ operation.authority, operation.extras, operation.expedited);
+ pop = mSyncStorageEngine.insertIntoPending(pop);
+ if (pop == null) {
+ throw new IllegalStateException("error adding pending sync operation "
+ + operation);
+ }
+ operation.pendingOperation = pop;
+ }
+
+ mOperationsMap.put(operationKey, operation);
+ return true;
+ }
+
+ /**
+ * Remove the specified operation if it is in the queue.
+ * @param operation the operation to remove
+ */
+ public void remove(SyncOperation operation) {
+ SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+ if (operationToRemove == null) {
+ return;
+ }
+ if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + operationToRemove;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+
+ /**
+ * Find the operation that should run next. Operations are sorted by their earliestRunTime,
+ * prioritizing first those with a syncable state of "unknown" that aren't retries then
+ * expedited operations.
+ * The earliestRunTime is adjusted by the sync adapter's backoff and delayUntil times, if any.
+ * @return the operation that should run next and when it should run. The time may be in
+ * the future. It is expressed in milliseconds since boot.
+ */
+ public Pair<SyncOperation, Long> nextOperation() {
+ SyncOperation best = null;
+ long bestRunTime = 0;
+ boolean bestSyncableIsUnknownAndNotARetry = false;
+ for (SyncOperation op : mOperationsMap.values()) {
+ long opRunTime = op.earliestRunTime;
+ if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+ Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(op.account, op.authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority);
+ opRunTime = Math.max(
+ Math.max(opRunTime, delayUntil),
+ backoff != null ? backoff.first : 0);
+ }
+ // we know a sync is a retry if the intialization flag is set, since that will only
+ // be set by the sync dispatching code, thus if it is set it must have already been
+ // dispatched
+ final boolean syncableIsUnknownAndNotARetry =
+ !op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+ && mSyncStorageEngine.getIsSyncable(op.account, op.authority) < 0;
+ // if the unsyncable state differs, make the current the best if it is unsyncable
+ // else, if the expedited state differs, make the current the best if it is expedited
+ // else, make the current the best if it is earlier than the best
+ if (best == null
+ || ((bestSyncableIsUnknownAndNotARetry == syncableIsUnknownAndNotARetry)
+ ? (best.expedited == op.expedited
+ ? opRunTime < bestRunTime
+ : op.expedited)
+ : syncableIsUnknownAndNotARetry)) {
+ best = op;
+ bestSyncableIsUnknownAndNotARetry = syncableIsUnknownAndNotARetry;
+ bestRunTime = opRunTime;
+ }
+ }
+ if (best == null) {
+ return null;
+ }
+ return Pair.create(best, bestRunTime);
+ }
+
+ /**
+ * Find and return the SyncOperation that should be run next and is ready to run.
+ * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
+ * decide if the sync operation is ready to run
+ * @return the SyncOperation that should be run next and is ready to run.
+ */
+ public Pair<SyncOperation, Long> nextReadyToRun(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation();
+ if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
+ return null;
+ }
+ return nextOpAndRunTime;
+ }
+
+ public void remove(Account account, String authority) {
+ Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry<String, SyncOperation> entry = entries.next();
+ SyncOperation syncOperation = entry.getValue();
+ if (account != null && !syncOperation.account.equals(account)) {
+ continue;
+ }
+ if (authority != null && !syncOperation.authority.equals(authority)) {
+ continue;
+ }
+ entries.remove();
+ if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + syncOperation;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+ }
+
+ public void dump(StringBuilder sb) {
+ sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
+ for (SyncOperation operation : mOperationsMap.values()) {
+ sb.append(operation).append("\n");
+ }
+ }
+}
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
index 57161b6..8b0afbd 100644
--- a/core/java/android/content/SyncResult.java
+++ b/core/java/android/content/SyncResult.java
@@ -1,30 +1,129 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content;
import android.os.Parcel;
import android.os.Parcelable;
/**
- * This class is used to store information about the result of a sync
+ * This class is used to communicate the results of a sync operation to the SyncManager.
+ * Based on the values here the SyncManager will determine the disposition of the
+ * sync and whether or not a new sync operation needs to be scheduled in the future.
+ *
*/
public final class SyncResult implements Parcelable {
+ /**
+ * Used to indicate that the SyncAdapter is already performing a sync operation, though
+ * not necessarily for the requested account and authority and that it wasn't able to
+ * process this request. The SyncManager will reschedule the request to run later.
+ */
public final boolean syncAlreadyInProgress;
+
+ /**
+ * Used to indicate that the SyncAdapter determined that it would need to issue
+ * too many delete operations to the server in order to satisfy the request
+ * (as defined by the SyncAdapter). The SyncManager will record
+ * that the sync request failed and will cause a System Notification to be created
+ * asking the user what they want to do about this. It will give the user a chance to
+ * choose between (1) go ahead even with those deletes, (2) revert the deletes,
+ * or (3) take no action. If the user decides (1) or (2) the SyncManager will issue another
+ * sync request with either {@link ContentResolver#SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS}
+ * or {@link ContentResolver#SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS} set in the extras.
+ * It is then up to the SyncAdapter to decide how to honor that request.
+ */
public boolean tooManyDeletions;
+
+ /**
+ * Used to indicate that the SyncAdapter experienced a hard error due to trying the same
+ * operation too many times (as defined by the SyncAdapter). The SyncManager will record
+ * that the sync request failed and it will not reschedule the request.
+ */
public boolean tooManyRetries;
+
+ /**
+ * Used to indicate that the SyncAdapter experienced a hard error due to an error it
+ * received from interacting with the storage later. The SyncManager will record that
+ * the sync request failed and it will not reschedule the request.
+ */
public boolean databaseError;
+
+ /**
+ * If set the SyncManager will request an immediate sync with the same Account and authority
+ * (but empty extras Bundle) as was used in the sync request.
+ */
public boolean fullSyncRequested;
+
+ /**
+ * This field is ignored by the SyncManager.
+ */
public boolean partialSyncUnavailable;
+
+ /**
+ * This field is ignored by the SyncManager.
+ */
public boolean moreRecordsToGet;
+
+ /**
+ * Used to indicate to the SyncManager that future sync requests that match the request's
+ * Account and authority should be delayed at least this many seconds.
+ */
+ public long delayUntil;
+
+ /**
+ * Used to hold extras statistics about the sync operation. Some of these indicate that
+ * the sync request resulted in a hard or soft error, others are for purely informational
+ * purposes.
+ */
public final SyncStats stats;
+
+ /**
+ * This instance of a SyncResult is returned by the SyncAdapter in response to a
+ * sync request when a sync is already underway. The SyncManager will reschedule the
+ * sync request to try again later.
+ */
public static final SyncResult ALREADY_IN_PROGRESS;
static {
ALREADY_IN_PROGRESS = new SyncResult(true);
}
+ /**
+ * Create a "clean" SyncResult. If this is returned without any changes then the
+ * SyncManager will consider the sync to have completed successfully. The various fields
+ * can be set by the SyncAdapter in order to give the SyncManager more information as to
+ * the disposition of the sync.
+ * <p>
+ * The errors are classified into two broad categories: hard errors and soft errors.
+ * Soft errors are retried with exponential backoff. Hard errors are not retried (except
+ * when the hard error is for a {@link ContentResolver#SYNC_EXTRAS_UPLOAD} request,
+ * in which the request is retryed without the {@link ContentResolver#SYNC_EXTRAS_UPLOAD}
+ * extra set). The SyncManager checks the type of error by calling
+ * {@link SyncResult#hasHardError()} and {@link SyncResult#hasSoftError()}. If both are
+ * true then the SyncManager treats it as a hard error, not a soft error.
+ */
public SyncResult() {
this(false);
}
+ /**
+ * Internal helper for creating a clean SyncResult or one that indicated that
+ * a sync is already in progress.
+ * @param syncAlreadyInProgress if true then set the {@link #syncAlreadyInProgress} flag
+ */
private SyncResult(boolean syncAlreadyInProgress) {
this.syncAlreadyInProgress = syncAlreadyInProgress;
this.tooManyDeletions = false;
@@ -32,6 +131,7 @@ public final class SyncResult implements Parcelable {
this.fullSyncRequested = false;
this.partialSyncUnavailable = false;
this.moreRecordsToGet = false;
+ this.delayUntil = 0;
this.stats = new SyncStats();
}
@@ -43,9 +143,25 @@ public final class SyncResult implements Parcelable {
fullSyncRequested = parcel.readInt() != 0;
partialSyncUnavailable = parcel.readInt() != 0;
moreRecordsToGet = parcel.readInt() != 0;
+ delayUntil = parcel.readLong();
stats = new SyncStats(parcel);
}
+ /**
+ * Convenience method for determining if the SyncResult indicates that a hard error
+ * occurred. See {@link #SyncResult()} for an explanation of what the SyncManager does
+ * when it sees a hard error.
+ * <p>
+ * A hard error is indicated when any of the following is true:
+ * <ul>
+ * <li> {@link SyncStats#numParseExceptions} > 0
+ * <li> {@link SyncStats#numConflictDetectedExceptions} > 0
+ * <li> {@link SyncStats#numAuthExceptions} > 0
+ * <li> {@link #tooManyDeletions}
+ * <li> {@link #tooManyRetries}
+ * <li> {@link #databaseError}
+ * @return true if a hard error is indicated
+ */
public boolean hasHardError() {
return stats.numParseExceptions > 0
|| stats.numConflictDetectedExceptions > 0
@@ -55,10 +171,26 @@ public final class SyncResult implements Parcelable {
|| databaseError;
}
+ /**
+ * Convenience method for determining if the SyncResult indicates that a soft error
+ * occurred. See {@link #SyncResult()} for an explanation of what the SyncManager does
+ * when it sees a soft error.
+ * <p>
+ * A soft error is indicated when any of the following is true:
+ * <ul>
+ * <li> {@link SyncStats#numIoExceptions} > 0
+ * <li> {@link #syncAlreadyInProgress}
+ * </ul>
+ * @return true if a hard error is indicated
+ */
public boolean hasSoftError() {
return syncAlreadyInProgress || stats.numIoExceptions > 0;
}
+ /**
+ * A convenience method for determining of the SyncResult indicates that an error occurred.
+ * @return true if either a soft or hard error occurred
+ */
public boolean hasError() {
return hasSoftError() || hasHardError();
}
@@ -69,6 +201,10 @@ public final class SyncResult implements Parcelable {
|| stats.numUpdates > 0;
}
+ /**
+ * Clears the SyncResult to a clean state. Throws an {@link UnsupportedOperationException}
+ * if this is called when {@link #syncAlreadyInProgress} is set.
+ */
public void clear() {
if (syncAlreadyInProgress) {
throw new UnsupportedOperationException(
@@ -80,6 +216,7 @@ public final class SyncResult implements Parcelable {
fullSyncRequested = false;
partialSyncUnavailable = false;
moreRecordsToGet = false;
+ delayUntil = 0;
stats.clear();
}
@@ -105,6 +242,7 @@ public final class SyncResult implements Parcelable {
parcel.writeInt(fullSyncRequested ? 1 : 0);
parcel.writeInt(partialSyncUnavailable ? 1 : 0);
parcel.writeInt(moreRecordsToGet ? 1 : 0);
+ parcel.writeLong(delayUntil);
stats.writeToParcel(parcel, flags);
}
@@ -123,6 +261,7 @@ public final class SyncResult implements Parcelable {
sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
}
if (moreRecordsToGet) sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+ if (delayUntil > 0) sb.append(" delayUntil: ").append(delayUntil);
sb.append(stats);
return sb.toString();
}
diff --git a/core/java/android/content/SyncStateContentProviderHelper.java b/core/java/android/content/SyncStateContentProviderHelper.java
deleted file mode 100644
index 64bbe25..0000000
--- a/core/java/android/content/SyncStateContentProviderHelper.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import com.android.internal.util.ArrayUtils;
-
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-import android.accounts.Account;
-
-/**
- * Extends the schema of a ContentProvider to include the _sync_state table
- * and implements query/insert/update/delete to access that table using the
- * authority "syncstate". This can be used to store the sync state for a
- * set of accounts.
- *
- * @hide
- */
-public class SyncStateContentProviderHelper {
- final SQLiteOpenHelper mOpenHelper;
-
- private static final String SYNC_STATE_AUTHORITY = "syncstate";
- private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int STATE = 0;
-
- private static final Uri CONTENT_URI =
- Uri.parse("content://" + SYNC_STATE_AUTHORITY + "/state");
-
- private static final String ACCOUNT_WHERE = "_sync_account = ? AND _sync_account_type = ?";
-
- private final Provider mInternalProviderInterface;
-
- private static final String SYNC_STATE_TABLE = "_sync_state";
- private static long DB_VERSION = 3;
-
- private static final String[] ACCOUNT_PROJECTION =
- new String[]{"_sync_account", "_sync_account_type"};
-
- static {
- sURIMatcher.addURI(SYNC_STATE_AUTHORITY, "state", STATE);
- }
-
- public SyncStateContentProviderHelper(SQLiteOpenHelper openHelper) {
- mOpenHelper = openHelper;
- mInternalProviderInterface = new Provider();
- }
-
- public ContentProvider asContentProvider() {
- return mInternalProviderInterface;
- }
-
- public void createDatabase(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS _sync_state");
- db.execSQL("CREATE TABLE _sync_state (" +
- "_id INTEGER PRIMARY KEY," +
- "_sync_account TEXT," +
- "_sync_account_type TEXT," +
- "data TEXT," +
- "UNIQUE(_sync_account, _sync_account_type)" +
- ");");
-
- db.execSQL("DROP TABLE IF EXISTS _sync_state_metadata");
- db.execSQL("CREATE TABLE _sync_state_metadata (" +
- "version INTEGER" +
- ");");
- ContentValues values = new ContentValues();
- values.put("version", DB_VERSION);
- db.insert("_sync_state_metadata", "version", values);
- }
-
- protected void onDatabaseOpened(SQLiteDatabase db) {
- long version = DatabaseUtils.longForQuery(db,
- "select version from _sync_state_metadata", null);
- if (version != DB_VERSION) {
- createDatabase(db);
- }
- }
-
- class Provider extends ContentProvider {
- public boolean onCreate() {
- throw new UnsupportedOperationException("not implemented");
- }
-
- public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- int match = sURIMatcher.match(url);
- switch (match) {
- case STATE:
- return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs,
- null, null, sortOrder);
- default:
- throw new UnsupportedOperationException("Cannot query URL: " + url);
- }
- }
-
- public String getType(Uri uri) {
- throw new UnsupportedOperationException("not implemented");
- }
-
- public Uri insert(Uri url, ContentValues values) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- int match = sURIMatcher.match(url);
- switch (match) {
- case STATE: {
- long id = db.insert(SYNC_STATE_TABLE, "feed", values);
- return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build();
- }
- default:
- throw new UnsupportedOperationException("Cannot insert into URL: " + url);
- }
- }
-
- public int delete(Uri url, String userWhere, String[] whereArgs) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- switch (sURIMatcher.match(url)) {
- case STATE:
- return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
- default:
- throw new IllegalArgumentException("Unknown URL " + url);
- }
-
- }
-
- public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- switch (sURIMatcher.match(url)) {
- case STATE:
- return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
- default:
- throw new UnsupportedOperationException("Cannot update URL: " + url);
- }
-
- }
- }
-
- /**
- * Check if the url matches content that this ContentProvider manages.
- * @param url the Uri to check
- * @return true if this ContentProvider can handle that Uri.
- */
- public boolean matches(Uri url) {
- return (SYNC_STATE_AUTHORITY.equals(url.getAuthority()));
- }
-
- /**
- * Replaces the contents of the _sync_state table in the destination ContentProvider
- * with the row that matches account, if any, in the source ContentProvider.
- * <p>
- * The ContentProviders must expose the _sync_state table as URI content://syncstate/state.
- * @param dbSrc the database to read from
- * @param dbDest the database to write to
- * @param account the account of the row that should be copied over.
- */
- public void copySyncState(SQLiteDatabase dbSrc, SQLiteDatabase dbDest,
- Account account) {
- final String[] whereArgs = new String[]{account.name, account.type};
- Cursor c = dbSrc.query(SYNC_STATE_TABLE,
- new String[]{"_sync_account", "_sync_account_type", "data"},
- ACCOUNT_WHERE, whereArgs, null, null, null);
- try {
- if (c.moveToNext()) {
- ContentValues values = new ContentValues();
- values.put("_sync_account", c.getString(0));
- values.put("_sync_account_type", c.getString(1));
- values.put("data", c.getBlob(2));
- dbDest.replace(SYNC_STATE_TABLE, "_sync_account", values);
- }
- } finally {
- c.close();
- }
- }
-
- public void onAccountsChanged(Account[] accounts) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
- try {
- while (c.moveToNext()) {
- final String accountName = c.getString(0);
- final String accountType = c.getString(1);
- Account account = new Account(accountName, accountType);
- if (!ArrayUtils.contains(accounts, account)) {
- db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE,
- new String[]{accountName, accountType});
- }
- }
- } finally {
- c.close();
- }
- }
-
- public void discardSyncData(SQLiteDatabase db, Account account) {
- if (account != null) {
- db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account.name, account.type});
- } else {
- db.delete(SYNC_STATE_TABLE, null, null);
- }
- }
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public byte[] readSyncDataBytes(SQLiteDatabase db, Account account) {
- Cursor c = db.query(SYNC_STATE_TABLE, null, ACCOUNT_WHERE,
- new String[]{account.name, account.type}, null, null, null);
- try {
- if (c.moveToFirst()) {
- return c.getBlob(c.getColumnIndexOrThrow("data"));
- }
- } finally {
- c.close();
- }
- return null;
- }
-
- /**
- * Sets the SyncData bytes for the given account. The bytes array may be null.
- */
- public void writeSyncDataBytes(SQLiteDatabase db, Account account, byte[] data) {
- ContentValues values = new ContentValues();
- values.put("data", data);
- db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE,
- new String[]{account.name, account.type});
- }
-}
diff --git a/core/java/android/content/SyncStats.java b/core/java/android/content/SyncStats.java
index cc544c0..b7f2a85 100644
--- a/core/java/android/content/SyncStats.java
+++ b/core/java/android/content/SyncStats.java
@@ -20,17 +20,77 @@ import android.os.Parcelable;
import android.os.Parcel;
/**
- * @hide
+ * Used to record various statistics about the result of a sync operation. The SyncManager
+ * gets access to these via a {@link SyncResult} and uses some of them to determine the
+ * disposition of the sync. See {@link SyncResult} for further dicussion on how the
+ * SyncManager uses these values.
*/
public class SyncStats implements Parcelable {
+ /**
+ * The SyncAdapter was unable to authenticate the {@link android.accounts.Account}
+ * that was specified in the request. The user needs to take some action to resolve
+ * before a future request can expect to succeed. This is considered a hard error.
+ */
public long numAuthExceptions;
+
+ /**
+ * The SyncAdapter had a problem, most likely with the network connectivity or a timeout
+ * while waiting for a network response. The request may succeed if it is tried again
+ * later. This is considered a soft error.
+ */
public long numIoExceptions;
+
+ /**
+ * The SyncAdapter had a problem with the data it received from the server or the storage
+ * later. This problem will likely repeat if the request is tried again. The problem
+ * will need to be cleared up by either the server or the storage layer (likely with help
+ * from the user). If the SyncAdapter cleans up the data itself then it typically won't
+ * increment this value although it may still do so in order to record that it had to
+ * perform some cleanup. E.g., if the SyncAdapter received a bad entry from the server
+ * when processing a feed of entries, it may choose to drop the entry and thus make
+ * progress and still increment this value just so the SyncAdapter can record that an
+ * error occurred. This is considered a hard error.
+ */
public long numParseExceptions;
+
+ /**
+ * The SyncAdapter detected that there was an unrecoverable version conflict when it
+ * attempted to update or delete a version of a resource on the server. This is expected
+ * to clear itself automatically once the new state is retrieved from the server,
+ * though it may remain until the user intervenes manually, perhaps by clearing the
+ * local storage and starting over frmo scratch. This is considered a hard error.
+ */
public long numConflictDetectedExceptions;
+
+ /**
+ * Counter for tracking how many inserts were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
public long numInserts;
+
+ /**
+ * Counter for tracking how many updates were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
public long numUpdates;
+
+ /**
+ * Counter for tracking how many deletes were performed by the sync operation, as defined
+ * by the SyncAdapter.
+ */
public long numDeletes;
+
+ /**
+ * Counter for tracking how many entries were affected by the sync operation, as defined
+ * by the SyncAdapter.
+ */
public long numEntries;
+
+ /**
+ * Counter for tracking how many entries, either from the server or the local store, were
+ * ignored during the sync operation. This could happen if the SyncAdapter detected some
+ * unparsable data but decided to skip it and move on rather than failing immediately.
+ */
public long numSkippedEntries;
public SyncStats() {
@@ -75,6 +135,9 @@ public class SyncStats implements Parcelable {
return sb.toString();
}
+ /**
+ * Reset all the counters to 0.
+ */
public void clear() {
numAuthExceptions = 0;
numIoExceptions = 0;
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index b8fda03..bb2b2da 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -20,10 +20,12 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import java.util.ArrayList;
+
/** @hide */
public class SyncStatusInfo implements Parcelable {
- static final int VERSION = 1;
-
+ static final int VERSION = 2;
+
public final int authorityId;
public long totalElapsedTime;
public int numSyncs;
@@ -31,6 +33,7 @@ public class SyncStatusInfo implements Parcelable {
public int numSourceServer;
public int numSourceLocal;
public int numSourceUser;
+ public int numSourcePeriodic;
public long lastSuccessTime;
public int lastSuccessSource;
public long lastFailureTime;
@@ -39,7 +42,10 @@ public class SyncStatusInfo implements Parcelable {
public long initialFailureTime;
public boolean pending;
public boolean initialize;
-
+ public ArrayList<Long> periodicSyncTimes;
+
+ private static final String TAG = "Sync";
+
SyncStatusInfo(int authorityId) {
this.authorityId = authorityId;
}
@@ -50,10 +56,11 @@ public class SyncStatusInfo implements Parcelable {
return Integer.parseInt(lastFailureMesg);
}
} catch (NumberFormatException e) {
+ Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e);
}
return def;
}
-
+
public int describeContents() {
return 0;
}
@@ -75,11 +82,19 @@ public class SyncStatusInfo implements Parcelable {
parcel.writeLong(initialFailureTime);
parcel.writeInt(pending ? 1 : 0);
parcel.writeInt(initialize ? 1 : 0);
+ if (periodicSyncTimes != null) {
+ parcel.writeInt(periodicSyncTimes.size());
+ for (long periodicSyncTime : periodicSyncTimes) {
+ parcel.writeLong(periodicSyncTime);
+ }
+ } else {
+ parcel.writeInt(-1);
+ }
}
SyncStatusInfo(Parcel parcel) {
int version = parcel.readInt();
- if (version != VERSION) {
+ if (version != VERSION && version != 1) {
Log.w("SyncStatusInfo", "Unknown version: " + version);
}
authorityId = parcel.readInt();
@@ -97,8 +112,51 @@ public class SyncStatusInfo implements Parcelable {
initialFailureTime = parcel.readLong();
pending = parcel.readInt() != 0;
initialize = parcel.readInt() != 0;
+ if (version == 1) {
+ periodicSyncTimes = null;
+ } else {
+ int N = parcel.readInt();
+ if (N < 0) {
+ periodicSyncTimes = null;
+ } else {
+ periodicSyncTimes = new ArrayList<Long>();
+ for (int i=0; i<N; i++) {
+ periodicSyncTimes.add(parcel.readLong());
+ }
+ }
+ }
}
-
+
+ public void setPeriodicSyncTime(int index, long when) {
+ ensurePeriodicSyncTimeSize(index);
+ periodicSyncTimes.set(index, when);
+ }
+
+ private void ensurePeriodicSyncTimeSize(int index) {
+ if (periodicSyncTimes == null) {
+ periodicSyncTimes = new ArrayList<Long>(0);
+ }
+
+ final int requiredSize = index + 1;
+ if (periodicSyncTimes.size() < requiredSize) {
+ for (int i = periodicSyncTimes.size(); i < requiredSize; i++) {
+ periodicSyncTimes.add((long) 0);
+ }
+ }
+ }
+
+ public long getPeriodicSyncTime(int index) {
+ if (periodicSyncTimes == null || periodicSyncTimes.size() < (index + 1)) {
+ return 0;
+ }
+ return periodicSyncTimes.get(index);
+ }
+
+ public void removePeriodicSyncTime(int index) {
+ ensurePeriodicSyncTimeSize(index);
+ periodicSyncTimes.remove(index);
+ }
+
public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() {
public SyncStatusInfo createFromParcel(Parcel in) {
return new SyncStatusInfo(in);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index be70909..98a4993 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -25,7 +25,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.accounts.Account;
-import android.backup.IBackupManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -37,10 +36,10 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
+import android.util.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -50,6 +49,7 @@ import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TimeZone;
+import java.util.List;
/**
* Singleton that tracks the sync data and overall sync
@@ -59,9 +59,10 @@ import java.util.TimeZone;
*/
public class SyncStorageEngine extends Handler {
private static final String TAG = "SyncManager";
- private static final boolean DEBUG = false;
private static final boolean DEBUG_FILE = false;
+ private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
// @VisibleForTesting
static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
@@ -89,6 +90,11 @@ public class SyncStorageEngine extends Handler {
/** Enum value for a user-initiated sync. */
public static final int SOURCE_USER = 3;
+ /** Enum value for a periodic sync. */
+ public static final int SOURCE_PERIODIC = 4;
+
+ public static final long NOT_IN_BACKOFF_MODE = -1;
+
private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
@@ -97,7 +103,8 @@ public class SyncStorageEngine extends Handler {
public static final String[] SOURCES = { "SERVER",
"LOCAL",
"POLL",
- "USER" };
+ "USER",
+ "PERIODIC" };
// The MESG column will contain one of these or one of the Error types.
public static final String MESG_SUCCESS = "success";
@@ -113,21 +120,34 @@ public class SyncStorageEngine extends Handler {
private static final boolean SYNC_ENABLED_DEFAULT = false;
+ // the version of the accounts xml file format
+ private static final int ACCOUNTS_VERSION = 2;
+
+ private static HashMap<String, String> sAuthorityRenames;
+
+ static {
+ sAuthorityRenames = new HashMap<String, String>();
+ sAuthorityRenames.put("contacts", "com.android.contacts");
+ sAuthorityRenames.put("calendar", "com.android.calendar");
+ }
+
public static class PendingOperation {
final Account account;
final int syncSource;
final String authority;
final Bundle extras; // note: read-only.
+ final boolean expedited;
int authorityId;
byte[] flatExtras;
PendingOperation(Account account, int source,
- String authority, Bundle extras) {
+ String authority, Bundle extras, boolean expedited) {
this.account = account;
this.syncSource = source;
this.authority = authority;
this.extras = extras != null ? new Bundle(extras) : extras;
+ this.expedited = expedited;
this.authorityId = -1;
}
@@ -137,6 +157,7 @@ public class SyncStorageEngine extends Handler {
this.authority = other.authority;
this.extras = other.extras;
this.authorityId = other.authorityId;
+ this.expedited = other.expedited;
}
}
@@ -156,6 +177,10 @@ public class SyncStorageEngine extends Handler {
final int ident;
boolean enabled;
int syncable;
+ long backoffTime;
+ long backoffDelay;
+ long delayUntil;
+ final ArrayList<Pair<Bundle, Long>> periodicSyncs;
AuthorityInfo(Account account, String authority, int ident) {
this.account = account;
@@ -163,6 +188,10 @@ public class SyncStorageEngine extends Handler {
this.ident = ident;
enabled = SYNC_ENABLED_DEFAULT;
syncable = -1; // default to "unknown"
+ backoffTime = -1; // if < 0 then we aren't in backoff mode
+ backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+ periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
}
}
@@ -200,7 +229,7 @@ public class SyncStorageEngine extends Handler {
private final ArrayList<PendingOperation> mPendingOperations =
new ArrayList<PendingOperation>();
- private ActiveSyncInfo mActiveSync;
+ private SyncInfo mCurrentSync;
private final SparseArray<SyncStatusInfo> mSyncStatus =
new SparseArray<SyncStatusInfo>();
@@ -211,6 +240,8 @@ public class SyncStorageEngine extends Handler {
private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
= new RemoteCallbackList<ISyncStatusObserver>();
+ private int mNextAuthorityId = 0;
+
// We keep 4 weeks of stats.
private final DayStats[] mDayStats = new DayStats[7*4];
private final Calendar mCal;
@@ -218,6 +249,7 @@ public class SyncStorageEngine extends Handler {
private int mYearInDays;
private final Context mContext;
+
private static volatile SyncStorageEngine sSyncStorageEngine = null;
/**
@@ -252,15 +284,15 @@ public class SyncStorageEngine extends Handler {
private int mNextHistoryId = 0;
private boolean mMasterSyncAutomatically = true;
- private SyncStorageEngine(Context context) {
+ private SyncStorageEngine(Context context, File dataDir) {
mContext = context;
sSyncStorageEngine = this;
mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
- File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
+ syncDir.mkdirs();
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
@@ -270,18 +302,23 @@ public class SyncStorageEngine extends Handler {
readStatusLocked();
readPendingOperationsLocked();
readStatisticsLocked();
- readLegacyAccountInfoLocked();
+ readAndDeleteLegacyAccountInfoLocked();
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ writePendingOperationsLocked();
+ writeStatisticsLocked();
}
public static SyncStorageEngine newTestInstance(Context context) {
- return new SyncStorageEngine(context);
+ return new SyncStorageEngine(context, context.getFilesDir());
}
public static void init(Context context) {
if (sSyncStorageEngine != null) {
- throw new IllegalStateException("already initialized");
+ return;
}
- sSyncStorageEngine = new SyncStorageEngine(context);
+ File dataDir = Environment.getDataDirectory();
+ sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
public static SyncStorageEngine getSingleton() {
@@ -293,11 +330,11 @@ public class SyncStorageEngine extends Handler {
@Override public void handleMessage(Message msg) {
if (msg.what == MSG_WRITE_STATUS) {
- synchronized (mAccounts) {
+ synchronized (mAuthorities) {
writeStatusLocked();
}
} else if (msg.what == MSG_WRITE_STATISTICS) {
- synchronized (mAccounts) {
+ synchronized (mAuthorities) {
writeStatisticsLocked();
}
}
@@ -333,7 +370,9 @@ public class SyncStorageEngine extends Handler {
mChangeListeners.finishBroadcast();
}
- if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "reportChange " + which + " to: " + reports);
+ }
if (reports != null) {
int i = reports.size();
@@ -370,16 +409,20 @@ public class SyncStorageEngine extends Handler {
}
public void setSyncAutomatically(Account account, String providerName, boolean sync) {
- boolean wasEnabled;
+ Log.d(TAG, "setSyncAutomatically: " + account + ", provider " + providerName
+ + " -> " + sync);
synchronized (mAuthorities) {
AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
- wasEnabled = authority.enabled;
+ if (authority.enabled == sync) {
+ Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
+ return;
+ }
authority.enabled = sync;
writeAccountInfoLocked();
}
- if (!wasEnabled && sync) {
- mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+ if (sync) {
+ ContentResolver.requestSync(account, providerName, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
@@ -408,7 +451,6 @@ public class SyncStorageEngine extends Handler {
}
public void setIsSyncable(Account account, String providerName, int syncable) {
- int oldState;
if (syncable > 1) {
syncable = 1;
} else if (syncable < -1) {
@@ -417,26 +459,202 @@ public class SyncStorageEngine extends Handler {
Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable);
synchronized (mAuthorities) {
AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
- oldState = authority.syncable;
+ if (authority.syncable == syncable) {
+ Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
+ return;
+ }
authority.syncable = syncable;
writeAccountInfoLocked();
}
- if (oldState <= 0 && syncable > 0) {
- mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+ if (syncable > 0) {
+ ContentResolver.requestSync(account, providerName, new Bundle());
+ }
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public Pair<Long, Long> getBackoff(Account account, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff");
+ if (authority == null || authority.backoffTime < 0) {
+ return null;
+ }
+ return Pair.create(authority.backoffTime, authority.backoffDelay);
+ }
+ }
+
+ public void setBackoff(Account account, String providerName,
+ long nextSyncTime, long nextDelay) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+ + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
+ }
+ boolean changed = false;
+ synchronized (mAuthorities) {
+ if (account == null || providerName == null) {
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ if (account != null && !account.equals(accountInfo.account)) continue;
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (providerName != null && !providerName.equals(authorityInfo.authority)) {
+ continue;
+ }
+ if (authorityInfo.backoffTime != nextSyncTime
+ || authorityInfo.backoffDelay != nextDelay) {
+ authorityInfo.backoffTime = nextSyncTime;
+ authorityInfo.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+ }
+ } else {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
+ if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
+ return;
+ }
+ authority.backoffTime = nextSyncTime;
+ authority.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+ }
+
+ public void setDelayUntilTime(Account account, String providerName, long delayUntil) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
+ + " -> delayUntil " + delayUntil);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ account, providerName, -1 /* ident */, true);
+ if (authority.delayUntil == delayUntil) {
+ return;
+ }
+ authority.delayUntil = delayUntil;
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public long getDelayUntilTime(Account account, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil");
+ if (authority == null) {
+ return 0;
+ }
+ return authority.delayUntil;
+ }
+ }
+
+ private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras,
+ long period, boolean add) {
+ if (period <= 0) {
+ period = 0;
+ }
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName
+ + " -> period " + period + ", extras " + extras);
+ }
+ synchronized (mAuthorities) {
+ try {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (add) {
+ // add this periodic sync if one with the same extras doesn't already
+ // exist in the periodicSyncs array
+ boolean alreadyPresent = false;
+ for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+ Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+ final Bundle existingExtras = syncInfo.first;
+ if (equals(existingExtras, extras)) {
+ if (syncInfo.second == period) {
+ return;
+ }
+ authority.periodicSyncs.set(i, Pair.create(extras, period));
+ alreadyPresent = true;
+ break;
+ }
+ }
+ // if we added an entry to the periodicSyncs array also add an entry to
+ // the periodic syncs status to correspond to it
+ if (!alreadyPresent) {
+ authority.periodicSyncs.add(Pair.create(extras, period));
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+ }
+ } else {
+ // remove any periodic syncs that match the authority and extras
+ SyncStatusInfo status = mSyncStatus.get(authority.ident);
+ boolean changed = false;
+ Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Pair<Bundle, Long> syncInfo = iterator.next();
+ if (equals(syncInfo.first, extras)) {
+ iterator.remove();
+ changed = true;
+ // if we removed an entry from the periodicSyncs array also
+ // remove the corresponding entry from the status
+ if (status != null) {
+ status.removePeriodicSyncTime(i);
+ }
+ } else {
+ i++;
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ }
+ } finally {
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ }
}
+
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
+ public void addPeriodicSync(Account account, String providerName, Bundle extras,
+ long pollFrequency) {
+ updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */);
+ }
+
+ public void removePeriodicSync(Account account, String providerName, Bundle extras) {
+ updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */,
+ false /* remove */);
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs");
+ if (authority != null) {
+ for (Pair<Bundle, Long> item : authority.periodicSyncs) {
+ syncs.add(new PeriodicSync(account, providerName, item.first, item.second));
+ }
+ }
+ }
+ return syncs;
+ }
+
public void setMasterSyncAutomatically(boolean flag) {
- boolean old;
synchronized (mAuthorities) {
- old = mMasterSyncAutomatically;
+ if (mMasterSyncAutomatically == flag) {
+ return;
+ }
mMasterSyncAutomatically = flag;
writeAccountInfoLocked();
}
- if (!old && flag) {
- mContext.getContentResolver().requestSync(null, null, new Bundle());
+ if (flag) {
+ ContentResolver.requestSync(null, null, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
@@ -448,9 +666,17 @@ public class SyncStorageEngine extends Handler {
}
}
- public AuthorityInfo getAuthority(Account account, String authority) {
+ public AuthorityInfo getOrCreateAuthority(Account account, String authority) {
synchronized (mAuthorities) {
- return getAuthorityLocked(account, authority, null);
+ return getOrCreateAuthorityLocked(account, authority,
+ -1 /* assign a new identifier if creating a new authority */,
+ true /* write to storage if this results in a change */);
+ }
+ }
+
+ public void removeAuthority(Account account, String authority) {
+ synchronized (mAuthorities) {
+ removeAuthorityLocked(account, authority, true /* doWrite */);
}
}
@@ -477,8 +703,8 @@ public class SyncStorageEngine extends Handler {
}
}
- if (mActiveSync != null) {
- AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
+ if (mCurrentSync != null) {
+ AuthorityInfo ainfo = getAuthority(mCurrentSync.authorityId);
if (ainfo != null && ainfo.account.equals(account)
&& ainfo.authority.equals(authority)) {
return true;
@@ -491,10 +717,12 @@ public class SyncStorageEngine extends Handler {
public PendingOperation insertIntoPending(PendingOperation op) {
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "insertIntoPending: account=" + op.account
+ " auth=" + op.authority
+ " src=" + op.syncSource
+ " extras=" + op.extras);
+ }
AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
op.authority,
@@ -511,9 +739,6 @@ public class SyncStorageEngine extends Handler {
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = true;
- status.initialize = op.extras != null &&
- op.extras.containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE) &&
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
@@ -523,10 +748,12 @@ public class SyncStorageEngine extends Handler {
public boolean deleteFromPending(PendingOperation op) {
boolean res = false;
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "deleteFromPending: account=" + op.account
+ " auth=" + op.authority
+ " src=" + op.syncSource
+ " extras=" + op.extras);
+ }
if (mPendingOperations.remove(op)) {
if (mPendingOperations.size() == 0
|| mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
@@ -539,7 +766,7 @@ public class SyncStorageEngine extends Handler {
AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
"deleteFromPending");
if (authority != null) {
- if (DEBUG) Log.v(TAG, "removing - " + authority);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority);
final int N = mPendingOperations.size();
boolean morePending = false;
for (int i=0; i<N; i++) {
@@ -552,7 +779,7 @@ public class SyncStorageEngine extends Handler {
}
if (!morePending) {
- if (DEBUG) Log.v(TAG, "no more pending!");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "no more pending!");
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = false;
}
@@ -569,7 +796,9 @@ public class SyncStorageEngine extends Handler {
public int clearPending() {
int num;
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "clearPending");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "clearPending");
+ }
num = mPendingOperations.size();
mPendingOperations.clear();
final int N = mSyncStatus.size();
@@ -608,14 +837,16 @@ public class SyncStorageEngine extends Handler {
*/
public void doDatabaseCleanup(Account[] accounts) {
synchronized (mAuthorities) {
- if (DEBUG) Log.w(TAG, "Updating for new accounts...");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(TAG, "Updating for new accounts...");
SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
Iterator<AccountInfo> accIt = mAccounts.values().iterator();
while (accIt.hasNext()) {
AccountInfo acc = accIt.next();
if (!ArrayUtils.contains(accounts, acc.account)) {
// This account no longer exists...
- if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.w(TAG, "Account removed: " + acc.account);
+ }
for (AuthorityInfo auth : acc.authorities.values()) {
removing.put(auth.ident, auth);
}
@@ -661,12 +892,14 @@ public class SyncStorageEngine extends Handler {
public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
synchronized (mAuthorities) {
if (activeSyncContext != null) {
- if (DEBUG) Log.v(TAG, "setActiveSync: account="
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setActiveSync: account="
+ activeSyncContext.mSyncOperation.account
+ " auth=" + activeSyncContext.mSyncOperation.authority
+ " src=" + activeSyncContext.mSyncOperation.syncSource
+ " extras=" + activeSyncContext.mSyncOperation.extras);
- if (mActiveSync != null) {
+ }
+ if (mCurrentSync != null) {
Log.w(TAG, "setActiveSync called with existing active sync!");
}
AuthorityInfo authority = getAuthorityLocked(
@@ -676,12 +909,12 @@ public class SyncStorageEngine extends Handler {
if (authority == null) {
return;
}
- mActiveSync = new ActiveSyncInfo(authority.ident,
+ mCurrentSync = new SyncInfo(authority.ident,
authority.account, authority.authority,
activeSyncContext.mStartTime);
} else {
- if (DEBUG) Log.v(TAG, "setActiveSync: null");
- mActiveSync = null;
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "setActiveSync: null");
+ mCurrentSync = null;
}
}
@@ -702,8 +935,10 @@ public class SyncStorageEngine extends Handler {
long now, int source) {
long id;
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "insertStartSyncEvent: account=" + accountName
+ " auth=" + authorityName + " source=" + source);
+ }
AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
"insertStartSyncEvent");
if (authority == null) {
@@ -721,17 +956,37 @@ public class SyncStorageEngine extends Handler {
mSyncHistory.remove(mSyncHistory.size()-1);
}
id = item.historyId;
- if (DEBUG) Log.v(TAG, "returning historyId " + id);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "returning historyId " + id);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
return id;
}
+ public static boolean equals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ if (!b1.get(key).equals(b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
long downstreamActivity, long upstreamActivity) {
synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+ }
SyncHistoryItem item = null;
int i = mSyncHistory.size();
while (i > 0) {
@@ -771,6 +1026,9 @@ public class SyncStorageEngine extends Handler {
case SOURCE_SERVER:
status.numSourceServer++;
break;
+ case SOURCE_PERIODIC:
+ status.numSourcePeriodic++;
+ break;
}
boolean writeStatisticsNow = false;
@@ -836,9 +1094,9 @@ public class SyncStorageEngine extends Handler {
* active sync. Note that the returned object is the real, live active
* sync object, so be careful what you do with it.
*/
- public ActiveSyncInfo getActiveSync() {
+ public SyncInfo getCurrentSync() {
synchronized (mAuthorities) {
- return mActiveSync;
+ return mCurrentSync;
}
}
@@ -859,11 +1117,27 @@ public class SyncStorageEngine extends Handler {
}
/**
+ * Return an array of the current authorities. Note
+ * that the objects inside the array are the real, live objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<AuthorityInfo> getAuthorities() {
+ synchronized (mAuthorities) {
+ final int N = mAuthorities.size();
+ ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
+ for (int i=0; i<N; i++) {
+ infos.add(mAuthorities.valueAt(i));
+ }
+ return infos;
+ }
+ }
+
+ /**
* Returns the status that matches the authority and account.
*
* @param account the account we want to check
* @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority, or null if none exists
+ * @return the SyncStatusInfo for the authority
*/
public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
if (account == null || authority == null) {
@@ -1018,18 +1292,14 @@ public class SyncStorageEngine extends Handler {
AuthorityInfo authority = account.authorities.get(authorityName);
if (authority == null) {
if (ident < 0) {
- // Look for a new identifier for this authority.
- final int N = mAuthorities.size();
- ident = 0;
- for (int i=0; i<N; i++) {
- if (mAuthorities.valueAt(i).ident > ident) {
- break;
- }
- ident++;
- }
+ ident = mNextAuthorityId;
+ mNextAuthorityId++;
+ doWrite = true;
}
- if (DEBUG) Log.v(TAG, "created a new AuthorityInfo for " + accountName
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "created a new AuthorityInfo for " + accountName
+ ", provider " + authorityName);
+ }
authority = new AuthorityInfo(accountName, authorityName, ident);
account.authorities.put(authorityName, authority);
mAuthorities.put(ident, authority);
@@ -1041,6 +1311,25 @@ public class SyncStorageEngine extends Handler {
return authority;
}
+ private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) {
+ AccountInfo accountInfo = mAccounts.get(account);
+ if (accountInfo != null) {
+ final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
+ if (authorityInfo != null) {
+ mAuthorities.remove(authorityInfo.ident);
+ if (doWrite) {
+ writeAccountInfoLocked();
+ }
+ }
+ }
+ }
+
+ public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateSyncStatusLocked(authority.ident);
+ }
+ }
+
private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
SyncStatusInfo status = mSyncStatus.get(authorityId);
if (status == null) {
@@ -1066,9 +1355,33 @@ public class SyncStorageEngine extends Handler {
}
/**
+ * public for testing
+ */
+ public void clearAndReadState() {
+ synchronized (mAuthorities) {
+ mAuthorities.clear();
+ mAccounts.clear();
+ mPendingOperations.clear();
+ mSyncStatus.clear();
+ mSyncHistory.clear();
+
+ readAccountInfoLocked();
+ readStatusLocked();
+ readPendingOperationsLocked();
+ readStatisticsLocked();
+ readAndDeleteLegacyAccountInfoLocked();
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ writePendingOperationsLocked();
+ writeStatisticsLocked();
+ }
+ }
+
+ /**
* Read all account information back in to the initial engine state.
*/
private void readAccountInfoLocked() {
+ int highestAuthorityId = -1;
FileInputStream fis = null;
try {
fis = mAccountInfoFile.openRead();
@@ -1083,63 +1396,43 @@ public class SyncStorageEngine extends Handler {
if ("accounts".equals(tagName)) {
String listen = parser.getAttributeValue(
null, "listen-for-tickles");
- mMasterSyncAutomatically = listen == null
- || Boolean.parseBoolean(listen);
+ String versionString = parser.getAttributeValue(null, "version");
+ int version;
+ try {
+ version = (versionString == null) ? 0 : Integer.parseInt(versionString);
+ } catch (NumberFormatException e) {
+ version = 0;
+ }
+ String nextIdString = parser.getAttributeValue(null, "nextAuthorityId");
+ try {
+ int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
+ mNextAuthorityId = Math.max(mNextAuthorityId, id);
+ } catch (NumberFormatException e) {
+ // don't care
+ }
+ mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen);
eventType = parser.next();
+ AuthorityInfo authority = null;
+ Pair<Bundle, Long> periodicSync = null;
do {
- if (eventType == XmlPullParser.START_TAG
- && parser.getDepth() == 2) {
+ if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
- if ("authority".equals(tagName)) {
- int id = -1;
- try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
- } catch (NumberFormatException e) {
- } catch (NullPointerException e) {
- }
- if (id >= 0) {
- String accountName = parser.getAttributeValue(
- null, "account");
- String accountType = parser.getAttributeValue(
- null, "type");
- if (accountType == null) {
- accountType = "com.google";
- }
- String authorityName = parser.getAttributeValue(
- null, "authority");
- String enabled = parser.getAttributeValue(
- null, "enabled");
- String syncable = parser.getAttributeValue(null, "syncable");
- AuthorityInfo authority = mAuthorities.get(id);
- if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType),
- authorityName, id, false);
- }
- if (authority != null) {
- authority.enabled = enabled == null
- || Boolean.parseBoolean(enabled);
- if ("unknown".equals(syncable)) {
- authority.syncable = -1;
- } else {
- authority.syncable =
- (syncable == null || Boolean.parseBoolean(enabled))
- ? 1
- : 0;
- }
- } else {
- Log.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
+ if (parser.getDepth() == 2) {
+ if ("authority".equals(tagName)) {
+ authority = parseAuthority(parser, version);
+ periodicSync = null;
+ if (authority.ident > highestAuthorityId) {
+ highestAuthorityId = authority.ident;
}
}
+ } else if (parser.getDepth() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
+ }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
+ }
}
}
eventType = parser.next();
@@ -1147,10 +1440,13 @@ public class SyncStorageEngine extends Handler {
}
} catch (XmlPullParserException e) {
Log.w(TAG, "Error reading accounts", e);
+ return;
} catch (java.io.IOException e) {
if (fis == null) Log.i(TAG, "No initial accounts");
else Log.w(TAG, "Error reading accounts", e);
+ return;
} finally {
+ mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
if (fis != null) {
try {
fis.close();
@@ -1158,6 +1454,162 @@ public class SyncStorageEngine extends Handler {
}
}
}
+
+ maybeMigrateSettingsForRenamedAuthorities();
+ }
+
+ /**
+ * some authority names have changed. copy over their settings and delete the old ones
+ * @return true if a change was made
+ */
+ private boolean maybeMigrateSettingsForRenamedAuthorities() {
+ boolean writeNeeded = false;
+
+ ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
+ final int N = mAuthorities.size();
+ for (int i=0; i<N; i++) {
+ AuthorityInfo authority = mAuthorities.valueAt(i);
+ // skip this authority if it isn't one of the renamed ones
+ final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+ if (newAuthorityName == null) {
+ continue;
+ }
+
+ // remember this authority so we can remove it later. we can't remove it
+ // now without messing up this loop iteration
+ authoritiesToRemove.add(authority);
+
+ // this authority isn't enabled, no need to copy it to the new authority name since
+ // the default is "disabled"
+ if (!authority.enabled) {
+ continue;
+ }
+
+ // if we already have a record of this new authority then don't copy over the settings
+ if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) {
+ continue;
+ }
+
+ AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
+ newAuthorityName, -1 /* ident */, false /* doWrite */);
+ newAuthority.enabled = true;
+ writeNeeded = true;
+ }
+
+ for (AuthorityInfo authorityInfo : authoritiesToRemove) {
+ removeAuthorityLocked(authorityInfo.account, authorityInfo.authority,
+ false /* doWrite */);
+ writeNeeded = true;
+ }
+
+ return writeNeeded;
+ }
+
+ private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
+ AuthorityInfo authority = null;
+ int id = -1;
+ try {
+ id = Integer.parseInt(parser.getAttributeValue(
+ null, "id"));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the id of the authority", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the id of the authority is null", e);
+ }
+ if (id >= 0) {
+ String authorityName = parser.getAttributeValue(null, "authority");
+ String enabled = parser.getAttributeValue(null, "enabled");
+ String syncable = parser.getAttributeValue(null, "syncable");
+ String accountName = parser.getAttributeValue(null, "account");
+ String accountType = parser.getAttributeValue(null, "type");
+ if (accountType == null) {
+ accountType = "com.google";
+ syncable = "unknown";
+ }
+ authority = mAuthorities.get(id);
+ if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ if (authority == null) {
+ if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+ authority = getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), authorityName, id, false);
+ // If the version is 0 then we are upgrading from a file format that did not
+ // know about periodic syncs. In that case don't clear the list since we
+ // want the default, which is a daily periodioc sync.
+ // Otherwise clear out this default list since we will populate it later with
+ // the periodic sync descriptions that are read from the configuration file.
+ if (version > 0) {
+ authority.periodicSyncs.clear();
+ }
+ }
+ if (authority != null) {
+ authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+ if ("unknown".equals(syncable)) {
+ authority.syncable = -1;
+ } else {
+ authority.syncable =
+ (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
+ }
+ } else {
+ Log.w(TAG, "Failure adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ }
+ }
+
+ return authority;
+ }
+
+ private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle();
+ String periodValue = parser.getAttributeValue(null, "period");
+ final long period;
+ try {
+ period = Long.parseLong(periodValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the period of a periodic sync", e);
+ return null;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the period of a periodic sync is null", e);
+ return null;
+ }
+ final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ authority.periodicSyncs.add(periodicSync);
+
+ return periodicSync;
+ }
+
+ private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+ final Bundle extras = periodicSync.first;
+ String name = parser.getAttributeValue(null, "name");
+ String type = parser.getAttributeValue(null, "type");
+ String value1 = parser.getAttributeValue(null, "value1");
+ String value2 = parser.getAttributeValue(null, "value2");
+
+ try {
+ if ("long".equals(type)) {
+ extras.putLong(name, Long.parseLong(value1));
+ } else if ("integer".equals(type)) {
+ extras.putInt(name, Integer.parseInt(value1));
+ } else if ("double".equals(type)) {
+ extras.putDouble(name, Double.parseDouble(value1));
+ } else if ("float".equals(type)) {
+ extras.putFloat(name, Float.parseFloat(value1));
+ } else if ("boolean".equals(type)) {
+ extras.putBoolean(name, Boolean.parseBoolean(value1));
+ } else if ("string".equals(type)) {
+ extras.putString(name, value1);
+ } else if ("account".equals(type)) {
+ extras.putParcelable(name, new Account(value1, value2));
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ }
}
/**
@@ -1175,6 +1627,8 @@ public class SyncStorageEngine extends Handler {
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, "accounts");
+ out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
+ out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId));
if (!mMasterSyncAutomatically) {
out.attribute(null, "listen-for-tickles", "false");
}
@@ -1187,13 +1641,46 @@ public class SyncStorageEngine extends Handler {
out.attribute(null, "account", authority.account.name);
out.attribute(null, "type", authority.account.type);
out.attribute(null, "authority", authority.authority);
- if (!authority.enabled) {
- out.attribute(null, "enabled", "false");
- }
+ out.attribute(null, "enabled", Boolean.toString(authority.enabled));
if (authority.syncable < 0) {
out.attribute(null, "syncable", "unknown");
- } else if (authority.syncable == 0) {
- out.attribute(null, "syncable", "false");
+ } else {
+ out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
+ }
+ for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+ out.startTag(null, "periodicSync");
+ out.attribute(null, "period", Long.toString(periodicSync.second));
+ final Bundle extras = periodicSync.first;
+ for (String key : extras.keySet()) {
+ out.startTag(null, "extra");
+ out.attribute(null, "name", key);
+ final Object value = extras.get(key);
+ if (value instanceof Long) {
+ out.attribute(null, "type", "long");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Integer) {
+ out.attribute(null, "type", "integer");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Boolean) {
+ out.attribute(null, "type", "boolean");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Float) {
+ out.attribute(null, "type", "float");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Double) {
+ out.attribute(null, "type", "double");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof String) {
+ out.attribute(null, "type", "string");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Account) {
+ out.attribute(null, "type", "account");
+ out.attribute(null, "value1", ((Account)value).name);
+ out.attribute(null, "value2", ((Account)value).type);
+ }
+ out.endTag(null, "extra");
+ }
+ out.endTag(null, "periodicSync");
}
out.endTag(null, "authority");
}
@@ -1224,7 +1711,7 @@ public class SyncStorageEngine extends Handler {
* erase it. Note that we don't deal with pending operations, active
* sync, or history.
*/
- private void readLegacyAccountInfoLocked() {
+ private void readAndDeleteLegacyAccountInfoLocked() {
// Look for old database to initialize from.
File file = mContext.getDatabasePath("syncmanager.db");
if (!file.exists()) {
@@ -1300,6 +1787,7 @@ public class SyncStorageEngine extends Handler {
st.numSourcePoll = getIntColumn(c, "numSourcePoll");
st.numSourceServer = getIntColumn(c, "numSourceServer");
st.numSourceUser = getIntColumn(c, "numSourceUser");
+ st.numSourcePeriodic = 0;
st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
st.lastFailureSource = getIntColumn(c, "lastFailureSource");
@@ -1340,8 +1828,6 @@ public class SyncStorageEngine extends Handler {
db.close();
- writeAccountInfoLocked();
- writeStatusLocked();
(new File(path)).delete();
}
}
@@ -1413,7 +1899,7 @@ public class SyncStorageEngine extends Handler {
}
}
- public static final int PENDING_OPERATION_VERSION = 1;
+ public static final int PENDING_OPERATION_VERSION = 2;
/**
* Read all pending operations back in to the initial engine state.
@@ -1428,7 +1914,7 @@ public class SyncStorageEngine extends Handler {
final int SIZE = in.dataSize();
while (in.dataPosition() < SIZE) {
int version = in.readInt();
- if (version != PENDING_OPERATION_VERSION) {
+ if (version != PENDING_OPERATION_VERSION && version != 1) {
Log.w(TAG, "Unknown pending operation version "
+ version + "; dropping all ops");
break;
@@ -1436,6 +1922,12 @@ public class SyncStorageEngine extends Handler {
int authorityId = in.readInt();
int syncSource = in.readInt();
byte[] flatExtras = in.createByteArray();
+ boolean expedited;
+ if (version == PENDING_OPERATION_VERSION) {
+ expedited = in.readInt() != 0;
+ } else {
+ expedited = false;
+ }
AuthorityInfo authority = mAuthorities.get(authorityId);
if (authority != null) {
Bundle extras = null;
@@ -1444,12 +1936,13 @@ public class SyncStorageEngine extends Handler {
}
PendingOperation op = new PendingOperation(
authority.account, syncSource,
- authority.authority, extras);
+ authority.authority, extras, expedited);
op.authorityId = authorityId;
op.flatExtras = flatExtras;
if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
+ " auth=" + op.authority
+ " src=" + op.syncSource
+ + " expedited=" + op.expedited
+ " extras=" + op.extras);
mPendingOperations.add(op);
}
@@ -1467,6 +1960,7 @@ public class SyncStorageEngine extends Handler {
op.flatExtras = flattenBundle(op.extras);
}
out.writeByteArray(op.flatExtras);
+ out.writeInt(op.expedited ? 1 : 0);
}
/**
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
deleted file mode 100644
index ab4e91c..0000000
--- a/core/java/android/content/SyncableContentProvider.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.accounts.Account;
-
-import java.util.Map;
-
-/**
- * A specialization of the ContentProvider that centralizes functionality
- * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
- * inside of database transactions.
- *
- * @hide
- */
-public abstract class SyncableContentProvider extends ContentProvider {
- protected abstract boolean isTemporary();
-
- private volatile TempProviderSyncAdapter mTempProviderSyncAdapter;
-
- public void setTempProviderSyncAdapter(TempProviderSyncAdapter syncAdapter) {
- mTempProviderSyncAdapter = syncAdapter;
- }
-
- public TempProviderSyncAdapter getTempProviderSyncAdapter() {
- return mTempProviderSyncAdapter;
- }
-
- /**
- * Close resources that must be closed. You must call this to properly release
- * the resources used by the SyncableContentProvider.
- */
- public abstract void close();
-
- /**
- * Override to create your schema and do anything else you need to do with a new database.
- * This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected abstract void bootstrapDatabase(SQLiteDatabase db);
-
- /**
- * Override to upgrade your database from an old version to the version you specified.
- * Don't set the DB version, this will automatically be done after the method returns.
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- *
- * @param oldVersion version of the existing database
- * @param newVersion current version to upgrade to
- * @return true if the upgrade was lossless, false if it was lossy
- */
- protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
-
- /**
- * Override to do anything (like cleanups or checks) you need to do after opening a database.
- * Does nothing by default. This is run inside a transaction (so you don't need to use one).
- * This method may not use getDatabase(), or call content provider methods, it must only
- * use the database handle passed to it.
- */
- protected abstract void onDatabaseOpened(SQLiteDatabase db);
-
- /**
- * Get a non-persistent instance of this content provider.
- * You must call {@link #close} on the returned
- * SyncableContentProvider when you are done with it.
- *
- * @return a non-persistent content provider with the same layout as this
- * provider.
- */
- public abstract SyncableContentProvider getTemporaryInstance();
-
- public abstract SQLiteDatabase getDatabase();
-
- public abstract boolean getContainsDiffs();
-
- public abstract void setContainsDiffs(boolean containsDiffs);
-
- /**
- * Each subclass of this class should define a subclass of {@link
- * AbstractTableMerger} for each table they wish to merge. It
- * should then override this method and return one instance of
- * each merger, in sequence. Their {@link
- * AbstractTableMerger#merge merge} methods will be called, one at a
- * time, in the order supplied.
- *
- * <p>The default implementation returns an empty list, so that no
- * merging will occur.
- * @return A sequence of subclasses of {@link
- * AbstractTableMerger}, one for each table that should be merged.
- */
- protected abstract Iterable<? extends AbstractTableMerger> getMergers();
-
- /**
- * Check if changes to this URI can be syncable changes.
- * @param uri the URI of the resource that was changed
- * @return true if changes to this URI can be syncable changes, false otherwise
- */
- public abstract boolean changeRequiresLocalSync(Uri uri);
-
- /**
- * Called right before a sync is started.
- *
- * @param context the sync context for the operation
- * @param account
- */
- public abstract void onSyncStart(SyncContext context, Account account);
-
- /**
- * Called right after a sync is completed
- *
- * @param context the sync context for the operation
- * @param success true if the sync succeeded, false if an error occurred
- */
- public abstract void onSyncStop(SyncContext context, boolean success);
-
- /**
- * The account of the most recent call to onSyncStart()
- * @return the account
- */
- public abstract Account getSyncingAccount();
-
- /**
- * Merge diffs from a sync source with this content provider.
- *
- * @param context the SyncContext within which this merge is taking place
- * @param diffs A temporary content provider containing diffs from a sync
- * source.
- * @param result a MergeResult that contains information about the merge, including
- * a temporary content provider with the same layout as this provider containing
- * @param syncResult
- */
- public abstract void merge(SyncContext context, SyncableContentProvider diffs,
- TempProviderSyncResult result, SyncResult syncResult);
-
-
- /**
- * Invoked when the active sync has been canceled. The default
- * implementation doesn't do anything (except ensure that this
- * provider is syncable). Subclasses of ContentProvider
- * that support canceling of sync should override this.
- */
- public abstract void onSyncCanceled();
-
-
- public abstract boolean isMergeCancelled();
-
- /**
- * Subclasses should override this instead of update(). See update()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int updateInternal(Uri url, ContentValues values,
- String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of delete(). See delete()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
-
- /**
- * Subclasses should override this instead of insert(). See insert()
- * for details.
- *
- * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
- * which means a database transaction will be active during the call;
- */
- protected abstract Uri insertInternal(Uri url, ContentValues values);
-
- /**
- * Subclasses should override this instead of query(). See query()
- * for details.
- *
- * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
- * block for performance reasons. If an implementation needs atomic access
- * to the database the lock can be acquired then.
- */
- protected abstract Cursor queryInternal(Uri url, String[] projection,
- String selection, String[] selectionArgs, String sortOrder);
-
- /**
- * Make sure that there are no entries for accounts that no longer exist
- * @param accountsArray the array of currently-existing accounts
- */
- protected abstract void onAccountsChanged(Account[] accountsArray);
-
- /**
- * A helper method to delete all rows whose account is not in the accounts
- * map. The accountColumnName is the name of the column that is expected
- * to hold the account. If a row has an empty account it is never deleted.
- *
- * @param accounts a map of existing accounts
- * @param table the table to delete from
- */
- protected abstract void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts,
- String table);
-
- /**
- * Called when the sync system determines that this provider should no longer
- * contain records for the specified account.
- */
- public abstract void wipeAccount(Account account);
-
- /**
- * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
- */
- public abstract byte[] readSyncDataBytes(Account account);
-
- /**
- * Sets the SyncData bytes for the given account. The bytes array may be null.
- */
- public abstract void writeSyncDataBytes(Account account, byte[] data);
-}
-
diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java
deleted file mode 100644
index b46c545..0000000
--- a/core/java/android/content/TempProviderSyncAdapter.java
+++ /dev/null
@@ -1,585 +0,0 @@
-package android.content;
-
-import android.database.SQLException;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.NetStat;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.TimingLogger;
-import android.accounts.Account;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-
-import java.io.IOException;
-
-/**
- * @hide
- */
-public abstract class TempProviderSyncAdapter extends SyncAdapter {
- private static final String TAG = "Sync";
-
- private static final int MAX_GET_SERVER_DIFFS_LOOP_COUNT = 20;
- private static final int MAX_UPLOAD_CHANGES_LOOP_COUNT = 10;
- private static final int NUM_ALLOWED_SIMULTANEOUS_DELETIONS = 5;
- private static final long PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS = 20;
-
- private volatile SyncableContentProvider mProvider;
- private volatile SyncThread mSyncThread = null;
- private volatile boolean mProviderSyncStarted;
- private volatile boolean mAdapterSyncStarted;
-
- public TempProviderSyncAdapter(SyncableContentProvider provider) {
- super();
- mProvider = provider;
- }
-
- /**
- * Used by getServerDiffs() to track the sync progress for a given
- * sync adapter. Implementations of SyncAdapter generally specialize
- * this class in order to track specific data about that SyncAdapter's
- * sync. If an implementation of SyncAdapter doesn't need to store
- * any data for a sync it may use TrivialSyncData.
- */
- public static abstract class SyncData implements Parcelable {
-
- }
-
- public final void setContext(Context context) {
- mContext = context;
- }
-
- /**
- * Retrieve the Context this adapter is running in. Only available
- * once onSyncStarting() is called (not available from constructor).
- */
- final public Context getContext() {
- return mContext;
- }
-
- /**
- * Called right before a sync is started.
- *
- * @param context allows you to publish status and interact with the
- * @param account the account to sync
- * @param manualSync true if this sync was requested manually by the user
- * @param result information to track what happened during this sync attempt
- */
- public abstract void onSyncStarting(SyncContext context, Account account, boolean manualSync,
- SyncResult result);
-
- /**
- * Called right after a sync is completed
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param success true if the sync suceeded, false if an error occured
- */
- public abstract void onSyncEnding(SyncContext context, boolean success);
-
- /**
- * Implement this to return true if the data in your content provider
- * is read only.
- */
- public abstract boolean isReadOnly();
-
- public abstract boolean getIsSyncable(Account account)
- throws IOException, AuthenticatorException, OperationCanceledException;
-
- /**
- * Get diffs from the server since the last completed sync and put them
- * into a temporary provider.
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param syncData used to track the progress this client has made in syncing data
- * from the server
- * @param tempProvider this is where the diffs should be stored
- * @param extras any extra data describing the sync that is desired
- * @param syncInfo sync adapter-specific data that is used during a single sync operation
- * @param syncResult information to track what happened during this sync attempt
- */
- public abstract void getServerDiffs(SyncContext context,
- SyncData syncData, SyncableContentProvider tempProvider,
- Bundle extras, Object syncInfo, SyncResult syncResult);
-
- /**
- * Send client diffs to the server, optionally receiving more diffs from the server
- *
- * @param context allows you to publish status and interact with the
- * user during interactive syncs.
- * @param clientDiffs the diffs from the client
- * @param serverDiffs the SyncableContentProvider that should be populated with
-* the entries that were returned in response to an insert/update/delete request
-* to the server
- * @param syncResult information to track what happened during this sync attempt
- * @param dontActuallySendDeletes
- */
- public abstract void sendClientDiffs(SyncContext context,
- SyncableContentProvider clientDiffs,
- SyncableContentProvider serverDiffs, SyncResult syncResult,
- boolean dontActuallySendDeletes);
-
- /**
- * Reads the sync data from the ContentProvider
- * @param contentProvider the ContentProvider to read from
- * @return the SyncData for the provider. This may be null.
- */
- public SyncData readSyncData(SyncableContentProvider contentProvider) {
- return null;
- }
-
- /**
- * Create and return a new, empty SyncData object
- */
- public SyncData newSyncData() {
- return null;
- }
-
- /**
- * Stores the sync data in the Sync Stats database, keying it by
- * the account that was set in the last call to onSyncStarting()
- */
- public void writeSyncData(SyncData syncData, SyncableContentProvider contentProvider) {}
-
- /**
- * Indicate to the SyncAdapter that the last sync that was started has
- * been cancelled.
- */
- public abstract void onSyncCanceled();
-
- /**
- * Initializes the temporary content providers used during
- * {@link TempProviderSyncAdapter#sendClientDiffs}.
- * May copy relevant data from the underlying db into this provider so
- * joins, etc., can work.
- *
- * @param cp The ContentProvider to initialize.
- */
- protected void initTempProvider(SyncableContentProvider cp) {}
-
- protected Object createSyncInfo() {
- return null;
- }
-
- /**
- * Called when the accounts list possibly changed, to give the
- * SyncAdapter a chance to do any necessary bookkeeping, e.g.
- * to make sure that any required SubscribedFeeds subscriptions
- * exist.
- * @param accounts the list of accounts
- */
- public abstract void onAccountsChanged(Account[] accounts);
-
- private Context mContext;
-
- private class SyncThread extends Thread {
- private final Account mAccount;
- private final String mAuthority;
- private final Bundle mExtras;
- private final SyncContext mSyncContext;
- private volatile boolean mIsCanceled = false;
- private long mInitialTxBytes;
- private long mInitialRxBytes;
- private final SyncResult mResult;
-
- SyncThread(SyncContext syncContext, Account account, String authority, Bundle extras) {
- super("SyncThread");
- mAccount = account;
- mAuthority = authority;
- mExtras = extras;
- mSyncContext = syncContext;
- mResult = new SyncResult();
- }
-
- void cancelSync() {
- mIsCanceled = true;
- if (mAdapterSyncStarted) onSyncCanceled();
- if (mProviderSyncStarted) mProvider.onSyncCanceled();
- // We may lose the last few sync events when canceling. Oh well.
- int uid = Process.myUid();
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.myTid(),
- Process.THREAD_PRIORITY_BACKGROUND);
- int uid = Process.myUid();
- mInitialTxBytes = NetStat.getUidTxBytes(uid);
- mInitialRxBytes = NetStat.getUidRxBytes(uid);
- try {
- sync(mSyncContext, mAccount, mAuthority, mExtras);
- } catch (SQLException e) {
- Log.e(TAG, "Sync failed", e);
- mResult.databaseError = true;
- } finally {
- mSyncThread = null;
- if (!mIsCanceled) {
- logSyncDetails(NetStat.getUidTxBytes(uid) - mInitialTxBytes,
- NetStat.getUidRxBytes(uid) - mInitialRxBytes, mResult);
- mSyncContext.onFinished(mResult);
- }
- }
- }
-
- private void sync(SyncContext syncContext, Account account, String authority,
- Bundle extras) {
- mIsCanceled = false;
-
- mProviderSyncStarted = false;
- mAdapterSyncStarted = false;
- String message = null;
-
- // always attempt to initialize if the isSyncable state isn't set yet
- int isSyncable = ContentResolver.getIsSyncable(account, authority);
- if (isSyncable < 0) {
- try {
- isSyncable = (getIsSyncable(account)) ? 1 : 0;
- ContentResolver.setIsSyncable(account, authority, isSyncable);
- } catch (IOException e) {
- ++mResult.stats.numIoExceptions;
- } catch (AuthenticatorException e) {
- ++mResult.stats.numParseExceptions;
- } catch (OperationCanceledException e) {
- // do nothing
- }
- }
-
- // if this is an initialization request then our work is done here
- if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
- return;
- }
-
- // if we aren't syncable then get out
- if (isSyncable <= 0) {
- return;
- }
-
- boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
-
- try {
- mProvider.onSyncStart(syncContext, account);
- mProviderSyncStarted = true;
- onSyncStarting(syncContext, account, manualSync, mResult);
- if (mResult.hasError()) {
- message = "SyncAdapter failed while trying to start sync";
- return;
- }
- mAdapterSyncStarted = true;
- if (mIsCanceled) {
- return;
- }
- final String syncTracingEnabledValue = SystemProperties.get(TAG + "Tracing");
- final boolean syncTracingEnabled = !TextUtils.isEmpty(syncTracingEnabledValue);
- try {
- if (syncTracingEnabled) {
- System.gc();
- System.gc();
- Debug.startMethodTracing("synctrace." + System.currentTimeMillis());
- }
- runSyncLoop(syncContext, account, extras);
- } finally {
- if (syncTracingEnabled) Debug.stopMethodTracing();
- }
- onSyncEnding(syncContext, !mResult.hasError());
- mAdapterSyncStarted = false;
- mProvider.onSyncStop(syncContext, true);
- mProviderSyncStarted = false;
- } finally {
- if (mAdapterSyncStarted) {
- mAdapterSyncStarted = false;
- onSyncEnding(syncContext, false);
- }
- if (mProviderSyncStarted) {
- mProviderSyncStarted = false;
- mProvider.onSyncStop(syncContext, false);
- }
- if (!mIsCanceled) {
- if (message != null) syncContext.setStatusText(message);
- }
- }
- }
-
- private void runSyncLoop(SyncContext syncContext, Account account, Bundle extras) {
- TimingLogger syncTimer = new TimingLogger(TAG + "Profiling", "sync");
- syncTimer.addSplit("start");
- int loopCount = 0;
- boolean tooManyGetServerDiffsAttempts = false;
-
- final boolean overrideTooManyDeletions =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS,
- false);
- final boolean discardLocalDeletions =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS, false);
- boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD,
- false /* default this flag to false */);
- SyncableContentProvider serverDiffs = null;
- TempProviderSyncResult result = new TempProviderSyncResult();
- try {
- if (!uploadOnly) {
- /**
- * This loop repeatedly calls SyncAdapter.getServerDiffs()
- * (to get changes from the feed) followed by
- * ContentProvider.merge() (to incorporate these changes
- * into the provider), stopping when the SyncData returned
- * from getServerDiffs() indicates that all the data was
- * fetched.
- */
- while (!mIsCanceled) {
- // Don't let a bad sync go forever
- if (loopCount++ == MAX_GET_SERVER_DIFFS_LOOP_COUNT) {
- Log.e(TAG, "runSyncLoop: Hit max loop count while getting server diffs "
- + getClass().getName());
- // TODO: change the structure here to schedule a new sync
- // with a backoff time, keeping track to be sure
- // we don't keep doing this forever (due to some bug or
- // mismatch between the client and the server)
- tooManyGetServerDiffsAttempts = true;
- break;
- }
-
- // Get an empty content provider to put the diffs into
- if (serverDiffs != null) serverDiffs.close();
- serverDiffs = mProvider.getTemporaryInstance();
-
- // Get records from the server which will be put into the serverDiffs
- initTempProvider(serverDiffs);
- Object syncInfo = createSyncInfo();
- SyncData syncData = readSyncData(serverDiffs);
- // syncData will only be null if there was a demarshalling error
- // while reading the sync data.
- if (syncData == null) {
- mProvider.wipeAccount(account);
- syncData = newSyncData();
- }
- mResult.clear();
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: running getServerDiffs using syncData "
- + syncData.toString());
- }
- getServerDiffs(syncContext, syncData, serverDiffs, extras, syncInfo,
- mResult);
-
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
- if (mResult.hasError()) return;
- if (mResult.partialSyncUnavailable) {
- if (Config.LOGD) {
- Log.d(TAG, "partialSyncUnavailable is set, setting "
- + "ignoreSyncData and retrying");
- }
- mProvider.wipeAccount(account);
- continue;
- }
-
- // write the updated syncData back into the temp provider
- writeSyncData(syncData, serverDiffs);
-
- // apply the downloaded changes to the provider
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: running merge");
- }
- mProvider.merge(syncContext, serverDiffs,
- null /* don't return client diffs */, mResult);
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- // if the server has no more changes then break out of the loop
- if (!mResult.moreRecordsToGet) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: fetched all data, moving on");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: more data to fetch, looping");
- }
- }
- }
-
- /**
- * This loop repeatedly calls ContentProvider.merge() followed
- * by SyncAdapter.merge() until either indicate that there is
- * no more work to do by returning null.
- * <p>
- * The initial ContentProvider.merge() returns a temporary
- * ContentProvider that contains any local changes that need
- * to be committed to the server.
- * <p>
- * The SyncAdapter.merge() calls upload the changes to the server
- * and populates temporary provider (the serverDiffs) with the
- * result.
- * <p>
- * Subsequent calls to ContentProvider.merge() incoporate the
- * result of previous SyncAdapter.merge() calls into the
- * real ContentProvider and again return a temporary
- * ContentProvider that contains any local changes that need
- * to be committed to the server.
- */
- loopCount = 0;
- boolean readOnly = isReadOnly();
- long previousNumModifications = 0;
- if (serverDiffs != null) {
- serverDiffs.close();
- serverDiffs = null;
- }
-
- // If we are discarding local deletions then we need to redownload all the items
- // again (since some of them might have been deleted). We do this by deleting the
- // sync data for the current account by writing in a null one.
- if (discardLocalDeletions) {
- serverDiffs = mProvider.getTemporaryInstance();
- initTempProvider(serverDiffs);
- writeSyncData(null, serverDiffs);
- }
-
- while (!mIsCanceled) {
- if (Config.LOGV) {
- Log.v(TAG, "runSyncLoop: Merging diffs from server to client");
- }
- if (result.tempContentProvider != null) {
- result.tempContentProvider.close();
- result.tempContentProvider = null;
- }
- mResult.clear();
- mProvider.merge(syncContext, serverDiffs, readOnly ? null : result,
- mResult);
- if (mIsCanceled) return;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- SyncableContentProvider clientDiffs =
- readOnly ? null : result.tempContentProvider;
- if (clientDiffs == null) {
- // Nothing to commit back to the server
- if (Config.LOGV) Log.v(TAG, "runSyncLoop: No client diffs");
- break;
- }
-
- long numModifications = mResult.stats.numUpdates
- + mResult.stats.numDeletes
- + mResult.stats.numInserts;
-
- // as long as we are making progress keep resetting the loop count
- if (numModifications < previousNumModifications) {
- loopCount = 0;
- }
- previousNumModifications = numModifications;
-
- // Don't let a bad sync go forever
- if (loopCount++ >= MAX_UPLOAD_CHANGES_LOOP_COUNT) {
- Log.e(TAG, "runSyncLoop: Hit max loop count while syncing "
- + getClass().getName());
- mResult.tooManyRetries = true;
- break;
- }
-
- if (!overrideTooManyDeletions && !discardLocalDeletions
- && hasTooManyDeletions(mResult.stats)) {
- if (Config.LOGD) {
- Log.d(TAG, "runSyncLoop: Too many deletions were found in provider "
- + getClass().getName() + ", not doing any more updates");
- }
- long numDeletes = mResult.stats.numDeletes;
- mResult.stats.clear();
- mResult.tooManyDeletions = true;
- mResult.stats.numDeletes = numDeletes;
- break;
- }
-
- if (Config.LOGV) Log.v(TAG, "runSyncLoop: Merging diffs from client to server");
- if (serverDiffs != null) serverDiffs.close();
- serverDiffs = clientDiffs.getTemporaryInstance();
- initTempProvider(serverDiffs);
- mResult.clear();
- sendClientDiffs(syncContext, clientDiffs, serverDiffs, mResult,
- discardLocalDeletions);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: result: " + mResult);
- }
-
- if (!mResult.madeSomeProgress()) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: No data from client diffs merge");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: made some progress, looping");
- }
- }
-
- // add in any status codes that we saved from earlier
- mResult.tooManyRetries |= tooManyGetServerDiffsAttempts;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "runSyncLoop: final result: " + mResult);
- }
- } finally {
- // do this in the finally block to guarantee that is is set and not overwritten
- if (discardLocalDeletions) {
- mResult.fullSyncRequested = true;
- }
- if (serverDiffs != null) serverDiffs.close();
- if (result.tempContentProvider != null) result.tempContentProvider.close();
- syncTimer.addSplit("stop");
- syncTimer.dumpToLog();
- }
- }
- }
-
- /**
- * Logs details on the sync.
- * Normally this will be overridden by a subclass that will provide
- * provider-specific details.
- *
- * @param bytesSent number of bytes the sync sent over the network
- * @param bytesReceived number of bytes the sync received over the network
- * @param result The SyncResult object holding info on the sync
- */
- protected void logSyncDetails(long bytesSent, long bytesReceived, SyncResult result) {
- EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
- }
-
- public void startSync(SyncContext syncContext, Account account, String authority,
- Bundle extras) {
- if (mSyncThread != null) {
- syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
- return;
- }
-
- mSyncThread = new SyncThread(syncContext, account, authority, extras);
- mSyncThread.start();
- }
-
- public void cancelSync() {
- if (mSyncThread != null) {
- mSyncThread.cancelSync();
- }
- }
-
- protected boolean hasTooManyDeletions(SyncStats stats) {
- long numEntries = stats.numEntries;
- long numDeletedEntries = stats.numDeletes;
-
- long percentDeleted = (numDeletedEntries == 0)
- ? 0
- : (100 * numDeletedEntries /
- (numEntries + numDeletedEntries));
- boolean tooManyDeletions =
- (numDeletedEntries > NUM_ALLOWED_SIMULTANEOUS_DELETIONS)
- && (percentDeleted > PERCENT_ALLOWED_SIMULTANEOUS_DELETIONS);
- return tooManyDeletions;
- }
-}
diff --git a/core/java/android/content/package.html b/core/java/android/content/package.html
index dd5360f..eac679d 100644
--- a/core/java/android/content/package.html
+++ b/core/java/android/content/package.html
@@ -421,7 +421,7 @@ can supply them explicitly in the XML file:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textSize="18" android:textColor="#008"</b>
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -447,7 +447,7 @@ one of those resources:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textColor="@color/opaque_red"</b>
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -463,7 +463,7 @@ reference a system resource, you would need to write:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
android:textColor="@<b>android:</b>color/opaque_red"
android:text="Hello, World!" /&gt;
&lt;/root&gt;
@@ -476,7 +476,7 @@ strings in a layout file so that they can be localized:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
android:textColor="@android:color/opaque_red"
android:text="@string/hello_world" /&gt;
&lt;/root&gt;
@@ -509,7 +509,7 @@ one of the standard colors defined in the base system theme:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text"
- android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent"
<b>android:textColor="?android:textDisabledColor"</b>
android:text="@string/hello_world" /&gt;
&lt;/root&gt;
@@ -637,10 +637,10 @@ new style resource with the desired values:</p>
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;root&gt;
&lt;EditText id="text1" <b>style="@style/SpecialText"</b>
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:text="Hello, World!" /&gt;
&lt;EditText id="text2" <b>style="@style/SpecialText"</b>
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:text="I love you all." /&gt;
&lt;/root&gt;</pre>
<h4>&nbsp;</h4>
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index b94bb51..238b840 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
@@ -251,6 +267,12 @@ public class ActivityInfo extends ComponentInfo
public static final int CONFIG_SCREEN_LAYOUT = 0x0100;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the ui mode. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_UI_MODE = 0x0200;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the font scaling factor. Set from the
* {@link android.R.attr#configChanges} attribute. This is
* not a core resource configutation, but a higher-level value, so its
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1800c30..1577f9e 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1,5 +1,24 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Printer;
@@ -68,8 +87,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* will be null if the application does not specify it in its manifest.
*
* <p>If android:allowBackup is set to false, this attribute is ignored.
- *
- * {@hide}
*/
public String backupAgentName;
@@ -178,34 +195,87 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 1<<13;
/**
- * Value for {@link #flags}: this is false if the application has set
- * its android:allowBackup to false, true otherwise.
+ * Value for {@link #flags}: set to true if this application would like to
+ * request the VM to operate under the safe mode. Comes from
+ * {@link android.R.styleable#AndroidManifestApplication_vmSafeMode
+ * android:vmSafeMode} of the &lt;application&gt; tag.
+ */
+ public static final int FLAG_VM_SAFE_MODE = 1<<14;
+
+ /**
+ * Value for {@link #flags}: set to <code>false</code> if the application does not wish
+ * to permit any OS-driven backups of its data; <code>true</code> otherwise.
*
- * {@hide}
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * attribute of the &lt;application&gt; tag.
*/
- public static final int FLAG_ALLOW_BACKUP = 1<<14;
+ public static final int FLAG_ALLOW_BACKUP = 1<<15;
/**
- * Value for {@link #flags}: this is false if the application has set
- * its android:killAfterRestore to false, true otherwise.
+ * Value for {@link #flags}: set to <code>false</code> if the application must be kept
+ * in memory following a full-system restore operation; <code>true</code> otherwise.
+ * Ordinarily, during a full system restore operation each application is shut down
+ * following execution of its agent's onRestore() method. Setting this attribute to
+ * <code>false</code> prevents this. Most applications will not need to set this attribute.
*
- * <p>If android:allowBackup is set to false or no android:backupAgent
+ * <p>If
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * is set to <code>false</code> or no
+ * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent}
* is specified, this flag will be ignored.
*
- * {@hide}
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_killAfterRestore android:killAfterRestore}
+ * attribute of the &lt;application&gt; tag.
*/
- public static final int FLAG_KILL_AFTER_RESTORE = 1<<15;
+ public static final int FLAG_KILL_AFTER_RESTORE = 1<<16;
/**
- * Value for {@link #flags}: this is true if the application has set
- * its android:restoreNeedsApplication to true, false otherwise.
+ * Value for {@link #flags}: Set to <code>true</code> if the application's backup
+ * agent claims to be able to handle restore data even "from the future,"
+ * i.e. from versions of the application with a versionCode greater than
+ * the one currently installed on the device. <i>Use with caution!</i> By default
+ * this attribute is <code>false</code> and the Backup Manager will ensure that data
+ * from "future" versions of the application are never supplied during a restore operation.
*
- * <p>If android:allowBackup is set to false or no android:backupAgent
+ * <p>If
+ * {@link android.R.styleable#AndroidManifestApplication_allowBackup android:allowBackup}
+ * is set to <code>false</code> or no
+ * {@link android.R.styleable#AndroidManifestApplication_backupAgent android:backupAgent}
* is specified, this flag will be ignored.
*
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_restoreAnyVersion android:restoreAnyVersion}
+ * attribute of the &lt;application&gt; tag.
+ */
+ public static final int FLAG_RESTORE_ANY_VERSION = 1<<17;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application is
+ * currently installed on external/removable/unprotected storage. Such
+ * applications may not be available if their storage is not currently
+ * mounted. When the storage it is on is not available, it will look like
+ * the application has been uninstalled (its .apk is no longer available)
+ * but its persistent data is not removed.
+ */
+ public static final int FLAG_EXTERNAL_STORAGE = 1<<18;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application has been
+ * installed using the forward lock option.
+ *
* {@hide}
*/
- public static final int FLAG_RESTORE_NEEDS_APPLICATION = 1<<16;
+ public static final int FLAG_FORWARD_LOCK = 1<<20;
+
+ /**
+ * Value for {@link #flags}: Set to true if the application is
+ * native-debuggable, i.e. embeds a gdbserver binary in its .apk
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21;
/**
* Flags associated with the application. Any combination of
@@ -216,7 +286,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
* {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS},
- * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}.
+ * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}
*/
public int flags = 0;
@@ -226,12 +296,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public String sourceDir;
/**
- * Full path to the location of the publicly available parts of this package (i.e. the resources
- * and manifest). For non-forward-locked apps this will be the same as {@link #sourceDir).
+ * Full path to the location of the publicly available parts of this
+ * package (i.e. the primary resource package and manifest). For
+ * non-forward-locked apps this will be the same as {@link #sourceDir).
*/
public String publicSourceDir;
/**
+ * Full paths to the locations of extra resource packages this application
+ * uses. This field is only used if there are extra resource packages,
+ * otherwise it is null.
+ *
+ * {@hide}
+ */
+ public String[] resourceDirs;
+
+ /**
* Paths to all shared libraries this application is linked against. This
* field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
* PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
@@ -284,6 +364,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+ " processName=" + processName);
pw.println(prefix + "sourceDir=" + sourceDir);
pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ pw.println(prefix + "resourceDirs=" + resourceDirs);
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
@@ -334,6 +415,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
flags = orig.flags;
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
+ resourceDirs = orig.resourceDirs;
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
uid = orig.uid;
@@ -364,6 +446,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(flags);
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
+ dest.writeStringArray(resourceDirs);
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeInt(uid);
@@ -394,6 +477,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
flags = source.readInt();
sourceDir = source.readString();
publicSourceDir = source.readString();
+ resourceDirs = source.readStringArray();
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
uid = source.readInt();
@@ -417,7 +501,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public CharSequence loadDescription(PackageManager pm) {
if (descriptionRes != 0) {
- CharSequence label = pm.getText(packageName, descriptionRes, null);
+ CharSequence label = pm.getText(packageName, descriptionRes, this);
if (label != null) {
return label;
}
@@ -435,4 +519,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS |
FLAG_SUPPORTS_SCREEN_DENSITIES);
}
+
+ /**
+ * @hide
+ */
+ @Override protected Drawable loadDefaultIcon(PackageManager pm) {
+ if ((flags & FLAG_EXTERNAL_STORAGE) != 0
+ && isPackageUnavailable(pm)) {
+ return Resources.getSystem().getDrawable(
+ com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
+ }
+ return pm.getDefaultActivityIcon();
+ }
+
+ private boolean isPackageUnavailable(PackageManager pm) {
+ try {
+ return pm.getPackageInfo(packageName, 0) == null;
+ } catch (NameNotFoundException ex) {
+ return true;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected ApplicationInfo getApplicationInfo() {
+ return this;
+ }
}
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 73c9244..cafe372 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.graphics.drawable.Drawable;
@@ -27,6 +43,13 @@ public class ComponentInfo extends PackageItemInfo {
public String processName;
/**
+ * A string resource identifier (in the package's resources) containing
+ * a user-readable description of the component. From the "description"
+ * attribute or, if not set, 0.
+ */
+ public int descriptionRes;
+
+ /**
* Indicates whether or not this component may be instantiated. Note that this value can be
* overriden by the one in its parent {@link ApplicationInfo}.
*/
@@ -47,6 +70,7 @@ public class ComponentInfo extends PackageItemInfo {
super(orig);
applicationInfo = orig.applicationInfo;
processName = orig.processName;
+ descriptionRes = orig.descriptionRes;
enabled = orig.enabled;
exported = orig.exported;
}
@@ -75,24 +99,6 @@ public class ComponentInfo extends PackageItemInfo {
return name;
}
- @Override public Drawable loadIcon(PackageManager pm) {
- ApplicationInfo ai = applicationInfo;
- Drawable dr;
- if (icon != 0) {
- dr = pm.getDrawable(packageName, icon, ai);
- if (dr != null) {
- return dr;
- }
- }
- if (ai.icon != 0) {
- dr = pm.getDrawable(packageName, ai.icon, ai);
- if (dr != null) {
- return dr;
- }
- }
- return pm.getDefaultActivityIcon();
- }
-
/**
* Return the icon resource identifier to use for this component. If
* the component defines an icon, that is used; else, the application
@@ -108,6 +114,9 @@ public class ComponentInfo extends PackageItemInfo {
super.dumpFront(pw, prefix);
pw.println(prefix + "enabled=" + enabled + " exported=" + exported
+ " processName=" + processName);
+ if (descriptionRes != 0) {
+ pw.println(prefix + "description=" + descriptionRes);
+ }
}
protected void dumpBack(Printer pw, String prefix) {
@@ -124,15 +133,31 @@ public class ComponentInfo extends PackageItemInfo {
super.writeToParcel(dest, parcelableFlags);
applicationInfo.writeToParcel(dest, parcelableFlags);
dest.writeString(processName);
+ dest.writeInt(descriptionRes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(exported ? 1 : 0);
}
-
+
protected ComponentInfo(Parcel source) {
super(source);
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
processName = source.readString();
+ descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
}
+
+ /**
+ * @hide
+ */
+ @Override protected Drawable loadDefaultIcon(PackageManager pm) {
+ return applicationInfo.loadIcon(pm);
+ }
+
+ /**
+ * @hide
+ */
+ @Override protected ApplicationInfo getApplicationInfo() {
+ return applicationInfo;
+ }
}
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index 57d61fd..89394f9 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fc6538f..9939478 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,6 +26,7 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
@@ -47,6 +48,9 @@ interface IPackageManager {
PackageInfo getPackageInfo(String packageName, int flags);
int getPackageUid(String packageName);
int[] getPackageGids(String packageName);
+
+ String[] currentToCanonicalPackageNames(in String[] names);
+ String[] canonicalToCurrentPackageNames(in String[] names);
PermissionInfo getPermissionInfo(String name, int flags);
@@ -150,6 +154,8 @@ interface IPackageManager {
void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags,
in String installerPackageName);
+ void finishPackageInstall(int token);
+
/**
* Delete a package.
*
@@ -296,4 +302,21 @@ interface IPackageManager {
* in the special development "no pre-dexopt" mode.
*/
boolean performDexOpt(String packageName);
+
+ /**
+ * Update status of external media on the package manager to scan and
+ * install packages installed on the external media. Like say the
+ * MountService uses this to call into the package manager to update
+ * status of sdcard.
+ */
+ void updateExternalMediaStatus(boolean mounted, boolean reportStatus);
+
+ String nextPackageToClean(String lastPackage);
+
+ void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+
+ boolean addPermissionAsync(in PermissionInfo info);
+
+ boolean setInstallLocation(int loc);
+ int getInstallLocation();
}
diff --git a/core/res/res/drawable/btn_application_selector.xml b/core/java/android/content/pm/IPackageMoveObserver.aidl
index 5575b85..baa1595 100644
--- a/core/res/res/drawable/btn_application_selector.xml
+++ b/core/java/android/content/pm/IPackageMoveObserver.aidl
@@ -1,5 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
/*
**
** Copyright 2007, The Android Open Source Project
@@ -16,11 +14,14 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/pressed_application_background_static" />
- <item android:state_focused="true"
- android:drawable="@drawable/focused_application_background_static" />
-</selector>
+package android.content.pm;
+
+/**
+ * Callback for moving package resources from the Package Manager.
+ * @hide
+ */
+oneway interface IPackageMoveObserver {
+ void packageMoved(in String packageName, int returnCode);
+}
+
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 30ca002..3e868a7 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
diff --git a/core/java/android/content/pm/LabeledIntent.java b/core/java/android/content/pm/LabeledIntent.java
index d70a698..68b0046 100644
--- a/core/java/android/content/pm/LabeledIntent.java
+++ b/core/java/android/content/pm/LabeledIntent.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.content.Intent;
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index a8ce889..af327c3 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
@@ -131,6 +147,40 @@ public class PackageInfo implements Parcelable {
* The features that this application has said it requires.
*/
public FeatureInfo[] reqFeatures;
+
+ /**
+ * Constant corresponding to <code>auto</code> in
+ * the {@link android.R.attr#installLocation} attribute.
+ * @hide
+ */
+ public static final int INSTALL_LOCATION_UNSPECIFIED = -1;
+ /**
+ * Constant corresponding to <code>auto</code> in
+ * the {@link android.R.attr#installLocation} attribute.
+ * @hide
+ */
+ public static final int INSTALL_LOCATION_AUTO = 0;
+ /**
+ * Constant corresponding to <code>internalOnly</code> in
+ * the {@link android.R.attr#installLocation} attribute.
+ * @hide
+ */
+ public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1;
+ /**
+ * Constant corresponding to <code>preferExternal</code> in
+ * the {@link android.R.attr#installLocation} attribute.
+ * @hide
+ */
+ public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
+ /**
+ * The install location requested by the activity. From the
+ * {@link android.R.attr#installLocation} attribute, one of
+ * {@link #INSTALL_LOCATION_AUTO},
+ * {@link #INSTALL_LOCATION_INTERNAL_ONLY},
+ * {@link #INSTALL_LOCATION_PREFER_EXTERNAL}
+ * @hide
+ */
+ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
public PackageInfo() {
}
@@ -168,6 +218,7 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(signatures, parcelableFlags);
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
+ dest.writeInt(installLocation);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -202,5 +253,6 @@ public class PackageInfo implements Parcelable {
signatures = source.createTypedArray(Signature.CREATOR);
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
+ installLocation = source.readInt();
}
}
diff --git a/core/java/android/content/Entity.aidl b/core/java/android/content/pm/PackageInfoLite.aidl
index fb201f3..2c80942 100644..100755
--- a/core/java/android/content/Entity.aidl
+++ b/core/java/android/content/pm/PackageInfoLite.aidl
@@ -1,4 +1,4 @@
-/* //device/java/android/android/content/Entity.aidl
+/* //device/java/android/android/view/WindowManager.aidl
**
** Copyright 2007, The Android Open Source Project
**
@@ -15,6 +15,6 @@
** limitations under the License.
*/
-package android.content;
+package android.content.pm;
-parcelable Entity;
+parcelable PackageInfoLite;
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
new file mode 100644
index 0000000..da97fde
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Basic information about a package as specified in its manifest.
+ * Utility class used in PackageManager methods
+ * @hide
+ */
+public class PackageInfoLite implements Parcelable {
+ /**
+ * The name of this package. From the &lt;manifest&gt; tag's "name"
+ * attribute.
+ */
+ public String packageName;
+
+ /**
+ * Specifies the recommended install location. Can be one of
+ * {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage
+ * {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media
+ * {@link PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors
+ * {@link PackageHelper.RECOMMEND_FAILED_INVALID_APK} for parse errors.
+ */
+ public int recommendedInstallLocation;
+ public int installLocation;
+
+ public PackageInfoLite() {
+ }
+
+ public String toString() {
+ return "PackageInfoLite{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + packageName + "}";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(packageName);
+ dest.writeInt(recommendedInstallLocation);
+ dest.writeInt(installLocation);
+ }
+
+ public static final Parcelable.Creator<PackageInfoLite> CREATOR
+ = new Parcelable.Creator<PackageInfoLite>() {
+ public PackageInfoLite createFromParcel(Parcel source) {
+ return new PackageInfoLite(source);
+ }
+
+ public PackageInfoLite[] newArray(int size) {
+ return new PackageInfoLite[size];
+ }
+ };
+
+ private PackageInfoLite(Parcel source) {
+ packageName = source.readString();
+ recommendedInstallLocation = source.readInt();
+ installLocation = source.readInt();
+ }
+}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 8043dae..14c0680 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.content.res.XmlResourceParser;
@@ -62,9 +78,11 @@ public class PackageItemInfo {
public PackageItemInfo(PackageItemInfo orig) {
name = orig.name;
+ if (name != null) name = name.trim();
packageName = orig.packageName;
labelRes = orig.labelRes;
nonLocalizedLabel = orig.nonLocalizedLabel;
+ if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim();
icon = orig.icon;
metaData = orig.metaData;
}
@@ -85,12 +103,12 @@ public class PackageItemInfo {
return nonLocalizedLabel;
}
if (labelRes != 0) {
- CharSequence label = pm.getText(packageName, labelRes, null);
+ CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
if (label != null) {
- return label;
+ return label.toString().trim();
}
}
- if(name != null) {
+ if (name != null) {
return name;
}
return packageName;
@@ -105,15 +123,31 @@ public class PackageItemInfo {
* the PackageManager from which you originally retrieved this item.
*
* @return Returns a Drawable containing the item's icon. If the
- * item does not have an icon, the default activity icon is returned.
+ * item does not have an icon, the item's default icon is returned
+ * such as the default activity icon.
*/
public Drawable loadIcon(PackageManager pm) {
if (icon != 0) {
- Drawable dr = pm.getDrawable(packageName, icon, null);
+ Drawable dr = pm.getDrawable(packageName, icon, getApplicationInfo());
if (dr != null) {
return dr;
}
}
+ return loadDefaultIcon(pm);
+ }
+
+ /**
+ * Retrieve the default graphical icon associated with this item.
+ *
+ * @param pm A PackageManager from which the icon can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's default icon
+ * such as the default activity icon.
+ *
+ * @hide
+ */
+ protected Drawable loadDefaultIcon(PackageManager pm) {
return pm.getDefaultActivityIcon();
}
@@ -134,7 +168,7 @@ public class PackageItemInfo {
if (metaData != null) {
int resid = metaData.getInt(name);
if (resid != 0) {
- return pm.getXml(packageName, resid, null);
+ return pm.getXml(packageName, resid, getApplicationInfo());
}
}
return null;
@@ -164,7 +198,7 @@ public class PackageItemInfo {
dest.writeInt(icon);
dest.writeBundle(metaData);
}
-
+
protected PackageItemInfo(Parcel source) {
name = source.readString();
packageName = source.readString();
@@ -175,6 +209,18 @@ public class PackageItemInfo {
metaData = source.readBundle();
}
+ /**
+ * Get the ApplicationInfo for the application to which this item belongs,
+ * if available, otherwise returns null.
+ *
+ * @return Returns the ApplicationInfo of this item, or null if not known.
+ *
+ * @hide
+ */
+ protected ApplicationInfo getApplicationInfo() {
+ return null;
+ }
+
public static class DisplayNameComparator
implements Comparator<PackageItemInfo> {
public DisplayNameComparator(PackageManager pm) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 53a966d..68b44e7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -27,6 +27,8 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Environment;
+import android.os.StatFs;
import android.util.AndroidException;
import android.util.DisplayMetrics;
@@ -231,7 +233,7 @@ public abstract class PackageManager {
/**
* Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
* indicate that this package should be installed as forward locked, i.e. only the app itself
- * should have access to it's code and non-resource assets.
+ * should have access to its code and non-resource assets.
* @hide
*/
public static final int INSTALL_FORWARD_LOCK = 0x00000001;
@@ -252,6 +254,20 @@ public abstract class PackageManager {
public static final int INSTALL_ALLOW_TEST = 0x00000004;
/**
+ * Flag parameter for {@link #installPackage} to indicate that this
+ * package has to be installed on the sdcard.
+ * @hide
+ */
+ public static final int INSTALL_EXTERNAL = 0x00000008;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that this
+ * package has to be installed on the sdcard.
+ * @hide
+ */
+ public static final int INSTALL_INTERNAL = 0x00000010;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
@@ -411,6 +427,33 @@ public abstract class PackageManager {
*/
public static final int INSTALL_FAILED_MISSING_FEATURE = -17;
+ // ------ Errors related to sdcard
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * a secure container mount point couldn't be accessed on external media.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_CONTAINER_ERROR = -18;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package couldn't be installed in the specified install
+ * location.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
+
+ /**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package couldn't be installed in the specified install
+ * location because the media is not available.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
+
/**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
@@ -495,17 +538,12 @@ public abstract class PackageManager {
public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109;
/**
- * Indicates the state of installation. Used by PackageManager to
- * figure out incomplete installations. Say a package is being installed
- * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till
- * the package installation is successful or unsuccesful lin which case
- * the PackageManager will no longer maintain state information associated
- * with the package. If some exception(like device freeze or battery being
- * pulled out) occurs during installation of a package, the PackageManager
- * needs this information to clean up the previously failed installation.
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the package because of system issues.
+ * @hide
*/
- public static final int PKG_INSTALL_INCOMPLETE = 0;
- public static final int PKG_INSTALL_COMPLETE = 1;
+ public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
/**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
@@ -516,34 +554,159 @@ public abstract class PackageManager {
public static final int DONT_DELETE_DATA = 0x00000001;
/**
+ * 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;
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * when the package hasn't been successfully moved by the system
+ * because of insufficient memory on specified media.
+ * @hide
+ */
+ public static final int MOVE_FAILED_INSUFFICIENT_STORAGE = -1;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package doesn't exist.
+ * @hide
+ */
+ public static final int MOVE_FAILED_DOESNT_EXIST = -2;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved since its a system package.
+ * @hide
+ */
+ public static final int MOVE_FAILED_SYSTEM_PACKAGE = -3;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved since its forward locked.
+ * @hide
+ */
+ public static final int MOVE_FAILED_FORWARD_LOCKED = -4;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved to the specified location.
+ * @hide
+ */
+ public static final int MOVE_FAILED_INVALID_LOCATION = -5;
+
+ /**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+ * if the specified package cannot be moved to the specified location.
+ * @hide
+ */
+ public static final int MOVE_FAILED_INTERNAL_ERROR = -6;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to internal storage if its
+ * been installed on external media.
+ * @hide
+ */
+ public static final int MOVE_INTERNAL = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #movePackage} to indicate that
+ * the package should be moved to external media.
+ * @hide
+ */
+ public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * other devices via Bluetooth.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a camera facing away
* from the screen.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CAMERA = "android.hardware.camera";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's camera supports auto-focus.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's camera supports flash.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
-
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports one or more methods of
+ * reporting current location.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION = "android.hardware.location";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a Global Positioning System
+ * receiver and can report precise location.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can report location with coarse
+ * accuracy using a network-based geolocation system.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_LOCATION_NETWORK = "android.hardware.location.network";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can record audio via a
+ * microphone.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MICROPHONE = "android.hardware.microphone";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes a magnetometer (compass).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device includes an accelerometer.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes a light sensor.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
-
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes a proximity sensor.
@@ -575,18 +738,49 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device's touch screen supports multitouch.
+ * {@link #hasSystemFeature}: The device's display has a touch screen.
+ */
+ @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
+ * multitouch sufficient for basic two-finger gesture detection.
*/
@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
+ * tracking two or more fingers fully independently.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports live wallpapers.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
-
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports WiFi (802.11) networking.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_WIFI = "android.hardware.wifi";
+
+ /**
+ * Action to external storage service to clean out removed apps.
+ * @hide
+ */
+ public static final String ACTION_CLEAN_EXTERNAL_STORAGE
+ = "android.content.pm.CLEAN_EXTERNAL_STORAGE";
+
/**
* Retrieve overall information about an application package that is
* installed on the system.
@@ -633,6 +827,23 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * Map from the current package names in use on the device to whatever
+ * the current canonical name of that package is.
+ * @param names Array of current names to be mapped.
+ * @return Returns an array of the same size as the original, containing
+ * 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.
+ * @return Returns an array of the same size as the original, containing
+ * 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
* packages. The current implementation will look first for a main
@@ -917,6 +1128,15 @@ public abstract class PackageManager {
public abstract boolean addPermission(PermissionInfo info);
/**
+ * Like {@link #addPermission(PermissionInfo)} but asynchronously
+ * persists the package manager state after returning from the call,
+ * allowing it to return quicker and batch a series of adds at the
+ * expense of no guarantee the added permission will be retained if
+ * 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
* -- you are only allowed to remove permissions that you are allowed
@@ -1094,7 +1314,9 @@ public abstract class PackageManager {
*
* @return Returns a ResolveInfo containing the final activity intent that
* was determined to be the best action. Returns null if no
- * matching activity was found.
+ * matching activity was found. If multiple matching activities are
+ * found and there is no default set, returns a ResolveInfo
+ * containing something else, such as the activity resolver.
*
* @see #MATCH_DEFAULT_ONLY
* @see #GET_INTENT_FILTERS
@@ -1715,6 +1937,10 @@ public abstract class PackageManager {
public abstract List<PackageInfo> getPreferredPackages(int flags);
/**
+ * @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
@@ -1729,10 +1955,15 @@ public abstract class PackageManager {
* @param activity The component name of the activity that is to be
* preferred.
*/
+ @Deprecated
public abstract void addPreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity);
/**
+ * @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
@@ -1749,6 +1980,7 @@ public abstract class PackageManager {
* preferred.
* @hide
*/
+ @Deprecated
public abstract void replacePreferredActivity(IntentFilter filter, int match,
ComponentName[] set, ComponentName activity);
@@ -1756,6 +1988,7 @@ public abstract class PackageManager {
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
+ * An application can only clear its own package(s).
*
* @param packageName The name of the package whose preferred activity
* mappings are to be removed.
@@ -1861,4 +2094,23 @@ public abstract class PackageManager {
* Return whether the device has been booted into safe mode.
*/
public abstract boolean isSafeMode();
+
+ /**
+ * Attempts to move package resources from internal to external media or vice versa.
+ * Since this may take a little while, the result will
+ * be posted back to the given observer. This call may fail if the calling context
+ * lacks the {@link android.Manifest.permission#MOVE_PACKAGE} permission, if the
+ * named package cannot be found, or if the named package is a "system package".
+ *
+ * @param packageName The name of the package to delete
+ * @param observer An observer callback to get notified when the package move is
+ * complete. {@link android.content.pm.IPackageMoveObserver#packageMoved(boolean)} will be
+ * called when that happens. observer may be null to indicate that no callback is desired.
+ * @param flags To indicate install location {@link #MOVE_INTERNAL} or
+ * {@link #MOVE_EXTERNAL_MEDIA}
+ *
+ * @hide
+ */
+ public abstract void movePackage(
+ String packageName, IPackageMoveObserver observer, int flags);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3f8c71e..2a20a2d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -30,11 +30,13 @@ import android.content.res.XmlResourceParser;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Config;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+
import com.android.internal.util.XmlUtils;
import java.io.File;
@@ -94,7 +96,8 @@ public class PackageParser {
private static final Object mSync = new Object();
private static WeakReference<byte[]> mReadBuffer;
- private static boolean sCompatibilityModeEnabled = true;
+ private static boolean sCompatibilityModeEnabled = true;
+ private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
static class ParsePackageItemArgs {
final Package owner;
@@ -119,19 +122,35 @@ public class PackageParser {
static class ParseComponentArgs extends ParsePackageItemArgs {
final String[] sepProcesses;
final int processRes;
+ final int descriptionRes;
final int enabledRes;
int flags;
ParseComponentArgs(Package _owner, String[] _outError,
int _nameRes, int _labelRes, int _iconRes,
- String[] _sepProcesses, int _processRes,int _enabledRes) {
+ String[] _sepProcesses, int _processRes,
+ int _descriptionRes, int _enabledRes) {
super(_owner, _outError, _nameRes, _labelRes, _iconRes);
sepProcesses = _sepProcesses;
processRes = _processRes;
+ descriptionRes = _descriptionRes;
enabledRes = _enabledRes;
}
}
-
+
+ /* Light weight package info.
+ * @hide
+ */
+ public static class PackageLite {
+ public String packageName;
+ public int installLocation;
+ public String mScanPath;
+ public PackageLite(String packageName, int installLocation) {
+ this.packageName = packageName;
+ this.installLocation = installLocation;
+ }
+ }
+
private ParsePackageItemArgs mParseInstrumentationArgs;
private ParseComponentArgs mParseActivityArgs;
private ParseComponentArgs mParseActivityAliasArgs;
@@ -174,6 +193,7 @@ public class PackageParser {
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = p.applicationInfo;
+ pi.installLocation = p.installLocation;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
@@ -323,20 +343,26 @@ public class PackageParser {
} catch (IOException e) {
Log.w(TAG, "Exception reading " + je.getName() + " in "
+ jarFile.getName(), e);
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Exception reading " + je.getName() + " in "
+ + jarFile.getName(), e);
}
return null;
}
- public final static int PARSE_IS_SYSTEM = 0x0001;
- public final static int PARSE_CHATTY = 0x0002;
- public final static int PARSE_MUST_BE_APK = 0x0004;
- public final static int PARSE_IGNORE_PROCESSES = 0x0008;
+ public final static int PARSE_IS_SYSTEM = 1<<0;
+ public final static int PARSE_CHATTY = 1<<1;
+ public final static int PARSE_MUST_BE_APK = 1<<2;
+ public final static int PARSE_IGNORE_PROCESSES = 1<<3;
+ public final static int PARSE_FORWARD_LOCK = 1<<4;
+ public final static int PARSE_ON_SDCARD = 1<<5;
+ public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
public int getParseError() {
return mParseError;
}
- public Package parsePackage(File sourceFile, String destFileName,
+ public Package parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
mParseError = PackageManager.INSTALL_SUCCEEDED;
@@ -413,8 +439,11 @@ public class PackageParser {
parser.close();
assmgr.close();
- pkg.applicationInfo.sourceDir = destFileName;
- pkg.applicationInfo.publicSourceDir = destFileName;
+ // Set code and resource paths
+ pkg.mPath = destCodePath;
+ pkg.mScanPath = mArchiveSourcePath;
+ //pkg.applicationInfo.sourceDir = destCodePath;
+ //pkg.applicationInfo.publicSourceDir = destRes;
pkg.mSignatures = null;
return pkg;
@@ -551,7 +580,14 @@ public class PackageParser {
return true;
}
- public static String parsePackageName(String packageFilePath, int flags) {
+ /*
+ * Utility method that retrieves just the package name and install
+ * location from the apk location at the given file path.
+ * @param packageFilePath file location of the apk
+ * @param flags Special parse flags
+ * @return PackageLite object with package information.
+ */
+ public static PackageLite parsePackageLite(String packageFilePath, int flags) {
XmlResourceParser parser = null;
AssetManager assmgr = null;
try {
@@ -566,9 +602,9 @@ public class PackageParser {
}
AttributeSet attrs = parser;
String errors[] = new String[1];
- String packageName = null;
+ PackageLite packageLite = null;
try {
- packageName = parsePackageName(parser, attrs, flags, errors);
+ packageLite = parsePackageLite(parser, attrs, flags, errors);
} catch (IOException e) {
Log.w(TAG, packageFilePath, e);
} catch (XmlPullParserException e) {
@@ -577,11 +613,11 @@ public class PackageParser {
if (parser != null) parser.close();
if (assmgr != null) assmgr.close();
}
- if (packageName == null) {
- Log.e(TAG, "parsePackageName error: " + errors[0]);
+ if (packageLite == null) {
+ Log.e(TAG, "parsePackageLite error: " + errors[0]);
return null;
}
- return packageName;
+ return packageLite;
}
private static String validateName(String name, boolean requiresSeparator) {
@@ -645,6 +681,49 @@ public class PackageParser {
return pkgName.intern();
}
+ private static PackageLite parsePackageLite(XmlPullParser parser,
+ AttributeSet attrs, int flags, String[] outError)
+ throws IOException, XmlPullParserException {
+
+ int type;
+ while ((type=parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != parser.START_TAG) {
+ outError[0] = "No start tag found";
+ return null;
+ }
+ if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+ TAG, "Root element name: '" + parser.getName() + "'");
+ if (!parser.getName().equals("manifest")) {
+ outError[0] = "No <manifest> tag";
+ return null;
+ }
+ String pkgName = attrs.getAttributeValue(null, "package");
+ if (pkgName == null || pkgName.length() == 0) {
+ outError[0] = "<manifest> does not specify package";
+ return null;
+ }
+ String nameError = validateName(pkgName, true);
+ if (nameError != null && !"android".equals(pkgName)) {
+ outError[0] = "<manifest> specifies bad package name \""
+ + pkgName + "\": " + nameError;
+ return null;
+ }
+ int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ String attr = attrs.getAttributeName(i);
+ if (attr.equals("installLocation")) {
+ installLocation = attrs.getAttributeIntValue(i,
+ PARSE_DEFAULT_INSTALL_LOCATION);
+ break;
+ }
+ }
+ return new PackageLite(pkgName.intern(), installLocation);
+ }
+
/**
* Temporary.
*/
@@ -681,14 +760,14 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifest);
pkg.mVersionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
- pkg.mVersionName = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifest_versionName);
+ pkg.mVersionName = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_versionName, 0);
if (pkg.mVersionName != null) {
pkg.mVersionName = pkg.mVersionName.intern();
}
- String str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifest_sharedUserId);
- if (str != null) {
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
+ if (str != null && str.length() > 0) {
String nameError = validateName(str, true);
if (nameError != null && !"android".equals(pkgName)) {
outError[0] = "<manifest> specifies bad sharedUserId name \""
@@ -702,6 +781,10 @@ public class PackageParser {
}
sa.recycle();
+ pkg.installLocation = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifest_installLocation,
+ PARSE_DEFAULT_INSTALL_LOCATION);
+
// Resource boolean are -1, so 1 means we don't know the value.
int supportsSmallScreens = 1;
int supportsNormalScreens = 1;
@@ -750,6 +833,8 @@ public class PackageParser {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesPermission);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
@@ -793,6 +878,8 @@ public class PackageParser {
FeatureInfo fi = new FeatureInfo();
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesFeature);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
fi.name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesFeature_name);
if (fi.name == null) {
@@ -924,6 +1011,8 @@ public class PackageParser {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);
@@ -945,6 +1034,42 @@ public class PackageParser {
return null;
}
+ } else if (tagName.equals("original-package")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String orig =sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
+ if (!pkg.packageName.equals(orig)) {
+ if (pkg.mOriginalPackages == null) {
+ pkg.mOriginalPackages = new ArrayList<String>();
+ pkg.mRealPackage = pkg.packageName;
+ }
+ pkg.mOriginalPackages.add(orig);
+ }
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("adopt-permissions")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage);
+
+ String name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestOriginalPackage_name, 0);
+
+ sa.recycle();
+
+ if (name != null) {
+ if (pkg.mAdoptPermissions == null) {
+ pkg.mAdoptPermissions = new ArrayList<String>();
+ }
+ pkg.mAdoptPermissions.add(name);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("eat-comment")) {
// Just skip this tag
XmlUtils.skipCurrentTag(parser);
@@ -1090,7 +1215,7 @@ public class PackageParser {
if (procSeq == null || procSeq.length() <= 0) {
return defProc;
}
- return buildCompoundName(pkg, procSeq, "package", outError);
+ return buildCompoundName(pkg, procSeq, "process", outError);
}
private static String buildTaskAffinityName(String pkg, String defProc,
@@ -1157,6 +1282,8 @@ public class PackageParser {
return null;
}
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
perm.info.group = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestPermission_permissionGroup);
if (perm.info.group != null) {
@@ -1261,6 +1388,8 @@ public class PackageParser {
}
String str;
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
str = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestInstrumentation_targetPackage);
a.info.targetPackage = str != null ? str.intern() : null;
@@ -1301,8 +1430,8 @@ public class PackageParser {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
- String name = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_name);
+ String name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_name, 0);
if (name != null) {
ai.className = buildClassName(pkgName, name, outError);
if (ai.className == null) {
@@ -1312,8 +1441,8 @@ public class PackageParser {
}
}
- String manageSpaceActivity = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity);
+ String manageSpaceActivity = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_manageSpaceActivity, 0);
if (manageSpaceActivity != null) {
ai.manageSpaceActivityName = buildClassName(pkgName, manageSpaceActivity,
outError);
@@ -1324,10 +1453,10 @@ public class PackageParser {
if (allowBackup) {
ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
- // backupAgent, killAfterRestore, and restoreNeedsApplication are only relevant
+ // backupAgent, killAfterRestore, and restoreAnyVersion are only relevant
// if backup is possible for the given application.
- String backupAgent = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_backupAgent);
+ String backupAgent = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupAgent, 0);
if (backupAgent != null) {
ai.backupAgentName = buildClassName(pkgName, backupAgent, outError);
if (false) {
@@ -1341,9 +1470,9 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_KILL_AFTER_RESTORE;
}
if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestApplication_restoreNeedsApplication,
+ com.android.internal.R.styleable.AndroidManifestApplication_restoreAnyVersion,
false)) {
- ai.flags |= ApplicationInfo.FLAG_RESTORE_NEEDS_APPLICATION;
+ ai.flags |= ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
}
}
}
@@ -1369,6 +1498,14 @@ public class PackageParser {
}
}
+ if ((flags & PARSE_FORWARD_LOCK) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_FORWARD_LOCK;
+ }
+
+ if ((flags & PARSE_ON_SDCARD) != 0) {
+ ai.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+ }
+
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
false)) {
@@ -1376,6 +1513,12 @@ public class PackageParser {
}
if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_vmSafeMode,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_VM_SAFE_MODE;
+ }
+
+ if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
true)) {
ai.flags |= ApplicationInfo.FLAG_HAS_CODE;
@@ -1400,21 +1543,40 @@ public class PackageParser {
}
String str;
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_permission);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_permission, 0);
ai.permission = (str != null && str.length() > 0) ? str.intern() : null;
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity);
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity, 0);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ str = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_taskAffinity);
+ }
ai.taskAffinity = buildTaskAffinityName(ai.packageName, ai.packageName,
str, outError);
if (outError[0] == null) {
- ai.processName = buildProcessName(ai.packageName, null, sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestApplication_process),
+ CharSequence pname;
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ pname = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestApplication_process, 0);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ pname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_process);
+ }
+ ai.processName = buildProcessName(ai.packageName, null, pname,
flags, mSeparateProcesses, outError);
- ai.enabled = sa.getBoolean(com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
+ ai.enabled = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
}
sa.recycle();
@@ -1493,6 +1655,8 @@ public class PackageParser {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
String lname = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesLibrary_name);
boolean req = sa.getBoolean(
@@ -1542,7 +1706,7 @@ public class PackageParser {
private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
String[] outError, String tag, TypedArray sa,
int nameRes, int labelRes, int iconRes) {
- String name = sa.getNonResourceString(nameRes);
+ String name = sa.getNonConfigurationString(nameRes, 0);
if (name == null) {
outError[0] = tag + " does not specify android:name";
return false;
@@ -1583,6 +1747,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestActivity_process,
+ com.android.internal.R.styleable.AndroidManifestActivity_description,
com.android.internal.R.styleable.AndroidManifestActivity_enabled);
}
@@ -1607,16 +1772,16 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_theme, 0);
String str;
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestActivity_permission);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivity_permission, 0);
if (str == null) {
a.info.permission = owner.applicationInfo.permission;
} else {
a.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity, 0);
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
@@ -1762,8 +1927,8 @@ public class PackageParser {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestActivityAlias);
- String targetActivity = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity);
+ String targetActivity = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_targetActivity, 0);
if (targetActivity == null) {
outError[0] = "<activity-alias> does not specify android:targetActivity";
sa.recycle();
@@ -1784,6 +1949,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
mSeparateProcesses,
0,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
com.android.internal.R.styleable.AndroidManifestActivityAlias_enabled);
mParseActivityAliasArgs.tag = "<activity-alias>";
}
@@ -1818,6 +1984,9 @@ public class PackageParser {
info.nonLocalizedLabel = target.info.nonLocalizedLabel;
info.launchMode = target.info.launchMode;
info.processName = target.info.processName;
+ if (info.descriptionRes == 0) {
+ info.descriptionRes = target.info.descriptionRes;
+ }
info.screenOrientation = target.info.screenOrientation;
info.taskAffinity = target.info.taskAffinity;
info.theme = target.info.theme;
@@ -1836,8 +2005,8 @@ public class PackageParser {
}
String str;
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestActivityAlias_permission);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_permission, 0);
if (str != null) {
a.info.permission = str.length() > 0 ? str.toString().intern() : null;
}
@@ -1907,6 +2076,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestProvider_process,
+ com.android.internal.R.styleable.AndroidManifestProvider_description,
com.android.internal.R.styleable.AndroidManifestProvider_enabled);
mParseProviderArgs.tag = "<provider>";
}
@@ -1923,17 +2093,17 @@ public class PackageParser {
p.info.exported = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestProvider_exported, true);
- String cpname = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestProvider_authorities);
+ String cpname = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_authorities, 0);
p.info.isSyncable = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestProvider_syncable,
false);
- String permission = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestProvider_permission);
- String str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestProvider_readPermission);
+ String permission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_permission, 0);
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_readPermission, 0);
if (str == null) {
str = permission;
}
@@ -1943,8 +2113,8 @@ public class PackageParser {
p.info.readPermission =
str.length() > 0 ? str.toString().intern() : null;
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestProvider_writePermission);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestProvider_writePermission, 0);
if (str == null) {
str = permission;
}
@@ -2007,20 +2177,20 @@ public class PackageParser {
PatternMatcher pa = null;
- String str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path);
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_path, 0);
if (str != null) {
pa = new PatternMatcher(str, PatternMatcher.PATTERN_LITERAL);
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 0);
if (str != null) {
pa = new PatternMatcher(str, PatternMatcher.PATTERN_PREFIX);
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestGrantUriPermission_pathPattern, 0);
if (str != null) {
pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}
@@ -2058,15 +2228,15 @@ public class PackageParser {
PathPermission pa = null;
- String permission = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_permission);
- String readPermission = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission);
+ String permission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_permission, 0);
+ String readPermission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission, 0);
if (readPermission == null) {
readPermission = permission;
}
- String writePermission = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission);
+ String writePermission = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission, 0);
if (writePermission == null) {
writePermission = permission;
}
@@ -2077,7 +2247,7 @@ public class PackageParser {
havePerm = true;
}
if (writePermission != null) {
- writePermission = readPermission.intern();
+ writePermission = writePermission.intern();
havePerm = true;
}
@@ -2093,22 +2263,22 @@ public class PackageParser {
return false;
}
- String path = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_path);
+ String path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_path, 0);
if (path != null) {
pa = new PathPermission(path,
PatternMatcher.PATTERN_LITERAL, readPermission, writePermission);
}
- path = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix);
+ path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix, 0);
if (path != null) {
pa = new PathPermission(path,
PatternMatcher.PATTERN_PREFIX, readPermission, writePermission);
}
- path = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern);
+ path = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern, 0);
if (path != null) {
pa = new PathPermission(path,
PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission);
@@ -2169,6 +2339,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_icon,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestService_process,
+ com.android.internal.R.styleable.AndroidManifestService_description,
com.android.internal.R.styleable.AndroidManifestService_enabled);
mParseServiceArgs.tag = "<service>";
}
@@ -2189,8 +2360,8 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_exported, false);
}
- String str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestService_permission);
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestService_permission, 0);
if (str == null) {
s.info.permission = owner.applicationInfo.permission;
} else {
@@ -2287,8 +2458,8 @@ public class PackageParser {
data = new Bundle();
}
- String name = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestMetaData_name);
+ String name = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestMetaData_name, 0);
if (name == null) {
outError[0] = "<meta-data> requires an android:name attribute";
sa.recycle();
@@ -2406,8 +2577,8 @@ public class PackageParser {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestData);
- String str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_mimeType);
+ String str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_mimeType, 0);
if (str != null) {
try {
outInfo.addDataType(str);
@@ -2418,34 +2589,34 @@ public class PackageParser {
}
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_scheme);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_scheme, 0);
if (str != null) {
outInfo.addDataScheme(str);
}
- String host = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_host);
- String port = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_port);
+ String host = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_host, 0);
+ String port = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_port, 0);
if (host != null) {
outInfo.addDataAuthority(host, port);
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_path);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_path, 0);
if (str != null) {
outInfo.addDataPath(str, PatternMatcher.PATTERN_LITERAL);
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_pathPrefix);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPrefix, 0);
if (str != null) {
outInfo.addDataPath(str, PatternMatcher.PATTERN_PREFIX);
}
- str = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestData_pathPattern);
+ str = sa.getNonConfigurationString(
+ com.android.internal.R.styleable.AndroidManifestData_pathPattern, 0);
if (str != null) {
outInfo.addDataPath(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
}
@@ -2478,7 +2649,7 @@ public class PackageParser {
}
public final static class Package {
- public final String packageName;
+ public String packageName;
// For now we only support one application per package.
public final ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -2499,6 +2670,10 @@ public class PackageParser {
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
+ public ArrayList<String> mOriginalPackages = null;
+ public String mRealPackage = null;
+ public ArrayList<String> mAdoptPermissions = null;
+
// We store the application meta-data independently to avoid multiple unwanted references
public Bundle mAppMetaData = null;
@@ -2524,10 +2699,6 @@ public class PackageParser {
// preferred up order.
public int mPreferredOrder = 0;
- // For use by package manager service to keep track of which apps
- // have been installed with forward locking.
- public boolean mForwardLocked;
-
// For use by the package manager to keep track of the path to the
// file an app came from.
public String mScanPath;
@@ -2549,12 +2720,40 @@ public class PackageParser {
*/
public ArrayList<FeatureInfo> reqFeatures = null;
+ public int installLocation;
+
public Package(String _name) {
packageName = _name;
applicationInfo.packageName = _name;
applicationInfo.uid = -1;
}
+ public void setPackageName(String newName) {
+ packageName = newName;
+ applicationInfo.packageName = newName;
+ for (int i=permissions.size()-1; i>=0; i--) {
+ permissions.get(i).setPackageName(newName);
+ }
+ for (int i=permissionGroups.size()-1; i>=0; i--) {
+ permissionGroups.get(i).setPackageName(newName);
+ }
+ for (int i=activities.size()-1; i>=0; i--) {
+ activities.get(i).setPackageName(newName);
+ }
+ for (int i=receivers.size()-1; i>=0; i--) {
+ receivers.get(i).setPackageName(newName);
+ }
+ for (int i=providers.size()-1; i>=0; i--) {
+ providers.get(i).setPackageName(newName);
+ }
+ for (int i=services.size()-1; i>=0; i--) {
+ services.get(i).setPackageName(newName);
+ }
+ for (int i=instrumentation.size()-1; i>=0; i--) {
+ instrumentation.get(i).setPackageName(newName);
+ }
+ }
+
public String toString() {
return "Package{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -2565,24 +2764,24 @@ public class PackageParser {
public static class Component<II extends IntentInfo> {
public final Package owner;
public final ArrayList<II> intents;
- public final ComponentName component;
- public final String componentShortName;
+ public final String className;
public Bundle metaData;
+ ComponentName componentName;
+ String componentShortName;
+
public Component(Package _owner) {
owner = _owner;
intents = null;
- component = null;
- componentShortName = null;
+ className = null;
}
public Component(final ParsePackageItemArgs args, final PackageItemInfo outInfo) {
owner = args.owner;
intents = new ArrayList<II>(0);
- String name = args.sa.getNonResourceString(args.nameRes);
+ String name = args.sa.getNonConfigurationString(args.nameRes, 0);
if (name == null) {
- component = null;
- componentShortName = null;
+ className = null;
args.outError[0] = args.tag + " does not specify android:name";
return;
}
@@ -2590,15 +2789,12 @@ public class PackageParser {
outInfo.name
= buildClassName(owner.applicationInfo.packageName, name, args.outError);
if (outInfo.name == null) {
- component = null;
- componentShortName = null;
+ className = null;
args.outError[0] = args.tag + " does not have valid android:name";
return;
}
- component = new ComponentName(owner.applicationInfo.packageName,
- outInfo.name);
- componentShortName = component.flattenToShortString();
+ className = outInfo.name;
int iconVal = args.sa.getResourceId(args.iconRes, 0);
if (iconVal != 0) {
@@ -2621,19 +2817,60 @@ public class PackageParser {
}
if (args.processRes != 0) {
+ CharSequence pname;
+ if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
+ pname = args.sa.getNonConfigurationString(args.processRes, 0);
+ } else {
+ // Some older apps have been seen to use a resource reference
+ // here that on older builds was ignored (with a warning). We
+ // need to continue to do this for them so they don't break.
+ pname = args.sa.getNonResourceString(args.processRes);
+ }
outInfo.processName = buildProcessName(owner.applicationInfo.packageName,
- owner.applicationInfo.processName, args.sa.getNonResourceString(args.processRes),
+ owner.applicationInfo.processName, pname,
args.flags, args.sepProcesses, args.outError);
}
+
+ if (args.descriptionRes != 0) {
+ outInfo.descriptionRes = args.sa.getResourceId(args.descriptionRes, 0);
+ }
+
outInfo.enabled = args.sa.getBoolean(args.enabledRes, true);
}
public Component(Component<II> clone) {
owner = clone.owner;
intents = clone.intents;
- component = clone.component;
+ className = clone.className;
+ componentName = clone.componentName;
componentShortName = clone.componentShortName;
- metaData = clone.metaData;
+ }
+
+ public ComponentName getComponentName() {
+ if (componentName != null) {
+ return componentName;
+ }
+ if (className != null) {
+ componentName = new ComponentName(owner.applicationInfo.packageName,
+ className);
+ }
+ return componentName;
+ }
+
+ public String getComponentShortName() {
+ if (componentShortName != null) {
+ return componentShortName;
+ }
+ ComponentName component = getComponentName();
+ if (component != null) {
+ componentShortName = component.flattenToShortString();
+ }
+ return componentShortName;
+ }
+
+ public void setPackageName(String packageName) {
+ componentName = null;
+ componentShortName = null;
}
}
@@ -2651,6 +2888,11 @@ public class PackageParser {
super(_owner);
info = _info;
}
+
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
public String toString() {
return "Permission{"
@@ -2672,6 +2914,11 @@ public class PackageParser {
info = _info;
}
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
public String toString() {
return "PermissionGroup{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -2747,10 +2994,15 @@ public class PackageParser {
info.applicationInfo = args.owner.applicationInfo;
}
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
public String toString() {
return "Activity{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + component.flattenToString() + "}";
+ + " " + getComponentShortName() + "}";
}
}
@@ -2776,10 +3028,15 @@ public class PackageParser {
info.applicationInfo = args.owner.applicationInfo;
}
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
public String toString() {
return "Service{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + component.flattenToString() + "}";
+ + " " + getComponentShortName() + "}";
}
}
@@ -2812,6 +3069,11 @@ public class PackageParser {
this.syncable = existingProvider.syncable;
}
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
public String toString() {
return "Provider{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -2845,10 +3107,15 @@ public class PackageParser {
info = _info;
}
+ public void setPackageName(String packageName) {
+ super.setPackageName(packageName);
+ info.packageName = packageName;
+ }
+
public String toString() {
return "Instrumentation{"
+ Integer.toHexString(System.identityHashCode(this))
- + " " + component.flattenToString() + "}";
+ + " " + getComponentShortName() + "}";
}
}
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 66c6efd..9464321 100755
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index b39a67d..dce3963 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -21,6 +21,8 @@ import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ComponentName;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
@@ -115,6 +117,11 @@ public abstract class RegisteredServicesCache<V> {
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
mContext.registerReceiver(receiver, intentFilter);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiver(receiver, sdFilter);
}
public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -397,7 +404,8 @@ public abstract class RegisteredServicesCache<V> {
"Meta-data does not start with " + mAttributesName + " tag");
}
- V v = parseServiceAttributes(si.packageName, attrs);
+ V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
+ si.packageName, attrs);
if (v == null) {
return null;
}
@@ -405,6 +413,9 @@ public abstract class RegisteredServicesCache<V> {
final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
final int uid = applicationInfo.uid;
return new ServiceInfo<V>(v, componentName, uid);
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to load resources for pacakge " + si.packageName);
} finally {
if (parser != null) parser.close();
}
@@ -494,5 +505,6 @@ public abstract class RegisteredServicesCache<V> {
}
}
- public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
+ public abstract V parseServiceAttributes(Resources res,
+ String packageName, AttributeSet attrs);
}
diff --git a/core/java/android/content/pm/RegisteredServicesCacheListener.java b/core/java/android/content/pm/RegisteredServicesCacheListener.java
index 2bc0942..7095229 100644
--- a/core/java/android/content/pm/RegisteredServicesCacheListener.java
+++ b/core/java/android/content/pm/RegisteredServicesCacheListener.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcelable;
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 380db65..74e756b 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.content.IntentFilter;
@@ -117,7 +133,7 @@ public class ResolveInfo implements Parcelable {
if (resolvePackageName != null && labelRes != 0) {
label = pm.getText(resolvePackageName, labelRes, null);
if (label != null) {
- return label;
+ return label.toString().trim();
}
}
ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
@@ -125,10 +141,14 @@ public class ResolveInfo implements Parcelable {
if (labelRes != 0) {
label = pm.getText(ci.packageName, labelRes, ai);
if (label != null) {
- return label;
+ return label.toString().trim();
}
}
- return ci.loadLabel(pm);
+
+ CharSequence data = ci.loadLabel(pm);
+ // Make the data safe
+ if (data != null) data = data.toString().trim();
+ return data;
}
/**
@@ -143,8 +163,6 @@ public class ResolveInfo implements Parcelable {
* item does not have an icon, the default activity icon is returned.
*/
public Drawable loadIcon(PackageManager pm) {
- ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
- ApplicationInfo ai = ci.applicationInfo;
Drawable dr;
if (resolvePackageName != null && icon != 0) {
dr = pm.getDrawable(resolvePackageName, icon, null);
@@ -152,6 +170,8 @@ public class ResolveInfo implements Parcelable {
return dr;
}
}
+ ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
+ ApplicationInfo ai = ci.applicationInfo;
if (icon != 0) {
dr = pm.getDrawable(ci.packageName, icon, ai);
if (dr != null) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 51d2a4d..087a4fe 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import android.os.Parcel;
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
index 33598f0..935fc02 100644
--- a/core/java/android/content/pm/XmlSerializerAndParser.java
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.pm;
import org.xmlpull.v1.XmlSerializer;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 5894c4f..1070f08 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -24,6 +24,7 @@ import android.util.TypedValue;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.util.HashMap;
/**
* Provides access to an application's raw asset files; see {@link Resources}
@@ -59,6 +60,8 @@ public final class AssetManager {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = Config.LOGV || false;
+ private static final boolean DEBUG_REFS = false;
+
private static final Object sSync = new Object();
private static AssetManager sSystem = null;
@@ -72,6 +75,7 @@ public final class AssetManager {
private int mNumRefs = 1;
private boolean mOpen = true;
+ private HashMap<Integer, RuntimeException> mRefStacks;
/**
* Create a new AssetManager containing only the basic system assets.
@@ -82,6 +86,10 @@ public final class AssetManager {
*/
public AssetManager() {
synchronized (this) {
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
init();
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
@@ -99,6 +107,12 @@ public final class AssetManager {
}
private AssetManager(boolean isSystem) {
+ if (DEBUG_REFS) {
+ synchronized (this) {
+ mNumRefs = 0;
+ incRefsLocked(this.hashCode());
+ }
+ }
init();
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}
@@ -122,7 +136,7 @@ public final class AssetManager {
// + ", released=" + mReleased);
if (mOpen) {
mOpen = false;
- decRefsLocked();
+ decRefsLocked(this.hashCode());
}
}
}
@@ -298,8 +312,9 @@ public final class AssetManager {
}
int asset = openAsset(fileName, accessMode);
if (asset != 0) {
- mNumRefs++;
- return new AssetInputStream(asset);
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset file: " + fileName);
@@ -389,8 +404,9 @@ public final class AssetManager {
}
int asset = openNonAssetNative(cookie, fileName, accessMode);
if (asset != 0) {
- mNumRefs++;
- return new AssetInputStream(asset);
+ AssetInputStream res = new AssetInputStream(asset);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -468,16 +484,17 @@ public final class AssetManager {
}
int xmlBlock = openXmlAssetNative(cookie, fileName);
if (xmlBlock != 0) {
- mNumRefs++;
- return new XmlBlock(this, xmlBlock);
+ XmlBlock res = new XmlBlock(this, xmlBlock);
+ incRefsLocked(res.hashCode());
+ return res;
}
}
throw new FileNotFoundException("Asset XML file: " + fileName);
}
- /*package*/ void xmlBlockGone() {
+ /*package*/ void xmlBlockGone(int id) {
synchronized (this) {
- decRefsLocked();
+ decRefsLocked(id);
}
}
@@ -486,20 +503,34 @@ public final class AssetManager {
if (!mOpen) {
throw new RuntimeException("Assetmanager has been closed");
}
- mNumRefs++;
- return newTheme();
+ int res = newTheme();
+ incRefsLocked(res);
+ return res;
}
}
/*package*/ final void releaseTheme(int theme) {
synchronized (this) {
deleteTheme(theme);
- decRefsLocked();
+ decRefsLocked(theme);
}
}
protected void finalize() throws Throwable {
- destroy();
+ try {
+ if (DEBUG_REFS && mNumRefs != 0) {
+ Log.w(TAG, "AssetManager " + this
+ + " finalized with non-zero refs: " + mNumRefs);
+ if (mRefStacks != null) {
+ for (RuntimeException e : mRefStacks.values()) {
+ Log.w(TAG, "Reference from here", e);
+ }
+ }
+ }
+ destroy();
+ } finally {
+ super.finalize();
+ }
}
public final class AssetInputStream extends InputStream {
@@ -526,7 +557,7 @@ public final class AssetManager {
if (mAsset != 0) {
destroyAsset(mAsset);
mAsset = 0;
- decRefsLocked();
+ decRefsLocked(hashCode());
}
}
}
@@ -572,6 +603,26 @@ public final class AssetManager {
public native final int addAssetPath(String path);
/**
+ * Add multiple sets of assets to the asset manager at once. See
+ * {@link #addAssetPath(String)} for more information. Returns array of
+ * cookies for each added asset with 0 indicating failure, or null if
+ * the input array of paths is null.
+ * {@hide}
+ */
+ public final int[] addAssetPaths(String[] paths) {
+ if (paths == null) {
+ return null;
+ }
+
+ int[] cookies = new int[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ cookies[i] = addAssetPath(paths[i]);
+ }
+
+ return cookies;
+ }
+
+ /**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
@@ -599,7 +650,7 @@ public final class AssetManager {
public native final void setConfiguration(int mcc, int mnc, String locale,
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int screenLayout, int majorVersion);
+ int screenLayout, int uiMode, int majorVersion);
/**
* Retrieve the resource identifier for the given resource name.
@@ -690,7 +741,22 @@ public final class AssetManager {
private native final void init();
private native final void destroy();
- private final void decRefsLocked() {
+ private final void incRefsLocked(int id) {
+ if (DEBUG_REFS) {
+ if (mRefStacks == null) {
+ mRefStacks = new HashMap<Integer, RuntimeException>();
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ mRefStacks.put(this.hashCode(), ex);
+ }
+ }
+ mNumRefs++;
+ }
+
+ private final void decRefsLocked(int id) {
+ if (DEBUG_REFS && mRefStacks != null) {
+ mRefStacks.remove(id);
+ }
mNumRefs--;
//System.out.println("Dec streams: mNumRefs=" + mNumRefs
// + " mReleased=" + mReleased);
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 453a83d..70baaef 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -44,7 +44,7 @@ import java.util.Arrays;
* &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;item android:state_focused="true" android:color="@color/testcolor1"/&gt;
* &lt;item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /&gt;
- * &lt;item android:state_enabled="false" android:colore="@color/testcolor3" /&gt;
+ * &lt;item android:state_enabled="false" android:color="@color/testcolor3" /&gt;
* &lt;item android:state_active="true" android:color="@color/testcolor4" /&gt;
* &lt;item android:color="@color/testcolor5"/&gt;
* &lt;/selector&gt;
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 1fe34b5..1a0c867 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.res;
import android.content.pm.ActivityInfo;
@@ -161,6 +177,35 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* or {@link #ORIENTATION_SQUARE}.
*/
public int orientation;
+
+ public static final int UI_MODE_TYPE_MASK = 0x0f;
+ public static final int UI_MODE_TYPE_UNDEFINED = 0x00;
+ public static final int UI_MODE_TYPE_NORMAL = 0x01;
+ public static final int UI_MODE_TYPE_DESK = 0x02;
+ public static final int UI_MODE_TYPE_CAR = 0x03;
+
+ public static final int UI_MODE_NIGHT_MASK = 0x30;
+ public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
+ public static final int UI_MODE_NIGHT_NO = 0x10;
+ public static final int UI_MODE_NIGHT_YES = 0x20;
+
+ /**
+ * Bit mask of the ui mode. Currently there are two fields:
+ * <p>The {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the
+ * device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED},
+ * {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK},
+ * or {@link #UI_MODE_TYPE_CAR}.
+ *
+ * <p>The {@link #UI_MODE_NIGHT_MASK} defines whether the screen
+ * is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED},
+ * {@link #UI_MODE_NIGHT_NO} or {@link #UI_MODE_NIGHT_YES}.
+ */
+ public int uiMode;
+
+ /**
+ * @hide Internal book-keeping.
+ */
+ public int seq;
/**
* Construct an invalid Configuration. You must call {@link #setToDefaults}
@@ -174,6 +219,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* Makes a deep copy suitable for modification.
*/
public Configuration(Configuration o) {
+ setTo(o);
+ }
+
+ public void setTo(Configuration o) {
fontScale = o.fontScale;
mcc = o.mcc;
mnc = o.mnc;
@@ -189,8 +238,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigationHidden = o.navigationHidden;
orientation = o.orientation;
screenLayout = o.screenLayout;
+ uiMode = o.uiMode;
+ seq = o.seq;
}
-
+
public String toString() {
StringBuilder sb = new StringBuilder(128);
sb.append("{ scale=");
@@ -217,6 +268,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(orientation);
sb.append(" layout=");
sb.append(screenLayout);
+ sb.append(" uiMode=");
+ sb.append(uiMode);
+ if (seq != 0) {
+ sb.append(" seq=");
+ sb.append(seq);
+ }
sb.append('}');
return sb.toString();
}
@@ -227,7 +284,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public void setToDefaults() {
fontScale = 1;
mcc = mnc = 0;
- locale = Locale.getDefault();
+ locale = null;
userSetLocale = false;
touchscreen = TOUCHSCREEN_UNDEFINED;
keyboard = KEYBOARD_UNDEFINED;
@@ -237,6 +294,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
screenLayout = SCREENLAYOUT_SIZE_UNDEFINED;
+ uiMode = UI_MODE_TYPE_UNDEFINED;
+ seq = 0;
}
/** {@hide} */
@@ -317,6 +376,22 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
screenLayout = delta.screenLayout;
}
+ if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ if ((delta.uiMode&UI_MODE_TYPE_MASK) != UI_MODE_TYPE_UNDEFINED) {
+ uiMode = (uiMode&~UI_MODE_TYPE_MASK)
+ | (delta.uiMode&UI_MODE_TYPE_MASK);
+ }
+ if ((delta.uiMode&UI_MODE_NIGHT_MASK) != UI_MODE_NIGHT_UNDEFINED) {
+ uiMode = (uiMode&~UI_MODE_NIGHT_MASK)
+ | (delta.uiMode&UI_MODE_NIGHT_MASK);
+ }
+ }
+
+ if (delta.seq != 0) {
+ seq = delta.seq;
+ }
return changed;
}
@@ -393,6 +468,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& screenLayout != delta.screenLayout) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
+ if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
+ && uiMode != delta.uiMode) {
+ changed |= ActivityInfo.CONFIG_UI_MODE;
+ }
return changed;
}
@@ -413,6 +492,35 @@ public final class Configuration implements Parcelable, Comparable<Configuration
}
/**
+ * @hide Return true if the sequence of 'other' is better than this. Assumes
+ * that 'this' is your current sequence and 'other' is a new one you have
+ * received some how and want to compare with what you have.
+ */
+ public boolean isOtherSeqNewer(Configuration other) {
+ if (other == null) {
+ // Sanity check.
+ return false;
+ }
+ if (other.seq == 0) {
+ // If the other sequence is not specified, then we must assume
+ // it is newer since we don't know any better.
+ return true;
+ }
+ if (seq == 0) {
+ // If this sequence is not specified, then we also consider the
+ // other is better. Yes we have a preference for other. Sue us.
+ return true;
+ }
+ int diff = other.seq - seq;
+ if (diff > 0x10000) {
+ // If there has been a sufficiently large jump, assume the
+ // sequence has wrapped around.
+ return false;
+ }
+ return diff > 0;
+ }
+
+ /**
* Parcelable methods
*/
public int describeContents() {
@@ -444,23 +552,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(navigationHidden);
dest.writeInt(orientation);
dest.writeInt(screenLayout);
+ dest.writeInt(uiMode);
+ dest.writeInt(seq);
}
- public static final Parcelable.Creator<Configuration> CREATOR
- = new Parcelable.Creator<Configuration>() {
- public Configuration createFromParcel(Parcel source) {
- return new Configuration(source);
- }
-
- public Configuration[] newArray(int size) {
- return new Configuration[size];
- }
- };
-
- /**
- * Construct this Configuration object, reading from the Parcel.
- */
- private Configuration(Parcel source) {
+ public void readFromParcel(Parcel source) {
fontScale = source.readFloat();
mcc = source.readInt();
mnc = source.readInt();
@@ -477,6 +573,26 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigationHidden = source.readInt();
orientation = source.readInt();
screenLayout = source.readInt();
+ uiMode = source.readInt();
+ seq = source.readInt();
+ }
+
+ public static final Parcelable.Creator<Configuration> CREATOR
+ = new Parcelable.Creator<Configuration>() {
+ public Configuration createFromParcel(Parcel source) {
+ return new Configuration(source);
+ }
+
+ public Configuration[] newArray(int size) {
+ return new Configuration[size];
+ }
+ };
+
+ /**
+ * Construct this Configuration object, reading from the Parcel.
+ */
+ private Configuration(Parcel source) {
+ readFromParcel(source);
}
public int compareTo(Configuration that) {
@@ -489,12 +605,18 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (n != 0) return n;
n = this.mnc - that.mnc;
if (n != 0) return n;
- n = this.locale.getLanguage().compareTo(that.locale.getLanguage());
- if (n != 0) return n;
- n = this.locale.getCountry().compareTo(that.locale.getCountry());
- if (n != 0) return n;
- n = this.locale.getVariant().compareTo(that.locale.getVariant());
- if (n != 0) return n;
+ if (this.locale == null) {
+ if (that.locale != null) return 1;
+ } else if (that.locale == null) {
+ return -1;
+ } else {
+ n = this.locale.getLanguage().compareTo(that.locale.getLanguage());
+ if (n != 0) return n;
+ n = this.locale.getCountry().compareTo(that.locale.getCountry());
+ if (n != 0) return n;
+ n = this.locale.getVariant().compareTo(that.locale.getVariant());
+ if (n != 0) return n;
+ }
n = this.touchscreen - that.touchscreen;
if (n != 0) return n;
n = this.keyboard - that.keyboard;
@@ -510,6 +632,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.orientation - that.orientation;
if (n != 0) return n;
n = this.screenLayout - that.screenLayout;
+ if (n != 0) return n;
+ n = this.uiMode - that.uiMode;
//if (n != 0) return n;
return n;
}
@@ -530,9 +654,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int hashCode() {
return ((int)this.fontScale) + this.mcc + this.mnc
- + this.locale.hashCode() + this.touchscreen
+ + (this.locale != null ? this.locale.hashCode() : 0)
+ + this.touchscreen
+ this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
+ this.navigation + this.navigationHidden
- + this.orientation + this.screenLayout;
+ + this.orientation + this.screenLayout + this.uiMode;
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 1c0ed36..0608cc0 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -39,6 +39,7 @@ import android.view.Display;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
+import java.util.Locale;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -1259,6 +1260,9 @@ public class Resources {
if (config != null) {
configChanges = mConfiguration.updateFrom(config);
}
+ if (mConfiguration.locale == null) {
+ mConfiguration.locale = Locale.getDefault();
+ }
if (metrics != null) {
mMetrics.setTo(metrics);
mMetrics.updateMetrics(mCompatibilityInfo,
@@ -1294,7 +1298,7 @@ public class Resources {
mConfiguration.touchscreen,
(int)(mMetrics.density*160), mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
- mConfiguration.screenLayout, sSdkVersion);
+ mConfiguration.screenLayout, mConfiguration.uiMode, sSdkVersion);
int N = mDrawableCache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 8fb82be..5e90b91 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -24,6 +24,7 @@ import android.util.SparseArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
+
import com.android.internal.util.XmlUtils;
/**
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 016ee7f..b0c149d 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.content.res;
import android.graphics.drawable.Drawable;
@@ -5,6 +21,7 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
+
import com.android.internal.util.XmlUtils;
import java.util.Arrays;
@@ -147,6 +164,42 @@ public class TypedArray {
}
/**
+ * @hide
+ * Retrieve the string value for the attribute at <var>index</var> that is
+ * not allowed to change with the given configurations.
+ *
+ * @param index Index of attribute to retrieve.
+ * @param allowedChangingConfigs Bit mask of configurations from
+ * ActivityInfo that are allowed to change.
+ *
+ * @return String holding string data. Any styling information is
+ * removed. Returns null if the attribute is not defined.
+ */
+ public String getNonConfigurationString(int index, int allowedChangingConfigs) {
+ index *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index+AssetManager.STYLE_TYPE];
+ if ((data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS]&~allowedChangingConfigs) != 0) {
+ return null;
+ }
+ if (type == TypedValue.TYPE_NULL) {
+ return null;
+ } else if (type == TypedValue.TYPE_STRING) {
+ return loadStringValueAt(index).toString();
+ }
+
+ TypedValue v = mValue;
+ if (getValueAt(index, v)) {
+ Log.w(Resources.TAG, "Converting to string: " + v);
+ CharSequence cs = v.coerceToString();
+ return cs != null ? cs.toString() : null;
+ }
+ Log.w(Resources.TAG, "getString of bad type: 0x"
+ + Integer.toHexString(type));
+ return null;
+ }
+
+ /**
* Retrieve the boolean value for the attribute at <var>index</var>.
*
* @param index Index of attribute to retrieve.
@@ -686,4 +739,4 @@ public class TypedArray {
public String toString() {
return Arrays.toString(mData);
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 6336678..ad1bfb2 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -17,6 +17,7 @@
package android.content.res;
import android.util.TypedValue;
+
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -59,7 +60,7 @@ final class XmlBlock {
if (mOpenCount == 0) {
nativeDestroy(mNative);
if (mAssets != null) {
- mAssets.xmlBlockGone();
+ mAssets.xmlBlockGone(hashCode());
}
}
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index cf30dd9..1469ea2 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -43,25 +43,43 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
try {
mCount = mBulkCursor.count();
mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls();
-
+
// Search for the rowID column index and set it for our parent
mColumns = mBulkCursor.getColumnNames();
- int length = mColumns.length;
- for (int i = 0; i < length; i++) {
- if (mColumns[i].equals("_id")) {
- mRowIdColumnIndex = i;
- break;
- }
- }
+ mRowIdColumnIndex = findRowIdColumnIndex(mColumns);
} catch (RemoteException ex) {
Log.e(TAG, "Setup failed because the remote process is dead");
}
}
/**
+ * Version of set() that does fewer Binder calls if the caller
+ * already knows BulkCursorToCursorAdaptor's properties.
+ */
+ public void set(IBulkCursor bulkCursor, int count, int idIndex) {
+ mBulkCursor = bulkCursor;
+ mColumns = null; // lazily retrieved
+ mCount = count;
+ mRowIdColumnIndex = idIndex;
+ }
+
+ /**
+ * Returns column index of "_id" column, or -1 if not found.
+ */
+ public static int findRowIdColumnIndex(String[] columnNames) {
+ int length = columnNames.length;
+ for (int i = 0; i < length; i++) {
+ if (columnNames[i].equals("_id")) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
* Gets a SelfDataChangeOberserver that can be sent to a remote
* process to receive change notifications over IPC.
- *
+ *
* @return A SelfContentObserver hooked up to this Cursor
*/
public synchronized IContentObserver getObserver() {
@@ -190,6 +208,14 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
@Override
public String[] getColumnNames() {
+ if (mColumns == null) {
+ try {
+ mColumns = mBulkCursor.getColumnNames();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to fetch column names because the remote process is dead");
+ return null;
+ }
+ }
return mColumns;
}
@@ -255,4 +281,3 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
}
}
}
-
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 05f8014..748eb99 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -143,7 +143,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
public void close() {
maybeUnregisterObserverProxy();
- mCursor.close();
+ mCursor.close();
}
public int requery(IContentObserver observer, CursorWindow window) {
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 99db81b..c756825 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -44,7 +44,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
/**
* Returns the starting position of this window within the entire
* Cursor's result set.
- *
+ *
* @return the starting position of this window within the entire
* Cursor's result set.
*/
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 4ca6601..9bfbb74 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -671,6 +671,102 @@ public class DatabaseUtils {
}
/**
+ * Reads a String out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getString(index));
+ }
+ }
+
+ /**
+ * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getLong(index));
+ }
+ }
+
+ /**
+ * Reads a Short out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getShort(index));
+ }
+ }
+
+ /**
+ * Reads a Integer out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getInt(index));
+ }
+ }
+
+ /**
+ * Reads a Float out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getFloat(index));
+ }
+ }
+
+ /**
+ * Reads a Double out of a column in a Cursor and writes it to a ContentValues.
+ * Adds nothing to the ContentValues if the column isn't present or if its value is null.
+ *
+ * @param cursor The cursor to read from
+ * @param column The column to read
+ * @param values The {@link ContentValues} to put the value into
+ */
+ public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
+ String column) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ if (!cursor.isNull(index)) {
+ values.put(column, cursor.getDouble(index));
+ }
+ }
+
+ /**
* This class allows users to do multiple inserts into a table but
* compile the SQL insert statement only once, which may increase
* performance.
diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java
index 24354fd..46790a3 100644
--- a/core/java/android/database/IBulkCursor.java
+++ b/core/java/android/database/IBulkCursor.java
@@ -27,11 +27,10 @@ import java.util.Map;
* This interface provides a low-level way to pass bulk cursor data across
* both process and language boundries. Application code should use the Cursor
* interface directly.
- *
+ *
* {@hide}
*/
-public interface IBulkCursor extends IInterface
-{
+public interface IBulkCursor extends IInterface {
/**
* Returns a BulkCursorWindow, which either has a reference to a shared
* memory segment with the rows, or an array of JSON strings.
@@ -60,7 +59,7 @@ public interface IBulkCursor extends IInterface
public boolean deleteRow(int position) throws RemoteException;
public void deactivate() throws RemoteException;
-
+
public void close() throws RemoteException;
public int requery(IContentObserver observer, CursorWindow window) throws RemoteException;
@@ -87,4 +86,3 @@ public interface IBulkCursor extends IInterface
static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
}
-
diff --git a/core/java/android/database/MergeCursor.java b/core/java/android/database/MergeCursor.java
index 7e91159..722d707 100644
--- a/core/java/android/database/MergeCursor.java
+++ b/core/java/android/database/MergeCursor.java
@@ -185,6 +185,7 @@ public class MergeCursor extends AbstractCursor
mCursors[i].deactivate();
}
}
+ super.deactivate();
}
@Override
@@ -194,6 +195,7 @@ public class MergeCursor extends AbstractCursor
if (mCursors[i] == null) continue;
mCursors[i].close();
}
+ super.close();
}
@Override
diff --git a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
new file mode 100644
index 0000000..8ac4c0f
--- /dev/null
+++ b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.database.sqlite;
+
+/**
+ * An exception that indicates that garbage-collector is finalizing a database object
+ * that is not explicitly closed
+ * @hide
+ */
+public class DatabaseObjectNotClosedException extends RuntimeException
+{
+ private static final String s = "Application did not close the cursor or database object " +
+ "that was opened here";
+
+ public DatabaseObjectNotClosedException()
+ {
+ super(s);
+ }
+}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index f64261c..1830f6c 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,25 +16,28 @@
package android.database.sqlite;
+import android.database.CursorWindow;
+
/**
- * An object create from a SQLiteDatabase that can be closed.
+ * An object created from a SQLiteDatabase that can be closed.
*/
-public abstract class SQLiteClosable {
+public abstract class SQLiteClosable {
private int mReferenceCount = 1;
private Object mLock = new Object();
+
protected abstract void onAllReferencesReleased();
- protected void onAllReferencesReleasedFromContainer(){}
-
+ protected void onAllReferencesReleasedFromContainer() {}
+
public void acquireReference() {
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
- "attempt to acquire a reference on a close SQLiteClosable");
+ "attempt to re-open an already-closed object: " + getObjInfo());
}
- mReferenceCount++;
+ mReferenceCount++;
}
}
-
+
public void releaseReference() {
synchronized(mLock) {
mReferenceCount--;
@@ -43,13 +46,32 @@ public abstract class SQLiteClosable {
}
}
}
-
+
public void releaseReferenceFromContainer() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleasedFromContainer();
}
- }
+ }
+ }
+
+ private String getObjInfo() {
+ StringBuilder buff = new StringBuilder();
+ buff.append(this.getClass().getName());
+ buff.append(" (");
+ if (this instanceof SQLiteDatabase) {
+ buff.append("database = ");
+ buff.append(((SQLiteDatabase)this).getPath());
+ } else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement ||
+ this instanceof SQLiteQuery) {
+ buff.append("mSql = ");
+ buff.append(((SQLiteProgram)this).mSql);
+ } else if (this instanceof CursorWindow) {
+ buff.append("mStartPos = ");
+ buff.append(((CursorWindow)this).getStartPosition());
+ }
+ buff.append(") ");
+ return buff.toString();
}
}
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
new file mode 100644
index 0000000..25aa9b3
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -0,0 +1,166 @@
+/*
+ * 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.database.sqlite;
+
+import android.util.Log;
+
+/**
+ * This class encapsulates compilation of sql statement and release of the compiled statement obj.
+ * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
+ * and it is released in one of the 2 following ways
+ * 1. when {@link SQLiteDatabase} object is closed.
+ * 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()}
+ * releaases this obj.
+ */
+/* package */ class SQLiteCompiledSql {
+
+ private static final String TAG = "SQLiteCompiledSql";
+
+ /** The database this program is compiled against. */
+ /* package */ SQLiteDatabase mDatabase;
+
+ /**
+ * Native linkage, do not modify. This comes from the database.
+ */
+ /* package */ int nHandle = 0;
+
+ /**
+ * Native linkage, do not modify. When non-0 this holds a reference to a valid
+ * sqlite3_statement object. It is only updated by the native code, but may be
+ * checked in this class when the database lock is held to determine if there
+ * is a valid native-side program or not.
+ */
+ /* package */ int nStatement = 0;
+
+ /** the following are for debugging purposes */
+ private String mSqlStmt = null;
+ private Throwable mStackTrace = null;
+
+ /** when in cache and is in use, this member is set */
+ private boolean mInUse = false;
+
+ /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
+ if (!db.isOpen()) {
+ throw new IllegalStateException("database " + db.getPath() + " already closed");
+ }
+ mDatabase = db;
+ mSqlStmt = sql;
+ mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
+ this.nHandle = db.mNativeHandle;
+ compile(sql, true);
+ }
+
+ /**
+ * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
+ * this method has been called previously without a call to close and forCompilation is set
+ * to false the previous compilation will be used. Setting forceCompilation to true will
+ * always re-compile the program and should be done if you pass differing SQL strings to this
+ * method.
+ *
+ * <P>Note: this method acquires the database lock.</P>
+ *
+ * @param sql the SQL string to compile
+ * @param forceCompilation forces the SQL to be recompiled in the event that there is an
+ * existing compiled SQL program already around
+ */
+ private void compile(String sql, boolean forceCompilation) {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
+ // Only compile if we don't have a valid statement already or the caller has
+ // explicitly requested a recompile.
+ if (forceCompilation) {
+ mDatabase.lock();
+ try {
+ // Note that the native_compile() takes care of destroying any previously
+ // existing programs before it compiles.
+ native_compile(sql);
+ } finally {
+ mDatabase.unlock();
+ }
+ }
+ }
+
+ /* package */ void releaseSqlStatement() {
+ // Note that native_finalize() checks to make sure that nStatement is
+ // non-null before destroying it.
+ if (nStatement != 0) {
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")");
+ }
+ try {
+ mDatabase.lock();
+ native_finalize();
+ nStatement = 0;
+ } finally {
+ mDatabase.unlock();
+ }
+ }
+ }
+
+ /**
+ * returns true if acquire() succeeds. false otherwise.
+ */
+ /* package */ synchronized boolean acquire() {
+ if (mInUse) {
+ // someone already has acquired it.
+ return false;
+ }
+ mInUse = true;
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "Acquired DbObj (id#" + nStatement + ") from DB cache");
+ }
+ return true;
+ }
+
+ /* package */ synchronized void release() {
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "Released DbObj (id#" + nStatement + ") back to DB cache");
+ }
+ mInUse = false;
+ }
+
+ /**
+ * Make sure that the native resource is cleaned up.
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (nStatement == 0) return;
+ // finalizer should NEVER get called
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
+ }
+ int len = mSqlStmt.length();
+ Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
+ "that you explicitly call close() on your cursor: " +
+ mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
+ releaseSqlStatement();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Compiles SQL into a SQLite program.
+ *
+ * <P>The database lock must be held when calling this method.
+ * @param sql The SQL to compile.
+ */
+ private final native void native_compile(String sql);
+ private final native void native_finalize();
+}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 70b9b83..6e5b3e1 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -62,9 +62,8 @@ public class SQLiteCursor extends AbstractWindowedCursor {
/** A mapping of column names to column indices, to speed up lookups */
private Map<String, Integer> mColumnNameMap;
- /** Used to find out where a cursor was allocated in case it never got
- * released. */
- private StackTraceElement[] mStackTraceElements;
+ /** Used to find out where a cursor was allocated in case it never got released. */
+ private Throwable mStackTrace;
/**
* mMaxRead is the max items that each cursor window reads
@@ -208,11 +207,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
String editTable, SQLiteQuery query) {
// The AbstractCursor constructor needs to do some setup.
super();
-
- if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
- mStackTraceElements = new Exception().getStackTrace();
- }
-
+ mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mDatabase = db;
mDriver = driver;
mEditTable = editTable;
@@ -582,21 +577,19 @@ public class SQLiteCursor extends AbstractWindowedCursor {
@Override
protected void finalize() {
try {
+ // if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
+ int len = mQuery.mSql.length();
+ Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
+ "database = " + mDatabase.getPath() + ", table = " + mEditTable +
+ ", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
+ mStackTrace);
close();
- String message = "Finalizing cursor " + this + " on " + mEditTable
- + " that has not been deactivated or closed";
- if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
- Log.d(TAG, message + "\nThis cursor was created in:");
- for (StackTraceElement ste : mStackTraceElements) {
- Log.d(TAG, " " + ste);
- }
- }
SQLiteDebug.notifyActiveCursorFinalized();
- throw new IllegalStateException(message);
} else {
if (Config.LOGV) {
- Log.v(TAG, "Finalizing cursor " + this + " on " + mEditTable);
+ Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
+ ", table = " + mEditTable + ", query = " + mQuery.mSql);
}
}
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9ebf5d9..fb5507d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,10 +16,14 @@
package android.database.sqlite;
+import com.google.android.collect.Maps;
+
+import android.app.ActivityThread;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
+import android.database.sqlite.SQLiteDebug.DbStats;
import android.os.Debug;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -27,15 +31,22 @@ import android.text.TextUtils;
import android.util.Config;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import java.io.File;
+import java.lang.ref.WeakReference;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Pattern;
/**
* Exposes methods to manage a SQLite database.
@@ -60,65 +71,61 @@ public class SQLiteDatabase extends SQLiteClosable {
/**
* Algorithms used in ON CONFLICT clause
* http://www.sqlite.org/lang_conflict.html
- * @hide
*/
- public enum ConflictAlgorithm {
- /**
- * When a constraint violation occurs, an immediate ROLLBACK occurs,
- * thus ending the current transaction, and the command aborts with a
- * return code of SQLITE_CONSTRAINT. If no transaction is active
- * (other than the implied transaction that is created on every command)
- * then this algorithm works the same as ABORT.
- */
- ROLLBACK("ROLLBACK"),
+ /**
+ * When a constraint violation occurs, an immediate ROLLBACK occurs,
+ * thus ending the current transaction, and the command aborts with a
+ * return code of SQLITE_CONSTRAINT. If no transaction is active
+ * (other than the implied transaction that is created on every command)
+ * then this algorithm works the same as ABORT.
+ */
+ public static final int CONFLICT_ROLLBACK = 1;
- /**
- * When a constraint violation occurs,no ROLLBACK is executed
- * so changes from prior commands within the same transaction
- * are preserved. This is the default behavior.
- */
- ABORT("ABORT"),
+ /**
+ * When a constraint violation occurs,no ROLLBACK is executed
+ * so changes from prior commands within the same transaction
+ * are preserved. This is the default behavior.
+ */
+ public static final int CONFLICT_ABORT = 2;
- /**
- * When a constraint violation occurs, the command aborts with a return
- * code SQLITE_CONSTRAINT. But any changes to the database that
- * the command made prior to encountering the constraint violation
- * are preserved and are not backed out.
- */
- FAIL("FAIL"),
+ /**
+ * When a constraint violation occurs, the command aborts with a return
+ * code SQLITE_CONSTRAINT. But any changes to the database that
+ * the command made prior to encountering the constraint violation
+ * are preserved and are not backed out.
+ */
+ public static final int CONFLICT_FAIL = 3;
- /**
- * When a constraint violation occurs, the one row that contains
- * the constraint violation is not inserted or changed.
- * But the command continues executing normally. Other rows before and
- * after the row that contained the constraint violation continue to be
- * inserted or updated normally. No error is returned.
- */
- IGNORE("IGNORE"),
+ /**
+ * When a constraint violation occurs, the one row that contains
+ * the constraint violation is not inserted or changed.
+ * But the command continues executing normally. Other rows before and
+ * after the row that contained the constraint violation continue to be
+ * inserted or updated normally. No error is returned.
+ */
+ public static final int CONFLICT_IGNORE = 4;
- /**
- * When a UNIQUE constraint violation occurs, the pre-existing rows that
- * are causing the constraint violation are removed prior to inserting
- * or updating the current row. Thus the insert or update always occurs.
- * The command continues executing normally. No error is returned.
- * If a NOT NULL constraint violation occurs, the NULL value is replaced
- * by the default value for that column. If the column has no default
- * value, then the ABORT algorithm is used. If a CHECK constraint
- * violation occurs then the IGNORE algorithm is used. When this conflict
- * resolution strategy deletes rows in order to satisfy a constraint,
- * it does not invoke delete triggers on those rows.
- * This behavior might change in a future release.
- */
- REPLACE("REPLACE");
+ /**
+ * When a UNIQUE constraint violation occurs, the pre-existing rows that
+ * are causing the constraint violation are removed prior to inserting
+ * or updating the current row. Thus the insert or update always occurs.
+ * The command continues executing normally. No error is returned.
+ * If a NOT NULL constraint violation occurs, the NULL value is replaced
+ * by the default value for that column. If the column has no default
+ * value, then the ABORT algorithm is used. If a CHECK constraint
+ * violation occurs then the IGNORE algorithm is used. When this conflict
+ * resolution strategy deletes rows in order to satisfy a constraint,
+ * it does not invoke delete triggers on those rows.
+ * This behavior might change in a future release.
+ */
+ public static final int CONFLICT_REPLACE = 5;
- private final String mValue;
- ConflictAlgorithm(String value) {
- mValue = value;
- }
- public String value() {
- return mValue;
- }
- }
+ /**
+ * use the following when no conflict action is specified.
+ */
+ public static final int CONFLICT_NONE = 0;
+ private static final String[] CONFLICT_VALUES = new String[]
+ {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
/**
* Maximum Length Of A LIKE Or GLOB Pattern
@@ -198,8 +205,31 @@ public class SQLiteDatabase extends SQLiteClosable {
private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000;
+ // The pattern we remove from database filenames before
+ // potentially logging them.
+ private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
+
private long mLastLockMessageTime = 0L;
+ // Things related to query logging/sampling for debugging
+ // slow/frequent queries during development. Always log queries
+ // which take (by default) 500ms+; shorter queries are sampled
+ // accordingly. Commit statements, which are typically slow, are
+ // logged together with the most recently executed SQL statement,
+ // for disambiguation. The 500ms value is configurable via a
+ // SystemProperty, but developers actively debugging database I/O
+ // should probably use the regular log tunable,
+ // LOG_SLOW_QUERIES_PROPERTY, defined below.
+ private static int sQueryLogTimeInMillis = 0; // lazily initialized
+ private static final int QUERY_LOG_SQL_LENGTH = 64;
+ private static final String COMMIT_SQL = "COMMIT;";
+ private final Random mRandom = new Random();
+ private String mLastSqlStatement = null;
+
+ // String prefix for slow database query EventLog records that show
+ // lock acquistions of the database.
+ /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
+
/** Used by native code, do not rename */
/* package */ int mNativeHandle = 0;
@@ -209,6 +239,9 @@ public class SQLiteDatabase extends SQLiteClosable {
/** The path for the database file */
private String mPath;
+ /** The anonymized path for the database file for logging purposes */
+ private String mPathForLogs = null; // lazily populated
+
/** The flags passed to open/create */
private int mFlags;
@@ -217,11 +250,41 @@ public class SQLiteDatabase extends SQLiteClosable {
private WeakHashMap<SQLiteClosable, Object> mPrograms;
- private final RuntimeException mLeakedException;
+ /**
+ * for each instance of this class, a cache is maintained to store
+ * the compiled query statement ids returned by sqlite database.
+ * key = sql statement with "?" for bind args
+ * value = {@link SQLiteCompiledSql}
+ * If an application opens the database and keeps it open during its entire life, then
+ * there will not be an overhead of compilation of sql statements by sqlite.
+ *
+ * why is this cache NOT static? because sqlite attaches compiledsql statements to the
+ * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is
+ * invoked.
+ *
+ * this cache has an upper limit of mMaxSqlCacheSize (settable by calling the method
+ * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because
+ * most of the apps don't use "?" syntax in their sql, caching is not useful for them.
+ */
+ /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap();
+ /**
+ * @hide
+ */
+ public static final int MAX_SQL_CACHE_SIZE = 250;
+ private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance
+ private int mCacheFullWarnings;
+ private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1;
+
+ /** maintain stats about number of cache hits and misses */
+ private int mNumCacheHits;
+ private int mNumCacheMisses;
- // package visible, since callers will access directly to minimize overhead in the case
- // that logging is not enabled.
- /* package */ final boolean mLogStats;
+ /** the following 2 members maintain the time when a database is opened and closed */
+ private String mTimeOpened = null;
+ private String mTimeClosed = null;
+
+ /** Used to find out where this object was created in case it never got closed. */
+ private Throwable mStackTrace = null;
// System property that enables logging of slow queries. Specify the threshold in ms.
private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold";
@@ -251,6 +314,9 @@ public class SQLiteDatabase extends SQLiteClosable {
@Override
protected void onAllReferencesReleased() {
if (isOpen()) {
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeClosed = getTime();
+ }
dbclose();
}
}
@@ -281,15 +347,18 @@ public class SQLiteDatabase extends SQLiteClosable {
private boolean mLockingEnabled = true;
/* package */ void onCorruption() {
+ Log.e(TAG, "Removing corrupt database: " + mPath);
+ EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
try {
// Close the database (if we can), which will cause subsequent operations to fail.
close();
} finally {
- Log.e(TAG, "Removing corrupt database: " + mPath);
- EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
// Delete the corrupt file. Don't re-create it now -- that would just confuse people
// -- but the next time someone tries to open it, they can set it up from scratch.
- new File(mPath).delete();
+ if (!mPath.equalsIgnoreCase(":memory")) {
+ // delete is only for non-memory database files
+ new File(mPath).delete();
+ }
}
}
@@ -432,6 +501,9 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
lockForced();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
boolean ok = false;
try {
// If this thread already had the lock then get out
@@ -476,6 +548,9 @@ public class SQLiteDatabase extends SQLiteClosable {
* are committed and rolled back.
*/
public void endTransaction() {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
if (!mLock.isHeldByCurrentThread()) {
throw new IllegalStateException("no transaction pending");
}
@@ -502,7 +577,7 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
if (mTransactionIsSuccessful) {
- execSQL("COMMIT;");
+ execSQL(COMMIT_SQL);
} else {
try {
execSQL("ROLLBACK;");
@@ -536,6 +611,9 @@ public class SQLiteDatabase extends SQLiteClosable {
* transaction is already marked as successful.
*/
public void setTransactionSuccessful() {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
if (!mLock.isHeldByCurrentThread()) {
throw new IllegalStateException("no transaction pending");
}
@@ -733,18 +811,30 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
- SQLiteDatabase db = null;
+ SQLiteDatabase sqliteDatabase = null;
try {
// Open the database.
- return new SQLiteDatabase(path, factory, flags);
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ sqliteDatabase.enableSqlTracing(path);
+ }
+ if (SQLiteDebug.DEBUG_SQL_TIME) {
+ sqliteDatabase.enableSqlProfiling(path);
+ }
} catch (SQLiteDatabaseCorruptException e) {
// Try to recover from this, if we can.
// TODO: should we do this for other open failures?
Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
EventLog.writeEvent(EVENT_DB_CORRUPT, path);
- new File(path).delete();
- return new SQLiteDatabase(path, factory, flags);
+ if (!path.equalsIgnoreCase(":memory")) {
+ // delete is only for non-memory database files
+ new File(path).delete();
+ }
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags);
}
+ ActiveDatabases.getInstance().mActiveDatabases.add(
+ new WeakReference<SQLiteDatabase>(sqliteDatabase));
+ return sqliteDatabase;
}
/**
@@ -781,16 +871,29 @@ public class SQLiteDatabase extends SQLiteClosable {
* Close the database.
*/
public void close() {
+ if (!isOpen()) {
+ return; // already closed
+ }
lock();
try {
closeClosable();
- releaseReference();
+ // close this database instance - regardless of its reference count value
+ onAllReferencesReleased();
} finally {
unlock();
}
}
private void closeClosable() {
+ /* deallocate all compiled sql statement objects from mCompiledQueries cache.
+ * this should be done before de-referencing all {@link SQLiteClosable} objects
+ * from this database object because calling
+ * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database
+ * to be closed. sqlite doesn't let a database close if there are
+ * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries.
+ */
+ deallocCachedSqlStatements();
+
Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<SQLiteClosable, Object> entry = iter.next();
@@ -814,6 +917,9 @@ public class SQLiteDatabase extends SQLiteClosable {
public int getVersion() {
SQLiteStatement prog = null;
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
try {
prog = new SQLiteStatement(this, "PRAGMA user_version;");
long version = prog.simpleQueryForLong();
@@ -841,6 +947,9 @@ public class SQLiteDatabase extends SQLiteClosable {
public long getMaximumSize() {
SQLiteStatement prog = null;
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
try {
prog = new SQLiteStatement(this,
"PRAGMA max_page_count;");
@@ -862,6 +971,9 @@ public class SQLiteDatabase extends SQLiteClosable {
public long setMaximumSize(long numBytes) {
SQLiteStatement prog = null;
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
try {
long pageSize = getPageSize();
long numPages = numBytes / pageSize;
@@ -887,6 +999,9 @@ public class SQLiteDatabase extends SQLiteClosable {
public long getPageSize() {
SQLiteStatement prog = null;
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
try {
prog = new SQLiteStatement(this,
"PRAGMA page_size;");
@@ -1023,6 +1138,9 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public SQLiteStatement compileStatement(String sql) throws SQLException {
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
try {
return new SQLiteStatement(this, sql);
} finally {
@@ -1102,6 +1220,9 @@ public class SQLiteDatabase extends SQLiteClosable {
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit) {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
String sql = SQLiteQueryBuilder.buildQueryString(
distinct, table, columns, selection, groupBy, having, orderBy, limit);
@@ -1208,6 +1329,9 @@ public class SQLiteDatabase extends SQLiteClosable {
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable) {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
long timeStart = 0;
if (Config.LOGV || mSlowQueryThreshold != -1) {
@@ -1225,9 +1349,9 @@ public class SQLiteDatabase extends SQLiteClosable {
if (Config.LOGV || mSlowQueryThreshold != -1) {
// Force query execution
+ int count = -1;
if (cursor != null) {
- cursor.moveToFirst();
- cursor.moveToPosition(-1);
+ count = cursor.getCount();
}
long duration = System.currentTimeMillis() - timeStart;
@@ -1237,7 +1361,7 @@ public class SQLiteDatabase extends SQLiteClosable {
"query (" + duration + " ms): " + driver.toString() + ", args are "
+ (selectionArgs != null
? TextUtils.join(",", selectionArgs)
- : "<null>"));
+ : "<null>") + ", count is " + count);
}
}
}
@@ -1283,7 +1407,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
- return insertWithOnConflict(table, nullColumnHack, values, null);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
@@ -1305,7 +1429,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
- return insertWithOnConflict(table, nullColumnHack, values, null);
+ return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}
/**
@@ -1322,7 +1446,7 @@ public class SQLiteDatabase extends SQLiteClosable {
public long replace(String table, String nullColumnHack, ContentValues initialValues) {
try {
return insertWithOnConflict(table, nullColumnHack, initialValues,
- ConflictAlgorithm.REPLACE);
+ CONFLICT_REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + initialValues, e);
return -1;
@@ -1344,7 +1468,7 @@ public class SQLiteDatabase extends SQLiteClosable {
public long replaceOrThrow(String table, String nullColumnHack,
ContentValues initialValues) throws SQLException {
return insertWithOnConflict(table, nullColumnHack, initialValues,
- ConflictAlgorithm.REPLACE);
+ CONFLICT_REPLACE);
}
/**
@@ -1357,12 +1481,14 @@ public class SQLiteDatabase extends SQLiteClosable {
* @param initialValues this map contains the initial column values for the
* row. The keys should be the column names and the values the
* column values
- * @param algorithm {@link ConflictAlgorithm} for insert conflict resolver
- * @return the row ID of the newly inserted row, or -1 if an error occurred
- * @hide
+ * @param conflictAlgorithm for insert conflict resolver
+ * @return the row ID of the newly inserted row
+ * OR the primary key of the existing row if the input param 'conflictAlgorithm' =
+ * {@link #CONFLICT_IGNORE}
+ * OR -1 if any error
*/
public long insertWithOnConflict(String table, String nullColumnHack,
- ContentValues initialValues, ConflictAlgorithm algorithm) {
+ ContentValues initialValues, int conflictAlgorithm) {
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
@@ -1370,10 +1496,7 @@ public class SQLiteDatabase extends SQLiteClosable {
// Measurements show most sql lengths <= 152
StringBuilder sql = new StringBuilder(152);
sql.append("INSERT");
- if (algorithm != null) {
- sql.append(" OR ");
- sql.append(algorithm.value());
- }
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
// Measurements show most values lengths < 40
@@ -1457,10 +1580,10 @@ public class SQLiteDatabase extends SQLiteClosable {
* whereClause.
*/
public int delete(String table, String whereClause, String[] whereArgs) {
+ lock();
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
- lock();
SQLiteStatement statement = null;
try {
statement = compileStatement("DELETE FROM " + table
@@ -1473,7 +1596,6 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
statement.execute();
- statement.close();
return lastChangeCount();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
@@ -1497,7 +1619,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @return the number of rows affected
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
- return updateWithOnConflict(table, values, whereClause, whereArgs, null);
+ return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
/**
@@ -1508,28 +1630,18 @@ public class SQLiteDatabase extends SQLiteClosable {
* valid value that will be translated to NULL.
* @param whereClause the optional WHERE clause to apply when updating.
* Passing null will update all rows.
- * @param algorithm {@link ConflictAlgorithm} for update conflict resolver
+ * @param conflictAlgorithm for update conflict resolver
* @return the number of rows affected
- * @hide
*/
public int updateWithOnConflict(String table, ContentValues values,
- String whereClause, String[] whereArgs, ConflictAlgorithm algorithm) {
- if (!isOpen()) {
- throw new IllegalStateException("database not open");
- }
-
+ String whereClause, String[] whereArgs, int conflictAlgorithm) {
if (values == null || values.size() == 0) {
throw new IllegalArgumentException("Empty values");
}
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
- if (algorithm != null) {
- sql.append("OR ");
- sql.append(algorithm.value());
- sql.append(" ");
- }
-
+ sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
@@ -1551,6 +1663,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
SQLiteStatement statement = null;
try {
statement = compileStatement(sql.toString());
@@ -1575,7 +1690,6 @@ public class SQLiteDatabase extends SQLiteClosable {
// Run the program and then cleanup
statement.execute();
- statement.close();
int numChangedRows = lastChangeCount();
if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql);
@@ -1603,9 +1717,12 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql) throws SQLException {
- boolean logStats = mLogStats;
- long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+ long timeStart = SystemClock.uptimeMillis();
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX);
try {
native_execSQL(sql);
} catch (SQLiteDatabaseCorruptException e) {
@@ -1614,8 +1731,14 @@ public class SQLiteDatabase extends SQLiteClosable {
} finally {
unlock();
}
- if (logStats) {
- logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
+
+ // Log commit statements along with the most recently executed
+ // SQL statement for disambiguation. Note that instance
+ // equality to COMMIT_SQL is safe here.
+ if (sql == COMMIT_SQL) {
+ logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL);
+ } else {
+ logTimeStat(sql, timeStart, null);
}
}
@@ -1632,10 +1755,11 @@ public class SQLiteDatabase extends SQLiteClosable {
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
-
- boolean logStats = mLogStats;
- long timeStart = logStats ? SystemClock.elapsedRealtime() : 0;
+ long timeStart = SystemClock.uptimeMillis();
lock();
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
SQLiteStatement statement = null;
try {
statement = compileStatement(sql);
@@ -1655,21 +1779,14 @@ public class SQLiteDatabase extends SQLiteClosable {
}
unlock();
}
- if (logStats) {
- logTimeStat(false /* not a read */, timeStart, SystemClock.elapsedRealtime());
- }
+ logTimeStat(sql, timeStart);
}
@Override
protected void finalize() {
if (isOpen()) {
- if (mPrograms.isEmpty()) {
- Log.e(TAG, "Leak found", mLeakedException);
- } else {
- IllegalStateException leakProgram = new IllegalStateException(
- "mPrograms size " + mPrograms.size(), mLeakedException);
- Log.e(TAG, "Leak found", leakProgram);
- }
+ Log.e(TAG, "close() was never explicitly called on database '" +
+ mPath + "' ", mStackTrace);
closeClosable();
onAllReferencesReleased();
}
@@ -1689,23 +1806,30 @@ public class SQLiteDatabase extends SQLiteClosable {
}
mFlags = flags;
mPath = path;
- mLogStats = "1".equals(android.os.SystemProperties.get("db.logstats"));
mSlowQueryThreshold = SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1);
-
- mLeakedException = new IllegalStateException(path +
- " SQLiteDatabase created and never closed");
+ mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mFactory = factory;
dbopen(mPath, mFlags);
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeOpened = getTime();
+ }
mPrograms = new WeakHashMap<SQLiteClosable,Object>();
try {
setLocale(Locale.getDefault());
} catch (RuntimeException e) {
Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e);
dbclose();
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeClosed = getTime();
+ }
throw e;
}
}
+ private String getTime() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
+ }
+
/**
* return whether the DB is opened as read only.
* @return true if DB is opened as read only
@@ -1734,8 +1858,83 @@ public class SQLiteDatabase extends SQLiteClosable {
return mPath;
}
- /* package */ void logTimeStat(boolean read, long begin, long end) {
- EventLog.writeEvent(EVENT_DB_OPERATION, mPath, read ? 0 : 1, end - begin);
+ /* package */ void logTimeStat(String sql, long beginMillis) {
+ 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;
+
+ // 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.
+
+ int samplePercent;
+ long durationMillis = SystemClock.uptimeMillis() - beginMillis;
+ if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) {
+ // The common case is locks being uncontended. Don't log those,
+ // even at 1%, which is our default below.
+ return;
+ }
+ if (sQueryLogTimeInMillis == 0) {
+ sQueryLogTimeInMillis = SystemProperties.getInt("db.db_operation.threshold_ms", 500);
+ }
+ if (durationMillis >= sQueryLogTimeInMillis) {
+ samplePercent = 100;
+ } else {;
+ samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
+ if (mRandom.nextInt(100) >= samplePercent) return;
+ }
+
+ // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null. We wait to do
+ // it here so we avoid allocating in the common case.
+ 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
+ // current thread is an application main thread. This parameter tells
+ // us whether an event loop is blocked, and if so, which app it is.
+ //
+ // Sadly, there's no fast way to determine app name if this is *not* a
+ // main thread, or when we are invoked via Binder (e.g. ContentProvider).
+ // Hopefully the full path to the database will be informative enough.
+
+ String blockingPackage = ActivityThread.currentPackageName();
+ if (blockingPackage == null) blockingPackage = "";
+
+ EventLog.writeEvent(
+ EVENT_DB_OPERATION,
+ getPathForLogs(),
+ sql,
+ durationMillis,
+ blockingPackage,
+ samplePercent);
+ }
+
+ /**
+ * Removes email addresses from database filenames before they're
+ * logged to the EventLog where otherwise apps could potentially
+ * read them.
+ */
+ private String getPathForLogs() {
+ if (mPathForLogs != null) {
+ return mPathForLogs;
+ }
+ if (mPath == null) {
+ return null;
+ }
+ if (mPath.indexOf('@') == -1) {
+ mPathForLogs = mPath;
+ } else {
+ mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY");
+ }
+ return mPathForLogs;
}
/**
@@ -1754,6 +1953,267 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
+ /*
+ * ============================================================================
+ *
+ * The following methods deal with compiled-sql cache
+ * ============================================================================
+ */
+ /**
+ * adds the given sql and its compiled-statement-id-returned-by-sqlite to the
+ * cache of compiledQueries attached to 'this'.
+ *
+ * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql,
+ * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
+ * mapping is NOT replaced with the new mapping).
+ */
+ /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) {
+ if (mMaxSqlCacheSize == 0) {
+ // for this database, there is no cache of compiled sql.
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql);
+ }
+ return;
+ }
+
+ SQLiteCompiledSql compiledSql = null;
+ synchronized(mCompiledQueries) {
+ // don't insert the new mapping if a mapping already exists
+ compiledSql = mCompiledQueries.get(sql);
+ if (compiledSql != null) {
+ return;
+ }
+ // add this <sql, compiledStatement> to the cache
+ if (mCompiledQueries.size() == mMaxSqlCacheSize) {
+ /*
+ * cache size of {@link #mMaxSqlCacheSize} is not enough for this app.
+ * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times
+ * chances are it is NOT using ? for bindargs - so caching is useless.
+ * TODO: either let the callers set max cchesize for their app, or intelligently
+ * figure out what should be cached for a given app.
+ */
+ if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) {
+ Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
+ getPath() + "; i.e., NO space for this sql statement in cache: " +
+ sql + ". Please change your sql statements to use '?' for " +
+ "bindargs, instead of using actual values");
+ }
+ // don't add this entry to cache
+ } else {
+ // cache is NOT full. add this to cache.
+ mCompiledQueries.put(sql, compiledStatement);
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" +
+ mCompiledQueries.size() + "|" + sql);
+ }
+ }
+ }
+ return;
+ }
+
+
+ private void deallocCachedSqlStatements() {
+ synchronized (mCompiledQueries) {
+ for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) {
+ compiledSql.releaseSqlStatement();
+ }
+ mCompiledQueries.clear();
+ }
+ }
+
+ /**
+ * from the compiledQueries cache, returns the compiled-statement-id for the given sql.
+ * returns null, if not found in the cache.
+ */
+ /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) {
+ SQLiteCompiledSql compiledStatement = null;
+ boolean cacheHit;
+ synchronized(mCompiledQueries) {
+ if (mMaxSqlCacheSize == 0) {
+ // for this database, there is no cache of compiled sql.
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|cache NOT found|" + getPath());
+ }
+ return null;
+ }
+ cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null;
+ }
+ if (cacheHit) {
+ mNumCacheHits++;
+ } else {
+ mNumCacheMisses++;
+ }
+
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ Log.v(TAG, "|cache_stats|" +
+ getPath() + "|" + mCompiledQueries.size() +
+ "|" + mNumCacheHits + "|" + mNumCacheMisses +
+ "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql);
+ }
+ return compiledStatement;
+ }
+
+ /**
+ * returns true if the given sql is cached in compiled-sql cache.
+ * @hide
+ */
+ public boolean isInCompiledSqlCache(String sql) {
+ synchronized(mCompiledQueries) {
+ return mCompiledQueries.containsKey(sql);
+ }
+ }
+
+ /**
+ * purges the given sql from the compiled-sql cache.
+ * @hide
+ */
+ public void purgeFromCompiledSqlCache(String sql) {
+ synchronized(mCompiledQueries) {
+ mCompiledQueries.remove(sql);
+ }
+ }
+
+ /**
+ * remove everything from the compiled sql cache
+ * @hide
+ */
+ public void resetCompiledSqlCache() {
+ synchronized(mCompiledQueries) {
+ mCompiledQueries.clear();
+ }
+ }
+
+ /**
+ * return the current maxCacheSqlCacheSize
+ * @hide
+ */
+ public synchronized int getMaxSqlCacheSize() {
+ return mMaxSqlCacheSize;
+ }
+
+ /**
+ * set the max size of the compiled sql cache for this database after purging the cache.
+ * (size of the cache = number of compiled-sql-statements stored in the cache).
+ *
+ * max cache size can ONLY be increased from its current size (default = 0).
+ * if this method is called with smaller size than the current value of mMaxSqlCacheSize,
+ * then IllegalStateException is thrown
+ *
+ * synchronized because we don't want t threads to change cache size at the same time.
+ * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE)
+ * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or
+ * < the value set with previous setMaxSqlCacheSize() call.
+ *
+ * @hide
+ */
+ public synchronized void setMaxSqlCacheSize(int cacheSize) {
+ if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
+ throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE);
+ } else if (cacheSize < mMaxSqlCacheSize) {
+ throw new IllegalStateException("cannot set cacheSize to a value less than the value " +
+ "set with previous setMaxSqlCacheSize() call.");
+ }
+ mMaxSqlCacheSize = cacheSize;
+ }
+
+ static class ActiveDatabases {
+ private static final ActiveDatabases activeDatabases = new ActiveDatabases();
+ private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases =
+ new HashSet<WeakReference<SQLiteDatabase>>();
+ private ActiveDatabases() {} // disable instantiation of this class
+ static ActiveDatabases getInstance() {return activeDatabases;}
+ }
+
+ /**
+ * this method is used to collect data about ALL open databases in the current process.
+ * bugreport is a user of this data.
+ */
+ /* package */ static ArrayList<DbStats> getDbStats() {
+ ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
+ for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
+ SQLiteDatabase db = w.get();
+ if (db == null || !db.isOpen()) {
+ continue;
+ }
+ // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
+ int lookasideUsed = db.native_getDbLookaside();
+
+ // get the lastnode of the dbname
+ String path = db.getPath();
+ int indx = path.lastIndexOf("/");
+ String lastnode = path.substring((indx != -1) ? ++indx : 0);
+
+ // get list of attached dbs and for each db, get its size and pagesize
+ ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+ if (attachedDbs == null) {
+ continue;
+ }
+ for (int i = 0; i < attachedDbs.size(); i++) {
+ Pair<String, String> p = attachedDbs.get(i);
+ long pageCount = getPragmaVal(db, p.first + ".page_count;");
+
+ // first entry in the attached db list is always the main database
+ // don't worry about prefixing the dbname with "main"
+ String dbName;
+ if (i == 0) {
+ dbName = lastnode;
+ } else {
+ // lookaside is only relevant for the main db
+ lookasideUsed = 0;
+ dbName = " (attached) " + p.first;
+ // if the attached db has a path, attach the lastnode from the path to above
+ if (p.second.trim().length() > 0) {
+ int idx = p.second.lastIndexOf("/");
+ dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+ }
+ }
+ if (pageCount > 0) {
+ dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
+ lookasideUsed));
+ }
+ }
+ }
+ return dbStatsList;
+ }
+
+ /**
+ * get the specified pragma value from sqlite for the specified database.
+ * only handles pragma's that return int/long.
+ * NO JAVA locks are held in this method.
+ * TODO: use this to do all pragma's in this class
+ */
+ private static long getPragmaVal(SQLiteDatabase db, String pragma) {
+ if (!db.isOpen()) {
+ return 0;
+ }
+ SQLiteStatement prog = null;
+ try {
+ prog = new SQLiteStatement(db, "PRAGMA " + pragma);
+ long val = prog.simpleQueryForLong();
+ return val;
+ } finally {
+ if (prog != null) prog.close();
+ }
+ }
+
+ /**
+ * returns list of full pathnames of all attached databases
+ * including the main database
+ * TODO: move this to {@link DatabaseUtils}
+ */
+ private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
+ if (!dbObj.isOpen()) {
+ return null;
+ }
+ ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
+ Cursor c = dbObj.rawQuery("pragma database_list;", null);
+ while (c.moveToNext()) {
+ attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+ }
+ c.close();
+ return attachedDbs;
+ }
+
/**
* Native call to open the database.
*
@@ -1762,6 +2222,23 @@ public class SQLiteDatabase extends SQLiteClosable {
private native void dbopen(String path, int flags);
/**
+ * Native call to setup tracing of all sql statements
+ *
+ * @param path the full path to the database
+ */
+ private native void enableSqlTracing(String path);
+
+ /**
+ * Native call to setup profiling of all sql statements.
+ * currently, sqlite's profiling = printing of execution-time
+ * (wall-clock time) of each of the sql statements, as they
+ * are executed.
+ *
+ * @param path the full path to the database
+ */
+ private native void enableSqlProfiling(String path);
+
+ /**
* Native call to execute a raw SQL statement. {@link #lock} must be held
* when calling this method.
*
@@ -1790,4 +2267,11 @@ public class SQLiteDatabase extends SQLiteClosable {
* @return the number of changes made in the last statement executed.
*/
/* package */ native int lastChangeCount();
+
+ /**
+ * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
+ * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
+ * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
+ */
+ private native int native_getDbLookaside();
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 84d8879..89c3f96 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -16,12 +16,13 @@
package android.database.sqlite;
-import android.util.Config;
+import java.util.ArrayList;
+
import android.util.Log;
/**
* Provides debugging info about all SQLite databases running in the current process.
- *
+ *
* {@hide}
*/
public final class SQLiteDebug {
@@ -32,6 +33,19 @@ public final class SQLiteDebug {
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
+ * Controls the printing of wall-clock time taken to execute SQL statements
+ * as they are executed.
+ */
+ public static final boolean DEBUG_SQL_TIME =
+ Log.isLoggable("SQLiteTime", Log.VERBOSE);
+
+ /**
+ * Controls the printing of compiled-sql-statement cache stats.
+ */
+ public static final boolean DEBUG_SQL_CACHE =
+ Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
+
+ /**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
@@ -39,31 +53,102 @@ public final class SQLiteDebug {
Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE);
/**
- * Controls the tracking of time spent holding the database lock.
+ * Controls the tracking of time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING =
Log.isLoggable("SQLiteLockTime", Log.VERBOSE);
/**
- * Controls the printing of stack traces when tracking the time spent holding the database lock.
+ * Controls the printing of stack traces when tracking the time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
/**
* Contains statistics about the active pagers in the current process.
- *
+ *
* @see #getPagerStats(PagerStats)
*/
public static class PagerStats {
- /** The total number of bytes in all pagers in the current process */
+ /** The total number of bytes in all pagers in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public long totalBytes;
- /** The number of bytes in referenced pages in all pagers in the current process */
+ /** The number of bytes in referenced pages in all pagers in the current process
+ * @deprecated not used any longer
+ * */
+ @Deprecated
public long referencedBytes;
- /** The number of bytes in all database files opened in the current process */
+ /** The number of bytes in all database files opened in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public long databaseBytes;
- /** The number of pagers opened in the current process */
+ /** The number of pagers opened in the current process
+ * @deprecated not used any longer
+ */
+ @Deprecated
public int numPagers;
+
+ /** the current amount of memory checked out by sqlite using sqlite3_malloc().
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int memoryUsed;
+
+ /** the number of bytes of page cache allocation which could not be sattisfied by the
+ * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
+ * The returned value includes allocations that overflowed because they where too large
+ * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
+ * that overflowed because no space was left in the page cache.
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int pageCacheOverflo;
+
+ /** records the largest memory allocation request handed to sqlite3.
+ * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+ */
+ public int largestMemAlloc;
+
+ /** a list of {@link DbStats} - one for each main database opened by the applications
+ * running on the android device
+ */
+ public ArrayList<DbStats> dbStats;
+ }
+
+ /**
+ * contains statistics about a database
+ */
+ public static class DbStats {
+ /** name of the database */
+ public String dbName;
+
+ /** the page size for the database */
+ public long pageSize;
+
+ /** the database size */
+ public long dbSize;
+
+ /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
+ public int lookaside;
+
+ public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+ this.dbName = dbName;
+ this.pageSize = pageSize;
+ dbSize = (pageCount * pageSize) / 1024;
+ this.lookaside = lookaside;
+ }
+ }
+
+ /**
+ * return all pager and database stats for the current process.
+ * @return {@link PagerStats}
+ */
+ public static PagerStats getDatabaseInfo() {
+ PagerStats stats = new PagerStats();
+ getPagerStats(stats);
+ stats.dbStats = SQLiteDatabase.getDbStats();
+ return stats;
}
/**
@@ -76,13 +161,13 @@ public final class SQLiteDebug {
* @return The size of the SQLite heap in bytes.
*/
public static native long getHeapSize();
-
+
/**
* Returns the amount of allocated memory in the SQLite heap.
* @return The allocated size in bytes.
*/
public static native long getHeapAllocatedSize();
-
+
/**
* Returns the amount of free memory in the SQLite heap.
* @return The freed size in bytes.
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index ca64aca..2144fc3 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -18,7 +18,6 @@ package android.database.sqlite;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.util.Log;
/**
* A cursor driver that uses the given query directly.
@@ -26,7 +25,6 @@ import android.util.Log;
* @hide
*/
public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
- private static String TAG = "SQLiteDirectCursorDriver";
private String mEditTable;
private SQLiteDatabase mDatabase;
private Cursor mCursor;
@@ -36,11 +34,6 @@ public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
mDatabase = db;
mEditTable = editTable;
- //TODO remove all callers that end in ; and remove this check
- if (sql.charAt(sql.length() - 1) == ';') {
- Log.w(TAG, "Found SQL string that ends in ; -- " + sql);
- sql = sql.substring(0, sql.length() - 1);
- }
mSql = sql;
}
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 9e85452..89a5f0d 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -22,99 +22,150 @@ import android.util.Log;
* A base class for compiled SQLite programs.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
+
private static final String TAG = "SQLiteProgram";
- /** The database this program is compiled against. */
+ /** The database this program is compiled against.
+ * @deprecated do not use this
+ */
+ @Deprecated
protected SQLiteDatabase mDatabase;
+ /** The SQL used to create this query */
+ /* package */ final String mSql;
+
/**
* Native linkage, do not modify. This comes from the database and should not be modified
* in here or in the native code.
+ * @deprecated do not use this
*/
+ @Deprecated
protected int nHandle = 0;
/**
- * Native linkage, do not modify. When non-0 this holds a reference to a valid
- * sqlite3_statement object. It is only updated by the native code, but may be
- * checked in this class when the database lock is held to determine if there
- * is a valid native-side program or not.
+ * the SQLiteCompiledSql object for the given sql statement.
*/
- protected int nStatement = 0;
+ private SQLiteCompiledSql mCompiledSql;
/**
- * Used to find out where a cursor was allocated in case it never got
- * released.
+ * SQLiteCompiledSql statement id is populated with the corresponding object from the above
+ * member. This member is used by the native_bind_* methods
+ * @deprecated do not use this
*/
- private StackTraceElement[] mStackTraceElements;
-
+ @Deprecated
+ protected int nStatement = 0;
+
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- mStackTraceElements = new Exception().getStackTrace();
- }
-
mDatabase = db;
+ mSql = sql.trim();
db.acquireReference();
db.addSQLiteClosable(this);
this.nHandle = db.mNativeHandle;
- compile(sql, false);
- }
-
+
+ // only cache CRUD statements
+ String prefixSql = mSql.substring(0, 6);
+ if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") &&
+ !prefixSql.equalsIgnoreCase("REPLAC") &&
+ !prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) {
+ mCompiledSql = new SQLiteCompiledSql(db, sql);
+ nStatement = mCompiledSql.nStatement;
+ // since it is not in the cache, no need to acquire() it.
+ return;
+ }
+
+ // it is not pragma
+ mCompiledSql = db.getCompiledStatementForSql(sql);
+ if (mCompiledSql == null) {
+ // create a new compiled-sql obj
+ mCompiledSql = new SQLiteCompiledSql(db, sql);
+
+ // add it to the cache of compiled-sqls
+ // but before adding it and thus making it available for anyone else to use it,
+ // make sure it is acquired by me.
+ mCompiledSql.acquire();
+ db.addToCompiledQueries(sql, mCompiledSql);
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement +
+ ") for sql: " + sql);
+ }
+ } else {
+ // it is already in compiled-sql cache.
+ // try to acquire the object.
+ if (!mCompiledSql.acquire()) {
+ int last = mCompiledSql.nStatement;
+ // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
+ // we can't have two different SQLiteProgam objects can't share the same
+ // CompiledSql object. create a new one.
+ // finalize it when I am done with it in "this" object.
+ mCompiledSql = new SQLiteCompiledSql(db, sql);
+ if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
+ Log.v(TAG, "** possible bug ** Created NEW DbObj (id#" +
+ mCompiledSql.nStatement +
+ ") because the previously created DbObj (id#" + last +
+ ") was not released for sql:" + sql);
+ }
+ // since it is not in the cache, no need to acquire() it.
+ }
+ }
+ nStatement = mCompiledSql.nStatement;
+ }
+
@Override
protected void onAllReferencesReleased() {
- // Note that native_finalize() checks to make sure that nStatement is
- // non-null before destroying it.
- native_finalize();
+ releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
-
+
@Override
- protected void onAllReferencesReleasedFromContainer(){
- // Note that native_finalize() checks to make sure that nStatement is
- // non-null before destroying it.
- native_finalize();
- mDatabase.releaseReference();
+ protected void onAllReferencesReleasedFromContainer() {
+ releaseCompiledSqlIfNotInCache();
+ mDatabase.releaseReference();
+ }
+
+ private void releaseCompiledSqlIfNotInCache() {
+ if (mCompiledSql == null) {
+ return;
+ }
+ synchronized(mDatabase.mCompiledQueries) {
+ if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
+ // it is NOT in compiled-sql cache. i.e., responsibility of
+ // releasing this statement is on me.
+ mCompiledSql.releaseSqlStatement();
+ mCompiledSql = null;
+ nStatement = 0;
+ } else {
+ // it is in compiled-sql cache. reset its CompiledSql#mInUse flag
+ mCompiledSql.release();
+ }
+ }
}
/**
* Returns a unique identifier for this program.
- *
+ *
* @return a unique identifier for this program
*/
public final int getUniqueId() {
return nStatement;
}
+ /* package */ String getSqlString() {
+ return mSql;
+ }
+
/**
- * Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
- * this method has been called previously without a call to close and forCompilation is set
- * to false the previous compilation will be used. Setting forceCompilation to true will
- * always re-compile the program and should be done if you pass differing SQL strings to this
- * method.
- *
- * <P>Note: this method acquires the database lock.</P>
+ * @deprecated This method is deprecated and must not be used.
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
+ @Deprecated
protected void compile(String sql, boolean forceCompilation) {
- // Only compile if we don't have a valid statement already or the caller has
- // explicitly requested a recompile.
- if (nStatement == 0 || forceCompilation) {
- mDatabase.lock();
- try {
- // Note that the native_compile() takes care of destroying any previously
- // existing programs before it compiles.
- acquireReference();
- native_compile(sql);
- } finally {
- releaseReference();
- mDatabase.unlock();
- }
- }
- }
-
+ // TODO is there a need for this?
+ }
+
/**
* Bind a NULL value to this statement. The value remains bound until
* {@link #clearBindings} is called.
@@ -122,6 +173,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param index The 1-based index to the parameter to bind null to
*/
public void bindNull(int index) {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_bind_null(index);
@@ -138,6 +192,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindLong(int index, long value) {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_bind_long(index, value);
@@ -154,6 +211,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_bind_double(index, value);
@@ -173,6 +233,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_bind_string(index, value);
@@ -192,6 +255,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_bind_blob(index, value);
@@ -204,6 +270,9 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
acquireReference();
try {
native_clear_bindings();
@@ -216,42 +285,31 @@ public abstract class SQLiteProgram extends SQLiteClosable {
* Release this program's resources, making it invalid.
*/
public void close() {
+ if (!mDatabase.isOpen()) {
+ return;
+ }
mDatabase.lock();
try {
releaseReference();
} finally {
mDatabase.unlock();
- }
- }
-
- /**
- * Make sure that the native resource is cleaned up.
- */
- @Override
- protected void finalize() {
- if (nStatement != 0) {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- String message = "Finalizing " + this +
- " that has not been closed";
-
- Log.d(TAG, message + "\nThis cursor was created in:");
- for (StackTraceElement ste : mStackTraceElements) {
- Log.d(TAG, " " + ste);
- }
- }
- // when in finalize() it is already removed from weakhashmap
- // so it is safe to not removed itself from db
- onAllReferencesReleasedFromContainer();
}
}
/**
+ * @deprecated This method is deprecated and must not be used.
* Compiles SQL into a SQLite program.
- *
+ *
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
+ @Deprecated
protected final native void native_compile(String sql);
+
+ /**
+ * @deprecated This method is deprecated and must not be used.
+ */
+ @Deprecated
protected final native void native_finalize();
protected final native void native_bind_null(int index);
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index cdd9f86..43d2fac 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -30,9 +30,6 @@ public class SQLiteQuery extends SQLiteProgram {
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
- /** The SQL used to create this query */
- private String mQuery;
-
/** Args to bind on requery */
private String[] mBindArgs;
@@ -49,27 +46,25 @@ public class SQLiteQuery extends SQLiteProgram {
super(db, query);
mOffsetIndex = offsetIndex;
- mQuery = query;
mBindArgs = bindArgs;
}
/**
* Reads rows into a buffer. This method acquires the database lock.
- *
+ *
* @param window The window to fill into
* @return number of total rows in the query
*/
- /* package */ int fillWindow(CursorWindow window,
+ /* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
-
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
+ mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
window.acquireReference();
- // if the start pos is not equal to 0, then most likely window is
+ // if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
@@ -77,12 +72,9 @@ public class SQLiteQuery extends SQLiteProgram {
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.d(TAG, "fillWindow(): " + mQuery);
- }
- if (logStats) {
- mDatabase.logTimeStat(true /* read */, startTime,
- SystemClock.elapsedRealtime());
+ Log.d(TAG, "fillWindow(): " + mSql);
}
+ mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
@@ -91,7 +83,7 @@ public class SQLiteQuery extends SQLiteProgram {
mDatabase.onCorruption();
throw e;
} finally {
- window.releaseReference();
+ window.releaseReference();
}
} finally {
releaseReference();
@@ -133,7 +125,7 @@ public class SQLiteQuery extends SQLiteProgram {
@Override
public String toString() {
- return "SQLiteQuery: " + mQuery;
+ return "SQLiteQuery: " + mSql;
}
@Override
@@ -153,7 +145,7 @@ public class SQLiteQuery extends SQLiteProgram {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
- StringBuilder errMsg = new StringBuilder("mQuery " + mQuery);
+ StringBuilder errMsg = new StringBuilder("mSql " + mSql);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index af54a71..610bf70 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -40,9 +40,10 @@ public class SQLiteQueryBuilder
private Map<String, String> mProjectionMap = null;
private String mTables = "";
- private final StringBuilder mWhereClause = new StringBuilder(64);
+ private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
+ private boolean mStrictProjectionMap;
public SQLiteQueryBuilder() {
mDistinct = false;
@@ -89,6 +90,9 @@ public class SQLiteQueryBuilder
* @param inWhere the chunk of text to append to the WHERE clause.
*/
public void appendWhere(CharSequence inWhere) {
+ if (mWhereClause == null) {
+ mWhereClause = new StringBuilder(inWhere.length() + 16);
+ }
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
@@ -106,6 +110,9 @@ public class SQLiteQueryBuilder
* to avoid SQL injection attacks
*/
public void appendWhereEscapeString(String inWhere) {
+ if (mWhereClause == null) {
+ mWhereClause = new StringBuilder(inWhere.length() + 16);
+ }
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
@@ -138,6 +145,13 @@ public class SQLiteQueryBuilder
}
/**
+ * @hide
+ */
+ public void setStrictProjectionMap(boolean flag) {
+ mStrictProjectionMap = flag;
+ }
+
+ /**
* Build an SQL query string from the given clauses.
*
* @param distinct true if you want each row to be unique, false otherwise.
@@ -356,15 +370,16 @@ public class SQLiteQueryBuilder
String[] projection = computeProjection(projectionIn);
StringBuilder where = new StringBuilder();
+ boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0;
- if (mWhereClause.length() > 0) {
+ if (hasBaseWhereClause) {
where.append(mWhereClause.toString());
where.append(')');
}
// Tack on the user's selection, if present.
if (selection != null && selection.length() > 0) {
- if (mWhereClause.length() > 0) {
+ if (hasBaseWhereClause) {
where.append(" AND ");
}
@@ -498,8 +513,8 @@ public class SQLiteQueryBuilder
continue;
}
- if (userColumn.contains(" AS ")
- || userColumn.contains(" as ")) {
+ if (!mStrictProjectionMap &&
+ ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
continue;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 5889ad9..98da414 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -17,7 +17,6 @@
package android.database.sqlite;
import android.os.SystemClock;
-import android.util.Log;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
@@ -27,10 +26,6 @@ import android.util.Log;
*/
public class SQLiteStatement extends SQLiteProgram
{
- private static final String TAG = "SQLiteStatement";
-
- private final String mSql;
-
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
@@ -39,11 +34,6 @@ public class SQLiteStatement extends SQLiteProgram
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- mSql = sql;
- } else {
- mSql = null;
- }
}
/**
@@ -54,50 +44,43 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public void execute() {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "execute() for [" + mSql + "]");
- }
native_execute();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
- } finally {
+ mDatabase.logTimeStat(mSql, timeStart);
+ } finally {
releaseReference();
mDatabase.unlock();
}
}
/**
- * Execute this SQL statement and return the ID of the most
- * recently inserted row. The SQL statement should probably be an
- * INSERT for this to be a useful call.
+ * Execute this SQL statement and return the ID of the row inserted due to this call.
+ * The SQL statement should be an INSERT for this to be a useful call.
*
- * @return the row ID of the last row inserted.
+ * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public long executeInsert() {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "executeInsert() for [" + mSql + "]");
- }
native_execute();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
- return mDatabase.lastInsertRow();
+ mDatabase.logTimeStat(mSql, timeStart);
+ return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
} finally {
releaseReference();
mDatabase.unlock();
@@ -113,19 +96,16 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]");
- }
long retValue = native_1x1_long();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
+ mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
@@ -142,19 +122,16 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
+ if (!mDatabase.isOpen()) {
+ throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
+ }
+ long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
- boolean logStats = mDatabase.mLogStats;
- long startTime = logStats ? SystemClock.elapsedRealtime() : 0;
acquireReference();
try {
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- Log.v(TAG, "simpleQueryForString() for [" + mSql + "]");
- }
String retValue = native_1x1_string();
- if (logStats) {
- mDatabase.logTimeStat(false /* write */, startTime, SystemClock.elapsedRealtime());
- }
+ mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
diff --git a/core/java/android/database/sqlite/SQLiteTransactionListener.java b/core/java/android/database/sqlite/SQLiteTransactionListener.java
index e97ece8..f03b580 100644
--- a/core/java/android/database/sqlite/SQLiteTransactionListener.java
+++ b/core/java/android/database/sqlite/SQLiteTransactionListener.java
@@ -1,3 +1,19 @@
+/*
+ * 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.database.sqlite;
/**
diff --git a/core/java/android/database/sqlite/SqliteWrapper.java b/core/java/android/database/sqlite/SqliteWrapper.java
new file mode 100644
index 0000000..b019618
--- /dev/null
+++ b/core/java/android/database/sqlite/SqliteWrapper.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * @hide
+ */
+
+public final class SqliteWrapper {
+ private static final String TAG = "SqliteWrapper";
+ private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
+ = "unable to open database file";
+
+ private SqliteWrapper() {
+ // Forbidden being instantiated.
+ }
+
+ // FIXME: need to optimize this method.
+ private static boolean isLowMemory(SQLiteException e) {
+ return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
+ }
+
+ public static void checkSQLiteException(Context context, SQLiteException e) {
+ if (isLowMemory(e)) {
+ Toast.makeText(context, com.android.internal.R.string.low_memory,
+ Toast.LENGTH_SHORT).show();
+ } else {
+ throw e;
+ }
+ }
+
+ public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+ String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when query: ", e);
+ checkSQLiteException(context, e);
+ return null;
+ }
+ }
+
+ public static boolean requery(Context context, Cursor cursor) {
+ try {
+ return cursor.requery();
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when requery: ", e);
+ checkSQLiteException(context, e);
+ return false;
+ }
+ }
+ public static int update(Context context, ContentResolver resolver, Uri uri,
+ ContentValues values, String where, String[] selectionArgs) {
+ try {
+ return resolver.update(uri, values, where, selectionArgs);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when update: ", e);
+ checkSQLiteException(context, e);
+ return -1;
+ }
+ }
+
+ public static int delete(Context context, ContentResolver resolver, Uri uri,
+ String where, String[] selectionArgs) {
+ try {
+ return resolver.delete(uri, where, selectionArgs);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when delete: ", e);
+ checkSQLiteException(context, e);
+ return -1;
+ }
+ }
+
+ public static Uri insert(Context context, ContentResolver resolver,
+ Uri uri, ContentValues values) {
+ try {
+ return resolver.insert(uri, values);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Catch a SQLiteException when insert: ", e);
+ checkSQLiteException(context, e);
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/ddm/DdmHandleHeap.java b/core/java/android/ddm/DdmHandleHeap.java
index 95fa0a2..fa0fbbf 100644
--- a/core/java/android/ddm/DdmHandleHeap.java
+++ b/core/java/android/ddm/DdmHandleHeap.java
@@ -34,6 +34,7 @@ public class DdmHandleHeap extends ChunkHandler {
public static final int CHUNK_HPIF = type("HPIF");
public static final int CHUNK_HPSG = type("HPSG");
public static final int CHUNK_HPDU = type("HPDU");
+ public static final int CHUNK_HPDS = type("HPDS");
public static final int CHUNK_NHSG = type("NHSG");
public static final int CHUNK_HPGC = type("HPGC");
public static final int CHUNK_REAE = type("REAE");
@@ -53,6 +54,7 @@ public class DdmHandleHeap extends ChunkHandler {
DdmServer.registerHandler(CHUNK_HPIF, mInstance);
DdmServer.registerHandler(CHUNK_HPSG, mInstance);
DdmServer.registerHandler(CHUNK_HPDU, mInstance);
+ DdmServer.registerHandler(CHUNK_HPDS, mInstance);
DdmServer.registerHandler(CHUNK_NHSG, mInstance);
DdmServer.registerHandler(CHUNK_HPGC, mInstance);
DdmServer.registerHandler(CHUNK_REAE, mInstance);
@@ -86,6 +88,8 @@ public class DdmHandleHeap extends ChunkHandler {
return handleHPSGNHSG(request, false);
} else if (type == CHUNK_HPDU) {
return handleHPDU(request);
+ } else if (type == CHUNK_HPDS) {
+ return handleHPDS(request);
} else if (type == CHUNK_NHSG) {
return handleHPSGNHSG(request, true);
} else if (type == CHUNK_HPGC) {
@@ -167,7 +171,7 @@ public class DdmHandleHeap extends ChunkHandler {
result = -1;
} catch (IOException ioe) {
result = -1;
- } catch (RuntimeException ioe) {
+ } catch (RuntimeException re) {
result = -1;
}
@@ -177,6 +181,38 @@ public class DdmHandleHeap extends ChunkHandler {
}
/*
+ * Handle a "HeaP Dump Streaming" request.
+ *
+ * This tells the VM to create a heap dump and send it directly to
+ * DDMS. The dumps are large enough that we don't want to copy the
+ * data into a byte[] and send it from here.
+ */
+ private Chunk handleHPDS(Chunk request) {
+ ByteBuffer in = wrapChunk(request);
+ byte result;
+
+ /* get the filename for the output file */
+ if (Config.LOGD)
+ Log.d("ddm-heap", "Heap dump: [DDMS]");
+
+ String failMsg = null;
+ try {
+ Debug.dumpHprofDataDdms();
+ } catch (UnsupportedOperationException uoe) {
+ failMsg = "hprof dumps not supported in this VM";
+ } catch (RuntimeException re) {
+ failMsg = "Exception: " + re.getMessage();
+ }
+
+ if (failMsg != null) {
+ Log.w("ddm-heap", failMsg);
+ return createFailChunk(1, failMsg);
+ } else {
+ return null;
+ }
+ }
+
+ /*
* Handle a "HeaP Garbage Collection" request.
*/
private Chunk handleHPGC(Chunk request) {
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index c5d591f..714a611 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -56,7 +56,7 @@ public class DdmHandleHello extends ChunkHandler {
if (Config.LOGV)
Log.v("ddm-hello", "Connected!");
- if (true) {
+ if (false) {
/* test spontaneous transmission */
byte[] data = new byte[] { 0, 1, 2, 3, 4, -4, -3, -2, -1, 127 };
Chunk testChunk =
@@ -148,12 +148,10 @@ public class DdmHandleHello extends ChunkHandler {
private Chunk handleFEAT(Chunk request) {
// TODO: query the VM to ensure that support for these features
// is actually compiled in
- final String[] features = {
- "hprof-heap-dump", "method-trace-profiling"
- };
+ final String[] features = Debug.getVmFeatureList();
- if (Config.LOGD)
- Log.d("ddm-heap", "Got feature list request");
+ if (Config.LOGV)
+ Log.v("ddm-heap", "Got feature list request");
int size = 4 + 4 * features.length;
for (int i = features.length-1; i >= 0; i--)
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index beed505..63ee445 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -32,6 +32,8 @@ public class DdmHandleProfiling extends ChunkHandler {
public static final int CHUNK_MPRS = type("MPRS");
public static final int CHUNK_MPRE = type("MPRE");
+ public static final int CHUNK_MPSS = type("MPSS");
+ public static final int CHUNK_MPSE = type("MPSE");
public static final int CHUNK_MPRQ = type("MPRQ");
private static DdmHandleProfiling mInstance = new DdmHandleProfiling();
@@ -46,6 +48,8 @@ public class DdmHandleProfiling extends ChunkHandler {
public static void register() {
DdmServer.registerHandler(CHUNK_MPRS, mInstance);
DdmServer.registerHandler(CHUNK_MPRE, mInstance);
+ DdmServer.registerHandler(CHUNK_MPSS, mInstance);
+ DdmServer.registerHandler(CHUNK_MPSE, mInstance);
DdmServer.registerHandler(CHUNK_MPRQ, mInstance);
}
@@ -73,6 +77,10 @@ public class DdmHandleProfiling extends ChunkHandler {
return handleMPRS(request);
} else if (type == CHUNK_MPRE) {
return handleMPRE(request);
+ } else if (type == CHUNK_MPSS) {
+ return handleMPSS(request);
+ } else if (type == CHUNK_MPSE) {
+ return handleMPSE(request);
} else if (type == CHUNK_MPRQ) {
return handleMPRQ(request);
} else {
@@ -124,6 +132,50 @@ public class DdmHandleProfiling extends ChunkHandler {
}
/*
+ * Handle a "Method Profiling w/Streaming Start" request.
+ */
+ private Chunk handleMPSS(Chunk request) {
+ ByteBuffer in = wrapChunk(request);
+
+ int bufferSize = in.getInt();
+ int flags = in.getInt();
+ if (Config.LOGV) {
+ Log.v("ddm-heap", "Method prof stream start: size=" + bufferSize
+ + ", flags=" + flags);
+ }
+
+ try {
+ Debug.startMethodTracingDdms(bufferSize, flags);
+ return null; // empty response
+ } catch (RuntimeException re) {
+ return createFailChunk(1, re.getMessage());
+ }
+ }
+
+ /*
+ * Handle a "Method Profiling w/Streaming End" request.
+ */
+ private Chunk handleMPSE(Chunk request) {
+ byte result;
+
+ if (Config.LOGV) {
+ Log.v("ddm-heap", "Method prof stream end");
+ }
+
+ try {
+ Debug.stopMethodTracing();
+ result = 0;
+ } catch (RuntimeException re) {
+ Log.w("ddm-heap", "Method prof stream end failed: "
+ + re.getMessage());
+ return createFailChunk(1, re.getMessage());
+ }
+
+ /* VM sent the (perhaps very large) response directly */
+ return null;
+ }
+
+ /*
* Handle a "Method PRofiling Query" request.
*/
private Chunk handleMPRQ(Chunk request) {
diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java
index 62330e1..300cd28 100755
--- a/core/java/android/gesture/Gesture.java
+++ b/core/java/android/gesture/Gesture.java
@@ -34,7 +34,9 @@ import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * A gesture can have a single or multiple strokes
+ * 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.
*/
public class Gesture implements Parcelable {
@@ -58,6 +60,19 @@ public class Gesture implements Parcelable {
mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet();
}
+ @Override
+ public Object clone() {
+ Gesture gesture = new Gesture();
+ gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top,
+ mBoundingBox.right, mBoundingBox.bottom);
+ final int count = mStrokes.size();
+ for (int i = 0; i < count; i++) {
+ GestureStroke stroke = mStrokes.get(i);
+ gesture.mStrokes.add((GestureStroke)stroke.clone());
+ }
+ return gesture;
+ }
+
/**
* @return all the strokes of the gesture
*/
@@ -73,7 +88,7 @@ public class Gesture implements Parcelable {
}
/**
- * Add a stroke to the gesture
+ * Adds a stroke to the gesture.
*
* @param stroke
*/
@@ -83,8 +98,8 @@ public class Gesture implements Parcelable {
}
/**
- * Get the total length of the gesture. When there are multiple strokes in
- * the gesture, this returns the sum of the lengths of all the strokes
+ * Calculates the total length of the gesture. When there are multiple strokes in
+ * the gesture, this returns the sum of the lengths of all the strokes.
*
* @return the length of the gesture
*/
@@ -142,7 +157,7 @@ public class Gesture implements Parcelable {
}
/**
- * Set the id of the gesture
+ * Sets the id of the gesture.
*
* @param id
*/
@@ -158,7 +173,7 @@ public class Gesture implements Parcelable {
}
/**
- * Create a bitmap of the gesture with a transparent background
+ * Creates a bitmap of the gesture with a transparent background.
*
* @param width width of the target bitmap
* @param height height of the target bitmap
@@ -194,7 +209,7 @@ public class Gesture implements Parcelable {
}
/**
- * Create a bitmap of the gesture with a transparent background
+ * Creates a bitmap of the gesture with a transparent background.
*
* @param width
* @param height
@@ -278,7 +293,7 @@ public class Gesture implements Parcelable {
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
} finally {
- GestureUtilities.closeStream(inStream);
+ GestureUtils.closeStream(inStream);
}
if (gesture != null) {
@@ -307,8 +322,8 @@ public class Gesture implements Parcelable {
} catch (IOException e) {
Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
} finally {
- GestureUtilities.closeStream(outStream);
- GestureUtilities.closeStream(byteStream);
+ GestureUtils.closeStream(outStream);
+ GestureUtils.closeStream(byteStream);
}
if (result) {
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
index 30ecf5a..b6c260f 100755
--- a/core/java/android/gesture/GestureOverlayView.java
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -638,7 +638,7 @@ public class GestureOverlayView extends FrameLayout {
if (mTotalLength > mGestureStrokeLengthThreshold) {
final OrientedBoundingBox box =
- GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
+ GestureUtils.computeOrientedBoundingBox(mStrokeBuffer);
float angle = Math.abs(box.orientation);
if (angle > 90) {
diff --git a/core/java/android/gesture/GesturePoint.java b/core/java/android/gesture/GesturePoint.java
index 3698011..4cb7707 100644
--- a/core/java/android/gesture/GesturePoint.java
+++ b/core/java/android/gesture/GesturePoint.java
@@ -20,7 +20,7 @@ import java.io.DataInputStream;
import java.io.IOException;
/**
- * A timed point of a gesture stroke
+ * A timed point of a gesture stroke. Multiple points form a stroke.
*/
public class GesturePoint {
@@ -43,4 +43,9 @@ public class GesturePoint {
final long timeStamp = in.readLong();
return new GesturePoint(x, y, timeStamp);
}
+
+ @Override
+ public Object clone() {
+ return new GesturePoint(x, y, timestamp);
+ }
}
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
index 5f1a445..11b5044 100644
--- a/core/java/android/gesture/GestureStore.java
+++ b/core/java/android/gesture/GestureStore.java
@@ -65,7 +65,12 @@ public class GestureStore {
// ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
public static final int ORIENTATION_INVARIANT = 1;
+ // at most 2 directions can be recognized
public static final int ORIENTATION_SENSITIVE = 2;
+ // at most 4 directions can be recognized
+ static final int ORIENTATION_SENSITIVE_4 = 4;
+ // at most 8 directions can be recognized
+ static final int ORIENTATION_SENSITIVE_8 = 8;
private static final short FILE_FORMAT_VERSION = 1;
@@ -131,7 +136,7 @@ public class GestureStore {
public ArrayList<Prediction> recognize(Gesture gesture) {
Instance instance = Instance.createInstance(mSequenceType,
mOrientationStyle, gesture, null);
- return mClassifier.classify(mSequenceType, instance.vector);
+ return mClassifier.classify(mSequenceType, mOrientationStyle, instance.vector);
}
/**
@@ -259,7 +264,7 @@ public class GestureStore {
mChanged = false;
} finally {
- if (closeStream) GestureUtilities.closeStream(out);
+ if (closeStream) GestureUtils.closeStream(out);
}
}
@@ -294,7 +299,7 @@ public class GestureStore {
Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
}
} finally {
- if (closeStream) GestureUtilities.closeStream(in);
+ if (closeStream) GestureUtils.closeStream(in);
}
}
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
index 598eb85..1d0f0fe 100644
--- a/core/java/android/gesture/GestureStroke.java
+++ b/core/java/android/gesture/GestureStroke.java
@@ -27,10 +27,11 @@ import java.io.DataInputStream;
import java.util.ArrayList;
/**
- * A gesture stroke started on a touch down and ended on a touch up.
+ * A gesture stroke started on a touch down and ended on a touch up. A stroke
+ * consists of a sequence of timed points. One or multiple strokes form a gesture.
*/
public class GestureStroke {
- static final float TOUCH_TOLERANCE = 8;
+ static final float TOUCH_TOLERANCE = 3;
public final RectF boundingBox;
@@ -41,7 +42,7 @@ public class GestureStroke {
private Path mCachedPath;
/**
- * Construct a gesture stroke from a list of gesture points
+ * A constructor that constructs a gesture stroke from a list of gesture points.
*
* @param points
*/
@@ -82,7 +83,22 @@ public class GestureStroke {
}
/**
- * Draw the gesture with a given canvas and paint
+ * A faster constructor specially for cloning a stroke.
+ */
+ private GestureStroke(RectF bbx, float len, float[] pts, long[] times) {
+ boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom);
+ length = len;
+ points = pts.clone();
+ timestamps = times.clone();
+ }
+
+ @Override
+ public Object clone() {
+ return new GestureStroke(boundingBox, length, points, timestamps);
+ }
+
+ /**
+ * Draws the stroke with a given canvas and paint.
*
* @param canvas
*/
@@ -134,7 +150,7 @@ public class GestureStroke {
}
/**
- * Convert the stroke to a Path based on the number of points
+ * Converts the stroke to a Path of a given number of points.
*
* @param width the width of the bounding box of the target path
* @param height the height of the bounding box of the target path
@@ -143,15 +159,15 @@ public class GestureStroke {
* @return the path
*/
public Path toPath(float width, float height, int numSample) {
- final float[] pts = GestureUtilities.temporalSampling(this, numSample);
+ final float[] pts = GestureUtils.temporalSampling(this, numSample);
final RectF rect = boundingBox;
- GestureUtilities.translate(pts, -rect.left, -rect.top);
+ GestureUtils.translate(pts, -rect.left, -rect.top);
float sx = width / rect.width();
float sy = height / rect.height();
float scale = sx > sy ? sy : sx;
- GestureUtilities.scale(pts, scale, scale);
+ GestureUtils.scale(pts, scale, scale);
float mX = 0;
float mY = 0;
@@ -213,17 +229,18 @@ public class GestureStroke {
}
/**
- * Invalidate the cached path that is used to render the stroke
+ * Invalidates the cached path that is used to render the stroke.
*/
public void clearPath() {
if (mCachedPath != null) mCachedPath.rewind();
}
/**
- * Compute an oriented bounding box of the stroke
+ * Computes an oriented bounding box of the stroke.
+ *
* @return OrientedBoundingBox
*/
public OrientedBoundingBox computeOrientedBoundingBox() {
- return GestureUtilities.computeOrientedBoundingBox(points);
+ return GestureUtils.computeOrientedBoundingBox(points);
}
}
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtils.java
index 40d7029..dd221fc 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtils.java
@@ -26,10 +26,22 @@ import java.io.IOException;
import static android.gesture.GestureConstants.*;
-final class GestureUtilities {
- private static final int TEMPORAL_SAMPLING_RATE = 16;
-
- private GestureUtilities() {
+/**
+ * Utility functions for gesture processing & analysis, including methods for:
+ * <ul>
+ * <li>feature extraction (e.g., samplers and those for calculating bounding
+ * boxes and gesture path lengths);
+ * <li>geometric transformation (e.g., translation, rotation and scaling);
+ * <li>gesture similarity comparison (e.g., calculating Euclidean or Cosine
+ * distances between two gestures).
+ * </ul>
+ */
+public final class GestureUtils {
+
+ private static final float SCALING_THRESHOLD = 0.26f;
+ private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2);
+
+ private GestureUtils() {
}
/**
@@ -46,108 +58,154 @@ final class GestureUtilities {
}
}
}
+
+ /**
+ * Samples the gesture spatially by rendering the gesture into a 2D
+ * grayscale bitmap. Scales the gesture to fit the size of the bitmap.
+ * The scaling does not necessarily keep the aspect ratio of the gesture.
+ *
+ * @param gesture the gesture to be sampled
+ * @param bitmapSize the size of the bitmap
+ * @return a bitmapSize x bitmapSize grayscale bitmap that is represented
+ * as a 1D array. The float at index i represents the grayscale
+ * value at pixel [i%bitmapSize, i/bitmapSize]
+ */
+ public static float[] spatialSampling(Gesture gesture, int bitmapSize) {
+ return spatialSampling(gesture, bitmapSize, false);
+ }
- static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) {
- final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
- float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
+ /**
+ * Samples the gesture spatially by rendering the gesture into a 2D
+ * grayscale bitmap. Scales the gesture to fit the size of the bitmap.
+ *
+ * @param gesture the gesture to be sampled
+ * @param bitmapSize the size of the bitmap
+ * @param keepAspectRatio if the scaling should keep the gesture's
+ * aspect ratio
+ *
+ * @return a bitmapSize x bitmapSize grayscale bitmap that is represented
+ * as a 1D array. The float at index i represents the grayscale
+ * value at pixel [i%bitmapSize, i/bitmapSize]
+ */
+ public static float[] spatialSampling(Gesture gesture, int bitmapSize,
+ boolean keepAspectRatio) {
+ final float targetPatchSize = bitmapSize - 1;
+ float[] sample = new float[bitmapSize * bitmapSize];
Arrays.fill(sample, 0);
-
+
RectF rect = gesture.getBoundingBox();
- float sx = targetPatchSize / rect.width();
- float sy = targetPatchSize / rect.height();
- float scale = sx < sy ? sx : sy;
+ final float gestureWidth = rect.width();
+ final float gestureHeight = rect.height();
+ float sx = targetPatchSize / gestureWidth;
+ float sy = targetPatchSize / gestureHeight;
+
+ if (keepAspectRatio) {
+ float scale = sx < sy ? sx : sy;
+ sx = scale;
+ sy = scale;
+ } else {
+ float aspectRatio = gestureWidth / gestureHeight;
+ if (aspectRatio > 1) {
+ aspectRatio = 1 / aspectRatio;
+ }
+ if (aspectRatio < SCALING_THRESHOLD) {
+ float scale = sx < sy ? sx : sy;
+ sx = scale;
+ sy = scale;
+ } else {
+ if (sx > sy) {
+ float scale = sy * NONUNIFORM_SCALE;
+ if (scale < sx) {
+ sx = scale;
+ }
+ } else {
+ float scale = sx * NONUNIFORM_SCALE;
+ if (scale < sy) {
+ sy = scale;
+ }
+ }
+ }
+ }
float preDx = -rect.centerX();
float preDy = -rect.centerY();
float postDx = targetPatchSize / 2;
float postDy = targetPatchSize / 2;
-
final ArrayList<GestureStroke> strokes = gesture.getStrokes();
final int count = strokes.size();
-
int size;
float xpos;
float ypos;
-
for (int index = 0; index < count; index++) {
final GestureStroke stroke = strokes.get(index);
float[] strokepoints = stroke.points;
size = strokepoints.length;
-
final float[] pts = new float[size];
-
for (int i = 0; i < size; i += 2) {
- pts[i] = (strokepoints[i] + preDx) * scale + postDx;
- pts[i + 1] = (strokepoints[i + 1] + preDy) * scale + postDy;
+ pts[i] = (strokepoints[i] + preDx) * sx + postDx;
+ pts[i + 1] = (strokepoints[i + 1] + preDy) * sy + postDy;
}
-
float segmentEndX = -1;
float segmentEndY = -1;
-
for (int i = 0; i < size; i += 2) {
-
float segmentStartX = pts[i] < 0 ? 0 : pts[i];
float segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1];
-
if (segmentStartX > targetPatchSize) {
segmentStartX = targetPatchSize;
}
-
if (segmentStartY > targetPatchSize) {
segmentStartY = targetPatchSize;
}
-
- plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension);
-
+ plot(segmentStartX, segmentStartY, sample, bitmapSize);
if (segmentEndX != -1) {
- // evaluate horizontally
+ // Evaluate horizontally
if (segmentEndX > segmentStartX) {
xpos = (float) Math.ceil(segmentStartX);
- float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ float slope = (segmentEndY - segmentStartY) /
+ (segmentEndX - segmentStartX);
while (xpos < segmentEndX) {
ypos = slope * (xpos - segmentStartX) + segmentStartY;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
xpos++;
}
} else if (segmentEndX < segmentStartX){
xpos = (float) Math.ceil(segmentEndX);
- float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ float slope = (segmentEndY - segmentStartY) /
+ (segmentEndX - segmentStartX);
while (xpos < segmentStartX) {
ypos = slope * (xpos - segmentStartX) + segmentStartY;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
xpos++;
}
}
-
- // evaluating vertically
+ // Evaluate vertically
if (segmentEndY > segmentStartY) {
ypos = (float) Math.ceil(segmentStartY);
- float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ float invertSlope = (segmentEndX - segmentStartX) /
+ (segmentEndY - segmentStartY);
while (ypos < segmentEndY) {
xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
ypos++;
}
} else if (segmentEndY < segmentStartY) {
ypos = (float) Math.ceil(segmentEndY);
- float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ float invertSlope = (segmentEndX - segmentStartX) /
+ (segmentEndY - segmentStartY);
while (ypos < segmentStartY) {
xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
ypos++;
}
}
}
-
segmentEndX = segmentStartX;
segmentEndY = segmentStartY;
}
}
-
-
return sample;
}
-
+
private static void plot(float x, float y, float[] sample, int sampleSize) {
x = x < 0 ? 0 : x;
y = y < 0 ? 0 : y;
@@ -163,48 +221,53 @@ final class GestureUtilities {
sample[index] = 1;
}
} else {
- double topLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yFloor - y, 2));
- double topRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yFloor - y, 2));
- double btmLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yCeiling - y, 2));
- double btmRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yCeiling - y, 2));
- double sum = topLeft + topRight + btmLeft + btmRight;
+ final double xFloorSq = Math.pow(xFloor - x, 2);
+ final double yFloorSq = Math.pow(yFloor - y, 2);
+ final double xCeilingSq = Math.pow(xCeiling - x, 2);
+ final double yCeilingSq = Math.pow(yCeiling - y, 2);
+ float topLeft = (float) Math.sqrt(xFloorSq + yFloorSq);
+ float topRight = (float) Math.sqrt(xCeilingSq + yFloorSq);
+ float btmLeft = (float) Math.sqrt(xFloorSq + yCeilingSq);
+ float btmRight = (float) Math.sqrt(xCeilingSq + yCeilingSq);
+ float sum = topLeft + topRight + btmLeft + btmRight;
- double value = topLeft / sum;
+ float value = topLeft / sum;
int index = yFloor * sampleSize + xFloor;
if (value > sample[index]){
- sample[index] = (float) value;
+ sample[index] = value;
}
value = topRight / sum;
index = yFloor * sampleSize + xCeiling;
if (value > sample[index]){
- sample[index] = (float) value;
+ sample[index] = value;
}
value = btmLeft / sum;
index = yCeiling * sampleSize + xFloor;
if (value > sample[index]){
- sample[index] = (float) value;
+ sample[index] = value;
}
value = btmRight / sum;
index = yCeiling * sampleSize + xCeiling;
if (value > sample[index]){
- sample[index] = (float) value;
+ sample[index] = value;
}
}
}
-
+
/**
- * Featurize a stroke into a vector of a given number of elements
+ * Samples a stroke temporally into a given number of evenly-distributed
+ * points.
*
- * @param stroke
- * @param sampleSize
- * @return a float array
+ * @param stroke the gesture stroke to be sampled
+ * @param numPoints the number of points
+ * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn]
*/
- static float[] temporalSampling(GestureStroke stroke, int sampleSize) {
- final float increment = stroke.length / (sampleSize - 1);
- int vectorLength = sampleSize * 2;
+ public static float[] temporalSampling(GestureStroke stroke, int numPoints) {
+ final float increment = stroke.length / (numPoints - 1);
+ int vectorLength = numPoints * 2;
float[] vector = new float[vectorLength];
float distanceSoFar = 0;
float[] pts = stroke.points;
@@ -259,9 +322,9 @@ final class GestureUtilities {
}
/**
- * Calculate the centroid
+ * Calculates the centroid of a set of points.
*
- * @param points
+ * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
* @return the centroid
*/
static float[] computeCentroid(float[] points) {
@@ -281,13 +344,13 @@ final class GestureUtilities {
}
/**
- * calculate the variance-covariance matrix, treat each point as a sample
+ * Calculates the variance-covariance matrix of a set of points.
*
- * @param points
- * @return the covariance matrix
+ * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
+ * @return the variance-covariance matrix
*/
- private static double[][] computeCoVariance(float[] points) {
- double[][] array = new double[2][2];
+ private static float[][] computeCoVariance(float[] points) {
+ float[][] array = new float[2][2];
array[0][0] = 0;
array[0][1] = 0;
array[1][0] = 0;
@@ -321,28 +384,28 @@ final class GestureUtilities {
return sum;
}
- static double computeStraightness(float[] points) {
+ static float computeStraightness(float[] points) {
float totalLen = computeTotalLength(points);
float dx = points[2] - points[0];
float dy = points[3] - points[1];
- return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
- static double computeStraightness(float[] points, float totalLen) {
+ static float computeStraightness(float[] points, float totalLen) {
float dx = points[2] - points[0];
float dy = points[3] - points[1];
- return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ return (float) Math.sqrt(dx * dx + dy * dy) / totalLen;
}
/**
- * Calculate the squared Euclidean distance between two vectors
+ * Calculates the squared Euclidean distance between two vectors.
*
* @param vector1
* @param vector2
* @return the distance
*/
- static double squaredEuclideanDistance(float[] vector1, float[] vector2) {
- double squaredDistance = 0;
+ static float squaredEuclideanDistance(float[] vector1, float[] vector2) {
+ float squaredDistance = 0;
int size = vector1.length;
for (int i = 0; i < size; i++) {
float difference = vector1[i] - vector2[i];
@@ -352,37 +415,92 @@ final class GestureUtilities {
}
/**
- * Calculate the cosine distance between two instances
+ * Calculates the cosine distance between two instances.
*
* @param vector1
* @param vector2
* @return the distance between 0 and Math.PI
*/
- static double cosineDistance(float[] vector1, float[] vector2) {
+ static float cosineDistance(float[] vector1, float[] vector2) {
float sum = 0;
int len = vector1.length;
for (int i = 0; i < len; i++) {
sum += vector1[i] * vector2[i];
}
- return Math.acos(sum);
+ return (float) Math.acos(sum);
+ }
+
+ /**
+ * Calculates the "minimum" cosine distance between two instances.
+ *
+ * @param vector1
+ * @param vector2
+ * @param numOrientations the maximum number of orientation allowed
+ * @return the distance between the two instances (between 0 and Math.PI)
+ */
+ static float minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) {
+ final int len = vector1.length;
+ float a = 0;
+ float b = 0;
+ for (int i = 0; i < len; i += 2) {
+ a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1];
+ b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i];
+ }
+ if (a != 0) {
+ final float tan = b/a;
+ final double angle = Math.atan(tan);
+ if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) {
+ return (float) Math.acos(a);
+ } else {
+ final double cosine = Math.cos(angle);
+ final double sine = cosine * tan;
+ return (float) Math.acos(a * cosine + b * sine);
+ }
+ } else {
+ return (float) Math.PI / 2;
+ }
}
- static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) {
- GestureStroke stroke = new GestureStroke(pts);
- float[] points = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE);
- return computeOrientedBoundingBox(points);
+ /**
+ * Computes an oriented, minimum bounding box of a set of points.
+ *
+ * @param originalPoints
+ * @return an oriented bounding box
+ */
+ public static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
+ final int count = originalPoints.size();
+ float[] points = new float[count * 2];
+ for (int i = 0; i < count; i++) {
+ GesturePoint point = originalPoints.get(i);
+ int index = i * 2;
+ points[index] = point.x;
+ points[index + 1] = point.y;
+ }
+ float[] meanVector = computeCentroid(points);
+ return computeOrientedBoundingBox(points, meanVector);
}
- static OrientedBoundingBox computeOrientedBoundingBox(float[] points) {
+ /**
+ * Computes an oriented, minimum bounding box of a set of points.
+ *
+ * @param originalPoints
+ * @return an oriented bounding box
+ */
+ public static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
+ int size = originalPoints.length;
+ float[] points = new float[size];
+ for (int i = 0; i < size; i++) {
+ points[i] = originalPoints[i];
+ }
float[] meanVector = computeCentroid(points);
return computeOrientedBoundingBox(points, meanVector);
}
- static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
+ private static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
translate(points, -centroid[0], -centroid[1]);
- double[][] array = computeCoVariance(points);
- double[] targetVector = computeOrientation(array);
+ float[][] array = computeCoVariance(points);
+ float[] targetVector = computeOrientation(array);
float angle;
if (targetVector[0] == 0 && targetVector[1] == 0) {
@@ -416,25 +534,25 @@ final class GestureUtilities {
return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny);
}
- private static double[] computeOrientation(double[][] covarianceMatrix) {
- double[] targetVector = new double[2];
+ private static float[] computeOrientation(float[][] covarianceMatrix) {
+ float[] targetVector = new float[2];
if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) {
targetVector[0] = 1;
targetVector[1] = 0;
}
- double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
- double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
+ float a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
+ float b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
* covarianceMatrix[1][0];
- double value = a / 2;
- double rightside = Math.sqrt(Math.pow(value, 2) - b);
- double lambda1 = -value + rightside;
- double lambda2 = -value - rightside;
+ float value = a / 2;
+ float rightside = (float) Math.sqrt(Math.pow(value, 2) - b);
+ float lambda1 = -value + rightside;
+ float lambda2 = -value - rightside;
if (lambda1 == lambda2) {
targetVector[0] = 0;
targetVector[1] = 0;
} else {
- double lambda = lambda1 > lambda2 ? lambda1 : lambda2;
+ float lambda = lambda1 > lambda2 ? lambda1 : lambda2;
targetVector[0] = 1;
targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1];
}
@@ -442,13 +560,13 @@ final class GestureUtilities {
}
- static float[] rotate(float[] points, double angle) {
- double cos = Math.cos(angle);
- double sin = Math.sin(angle);
+ static float[] rotate(float[] points, float angle) {
+ float cos = (float) Math.cos(angle);
+ float sin = (float) Math.sin(angle);
int size = points.length;
for (int i = 0; i < size; i += 2) {
- float x = (float) (points[i] * cos - points[i + 1] * sin);
- float y = (float) (points[i] * sin + points[i + 1] * cos);
+ float x = points[i] * cos - points[i + 1] * sin;
+ float y = points[i] * sin + points[i + 1] * cos;
points[i] = x;
points[i + 1] = y;
}
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
index ef208ac..02a6519 100755
--- a/core/java/android/gesture/Instance.java
+++ b/core/java/android/gesture/Instance.java
@@ -84,17 +84,17 @@ class Instance {
}
private static float[] spatialSampler(Gesture gesture) {
- return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE);
+ return GestureUtils.spatialSampling(gesture, PATCH_SAMPLE_SIZE, false);
}
private static float[] temporalSampler(int orientationType, Gesture gesture) {
- float[] pts = GestureUtilities.temporalSampling(gesture.getStrokes().get(0),
+ float[] pts = GestureUtils.temporalSampling(gesture.getStrokes().get(0),
SEQUENCE_SAMPLE_SIZE);
- float[] center = GestureUtilities.computeCentroid(pts);
+ float[] center = GestureUtils.computeCentroid(pts);
float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);
float adjustment = -orientation;
- if (orientationType == GestureStore.ORIENTATION_SENSITIVE) {
+ if (orientationType != GestureStore.ORIENTATION_INVARIANT) {
int count = ORIENTATIONS.length;
for (int i = 0; i < count; i++) {
float delta = ORIENTATIONS[i] - orientation;
@@ -104,8 +104,8 @@ class Instance {
}
}
- GestureUtilities.translate(pts, -center[0], -center[1]);
- GestureUtilities.rotate(pts, adjustment);
+ GestureUtils.translate(pts, -center[0], -center[1]);
+ GestureUtils.rotate(pts, adjustment);
return pts;
}
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
index b93b76f..7224ded 100644
--- a/core/java/android/gesture/InstanceLearner.java
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -41,7 +41,7 @@ class InstanceLearner extends Learner {
};
@Override
- ArrayList<Prediction> classify(int sequenceType, float[] vector) {
+ ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector) {
ArrayList<Prediction> predictions = new ArrayList<Prediction>();
ArrayList<Instance> instances = getInstances();
int count = instances.size();
@@ -53,9 +53,9 @@ class InstanceLearner extends Learner {
}
double distance;
if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
- distance = GestureUtilities.cosineDistance(sample.vector, vector);
+ distance = GestureUtils.minimumCosineDistance(sample.vector, vector, orientationType);
} else {
- distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector);
+ distance = GestureUtils.squaredEuclideanDistance(sample.vector, vector);
}
double weight;
if (distance == 0) {
diff --git a/core/java/android/gesture/Learner.java b/core/java/android/gesture/Learner.java
index feacde5..a105652 100755
--- a/core/java/android/gesture/Learner.java
+++ b/core/java/android/gesture/Learner.java
@@ -72,12 +72,13 @@ abstract class Learner {
for (int i = 0; i < count; i++) {
final Instance instance = instances.get(i);
// the label can be null, as specified in Instance
- if ((instance.label == null && name == null) || instance.label.equals(name)) {
+ if ((instance.label == null && name == null)
+ || (instance.label != null && instance.label.equals(name))) {
toDelete.add(instance);
}
}
instances.removeAll(toDelete);
}
- abstract ArrayList<Prediction> classify(int gestureType, float[] vector);
+ abstract ArrayList<Prediction> classify(int sequenceType, int orientationType, float[] vector);
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index d90536c..8687a89 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -26,7 +26,7 @@ import java.io.IOException;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.graphics.PixelFormat;
+import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -78,7 +78,7 @@ public class Camera {
private PreviewCallback mPreviewCallback;
private PictureCallback mPostviewCallback;
private AutoFocusCallback mAutoFocusCallback;
- private ZoomCallback mZoomCallback;
+ private OnZoomChangeListener mZoomListener;
private ErrorCallback mErrorCallback;
private boolean mOneShot;
private boolean mWithBuffer;
@@ -96,7 +96,7 @@ public class Camera {
mJpegCallback = null;
mPreviewCallback = null;
mPostviewCallback = null;
- mZoomCallback = null;
+ mZoomListener = null;
Looper looper;
if ((looper = Looper.myLooper()) != null) {
@@ -137,8 +137,6 @@ public class Camera {
* can be connected to another process.
*
* @throws IOException if the method fails.
- *
- * @hide
*/
public native final void reconnect() throws IOException;
@@ -192,7 +190,7 @@ public class Camera {
* The callback that delivers the preview frames.
*
* @param data The contents of the preview frame in the format defined
- * by {@link android.graphics.PixelFormat}, which can be queried
+ * by {@link android.graphics.ImageFormat}, which can be queried
* with {@link android.hardware.Camera.Parameters#getPreviewFormat()}.
* If {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}
* is never called, the default will be the YCbCr_420_SP
@@ -263,7 +261,6 @@ public class Camera {
* setPreviewCallback, or to this method with a null callback parameter.
*
* @param cb A callback object that receives a copy of the preview frame. A null value will clear the queue.
- * @hide
*/
public final void setPreviewCallbackWithBuffer(PreviewCallback cb) {
mPreviewCallback = cb;
@@ -273,16 +270,24 @@ public class Camera {
}
/**
- * Adds a pre-allocated buffer to the callback buffer queue.
- * Preview width and height can be determined from getPreviewSize, and bitsPerPixel can be
- * found from from {@link android.hardware.Camera.Parameters#getPreviewFormat()} and
- * {@link android.graphics.PixelFormat#getPixelFormatInfo(int, PixelFormat)}
+ * Adds a pre-allocated buffer to the preview callback buffer queue.
+ * Applications can add one or more buffers to the queue. When a preview
+ * frame arrives and there is still available buffer, buffer will be filled
+ * and it is removed from the queue. Then preview callback is invoked with
+ * the buffer. If a frame arrives and there is no buffer left, the frame is
+ * discarded. Applications should add the buffers back when they finish the
+ * processing.
+ *
+ * The image format of the callback buffer can be read from {@link
+ * android.hardware.Camera.Parameters#getPreviewFormat()}. bitsPerPixel can
+ * be read from {@link android.graphics.ImageFormat#getBitsPerPixel(int)}.
+ * Preview width and height can be determined from getPreviewSize.
*
* Alternatively, a buffer from a previous callback may be passed in or used
* to determine the size of new preview frame buffers.
*
* @param callbackBuffer The buffer to register. Size should be width * height * bitsPerPixel / 8.
- * @hide
+ * @see #setPreviewCallbackWithBuffer(PreviewCallback)
*/
public native final void addCallbackBuffer(byte[] callbackBuffer);
@@ -347,8 +352,8 @@ public class Camera {
return;
case CAMERA_MSG_ZOOM:
- if (mZoomCallback != null) {
- mZoomCallback.onZoomUpdate(msg.arg1, msg.arg2 != 0, mCamera);
+ if (mZoomListener != null) {
+ mZoomListener.onZoomChange(msg.arg1, msg.arg2 != 0, mCamera);
}
return;
@@ -523,55 +528,78 @@ public class Camera {
}
/**
- * Zooms to the requested value smoothly. Driver will generate {@link
- * #ZoomCallback} for the current zoom value and whether zoom is stopped.
- * The applications can call {@link #stopSmoothZoom} to stop the zoom
- * earlier. The applications should not call startSmoothZoom again or {@link
- * android.hardware.Camera.Parameters#setZoom(int)} before the zoom stops.
+ * Zooms to the requested value smoothly. Driver will notify {@link
+ * OnZoomChangeListener} of the zoom value and whether zoom is stopped at
+ * the time. For example, suppose the current zoom is 0 and startSmoothZoom
+ * is called with value 3. Method onZoomChange will be called three times
+ * with zoom value 1, 2, and 3. The applications can call {@link
+ * #stopSmoothZoom} to stop the zoom earlier. The applications should not
+ * call startSmoothZoom again or change the zoom value before zoom stops. If
+ * the passing zoom value equals to the current zoom value, no zoom callback
+ * will be generated. This method is supported if {@link
+ * android.hardware.Camera.Parameters#isSmoothZoomSupported} is true.
*
* @param value zoom value. The valid range is 0 to {@link
* android.hardware.Camera.Parameters#getMaxZoom}.
- * @hide
+ * @throws IllegalArgumentException if the zoom value is invalid.
+ * @throws RuntimeException if the method fails.
*/
public native final void startSmoothZoom(int value);
/**
* Stops the smooth zoom. The applications should wait for the {@link
- * #ZoomCallback} to know when the zoom is actually stopped.
- * @hide
+ * OnZoomChangeListener} to know when the zoom is actually stopped. This
+ * method is supported if {@link
+ * android.hardware.Camera.Parameters#isSmoothZoomSupported} is true.
+ *
+ * @throws RuntimeException if the method fails.
*/
public native final void stopSmoothZoom();
/**
- * Handles the zoom callback.
+ * Set the display orientation. This affects the preview frames and the
+ * picture displayed after snapshot. This method is useful for portrait
+ * mode applications.
*
- * @hide
+ * This does not affect the order of byte array passed in
+ * {@link PreviewCallback#onPreviewFrame}. This method is not allowed to
+ * be called during preview.
+ *
+ * @param degrees the angle that the picture will be rotated clockwise.
+ * Valid values are 0, 90, 180, and 270. The starting
+ * position is 0 (landscape).
*/
- public interface ZoomCallback
+ public native final void setDisplayOrientation(int degrees);
+
+ /**
+ * Interface for a callback to be invoked when zoom value changes.
+ */
+ public interface OnZoomChangeListener
{
/**
- * Callback for zoom updates
+ * Called when the zoom value has changed.
*
* @param zoomValue the current zoom value. In smooth zoom mode, camera
- * generates this callback for every new zoom value.
+ * calls this for every new zoom value.
* @param stopped whether smooth zoom is stopped. If the value is true,
* this is the last zoom update for the application.
*
* @param camera the Camera service object
- * @see android.hardware.Camera.Parameters#startSmoothZoom
+ * @see #startSmoothZoom(int)
*/
- void onZoomUpdate(int zoomValue, boolean stopped, Camera camera);
+ void onZoomChange(int zoomValue, boolean stopped, Camera camera);
};
/**
- * Registers a callback to be invoked when the zoom value is updated by the
+ * Registers a listener to be notified when the zoom value is updated by the
* camera driver during smooth zoom.
- * @param cb the callback to run
- * @hide
+ *
+ * @param listener the listener to notify
+ * @see #startSmoothZoom(int)
*/
- public final void setZoomCallback(ZoomCallback cb)
+ public final void setZoomChangeListener(OnZoomChangeListener listener)
{
- mZoomCallback = cb;
+ mZoomListener = listener;
}
// These match the enum in include/ui/Camera.h
@@ -643,6 +671,25 @@ public class Camera {
width = w;
height = h;
}
+ /**
+ * Compares {@code obj} to this size.
+ *
+ * @param obj the object to compare this size with.
+ * @return {@code true} if the width and height of {@code obj} is the
+ * same as those of this size. {@code false} otherwise.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Size)) {
+ return false;
+ }
+ Size s = (Size) obj;
+ return width == s.width && height == s.height;
+ }
+ @Override
+ public int hashCode() {
+ return width * 32713 + height;
+ }
/** width of the picture */
public int width;
/** height of the picture */
@@ -670,6 +717,7 @@ public class Camera {
private static final String KEY_PREVIEW_FRAME_RATE = "preview-frame-rate";
private static final String KEY_PICTURE_SIZE = "picture-size";
private static final String KEY_PICTURE_FORMAT = "picture-format";
+ private static final String KEY_JPEG_THUMBNAIL_SIZE = "jpeg-thumbnail-size";
private static final String KEY_JPEG_THUMBNAIL_WIDTH = "jpeg-thumbnail-width";
private static final String KEY_JPEG_THUMBNAIL_HEIGHT = "jpeg-thumbnail-height";
private static final String KEY_JPEG_THUMBNAIL_QUALITY = "jpeg-thumbnail-quality";
@@ -679,15 +727,30 @@ public class Camera {
private static final String KEY_GPS_LONGITUDE = "gps-longitude";
private static final String KEY_GPS_ALTITUDE = "gps-altitude";
private static final String KEY_GPS_TIMESTAMP = "gps-timestamp";
+ private static final String KEY_GPS_PROCESSING_METHOD = "gps-processing-method";
private static final String KEY_WHITE_BALANCE = "whitebalance";
private static final String KEY_EFFECT = "effect";
private static final String KEY_ANTIBANDING = "antibanding";
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_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";
+ private static final String KEY_EXPOSURE_COMPENSATION = "exposure-compensation";
+ 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_ZOOM = "zoom";
+ private static final String KEY_MAX_ZOOM = "max-zoom";
+ private static final String KEY_ZOOM_RATIOS = "zoom-ratios";
+ private static final String KEY_ZOOM_SUPPORTED = "zoom-supported";
+ private static final String KEY_SMOOTH_ZOOM_SUPPORTED = "smooth-zoom-supported";
// Parameter key suffix for supported values.
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
+ private static final String TRUE = "true";
+
// Values for white balance settings.
public static final String WHITE_BALANCE_AUTO = "auto";
public static final String WHITE_BALANCE_INCANDESCENT = "incandescent";
@@ -761,6 +824,12 @@ public class Camera {
public static final String SCENE_MODE_PARTY = "party";
public static final String SCENE_MODE_CANDLELIGHT = "candlelight";
+ /**
+ * Applications are looking for a barcode. Camera driver will be
+ * optimized for barcode reading.
+ */
+ public static final String SCENE_MODE_BARCODE = "barcode";
+
// Values for focus mode settings.
/**
* Auto-focus mode.
@@ -782,6 +851,13 @@ public class Camera {
*/
public static final String FOCUS_MODE_FIXED = "fixed";
+ /**
+ * Extended depth of field (EDOF). Focusing is done digitally and
+ * continuously. Applications should not call {@link
+ * #autoFocus(AutoFocusCallback)} in this mode.
+ */
+ public static final String FOCUS_MODE_EDOF = "edof";
+
// Formats for setPreviewFormat and setPictureFormat.
private static final String PIXEL_FORMAT_YUV422SP = "yuv422sp";
private static final String PIXEL_FORMAT_YUV420SP = "yuv420sp";
@@ -930,7 +1006,7 @@ public class Camera {
/**
* Gets the supported preview sizes.
*
- * @return a List of Size object. This method will always return a list
+ * @return a list of Size object. This method will always return a list
* with at least one element.
*/
public List<Size> getSupportedPreviewSizes() {
@@ -939,7 +1015,9 @@ public class Camera {
}
/**
- * Sets the dimensions for EXIF thumbnail in Jpeg picture.
+ * Sets the dimensions for EXIF thumbnail in Jpeg picture. If
+ * applications set both width and height to 0, EXIF will not contain
+ * thumbnail.
*
* @param width the width of the thumbnail, in pixels
* @param height the height of the thumbnail, in pixels
@@ -961,6 +1039,18 @@ public class Camera {
}
/**
+ * Gets the supported jpeg thumbnail sizes.
+ *
+ * @return a list of Size object. This method will always return a list
+ * with at least two elements. Size 0,0 (no thumbnail) is always
+ * supported.
+ */
+ public List<Size> getSupportedJpegThumbnailSizes() {
+ String str = get(KEY_JPEG_THUMBNAIL_SIZE + SUPPORTED_VALUES_SUFFIX);
+ return splitSize(str);
+ }
+
+ /**
* Sets the quality of the EXIF thumbnail in Jpeg picture.
*
* @param quality the JPEG quality of the EXIF thumbnail. The range is 1
@@ -999,7 +1089,8 @@ public class Camera {
}
/**
- * Sets the rate at which preview frames are received.
+ * Sets the rate at which preview frames are received. This is the
+ * target frame rate. The actual frame rate depends on the driver.
*
* @param fps the frame rate (frames per second)
*/
@@ -1008,8 +1099,9 @@ public class Camera {
}
/**
- * Returns the setting for the rate at which preview frames
- * are received.
+ * Returns the setting for the rate at which preview frames are
+ * received. This is the target frame rate. The actual frame rate
+ * depends on the driver.
*
* @return the frame rate setting (frames per second)
*/
@@ -1020,8 +1112,8 @@ public class Camera {
/**
* Gets the supported preview frame rates.
*
- * @return a List of Integer objects (preview frame rates). null if
- * preview frame rate setting is not supported.
+ * @return a list of supported preview frame rates. null if preview
+ * frame rate setting is not supported.
*/
public List<Integer> getSupportedPreviewFrameRates() {
String str = get(KEY_PREVIEW_FRAME_RATE + SUPPORTED_VALUES_SUFFIX);
@@ -1031,15 +1123,15 @@ public class Camera {
/**
* Sets the image format for preview pictures.
* <p>If this is never called, the default format will be
- * {@link android.graphics.PixelFormat#YCbCr_420_SP}, which
+ * {@link android.graphics.ImageFormat#NV21}, which
* uses the NV21 encoding format.</p>
*
* @param pixel_format the desired preview picture format, defined
- * by one of the {@link android.graphics.PixelFormat} constants.
- * (E.g., <var>PixelFormat.YCbCr_420_SP</var> (default),
- * <var>PixelFormat.RGB_565</var>, or
- * <var>PixelFormat.JPEG</var>)
- * @see android.graphics.PixelFormat
+ * by one of the {@link android.graphics.ImageFormat} constants.
+ * (E.g., <var>ImageFormat.NV21</var> (default),
+ * <var>ImageFormat.RGB_565</var>, or
+ * <var>ImageFormat.JPEG</var>)
+ * @see android.graphics.ImageFormat
*/
public void setPreviewFormat(int pixel_format) {
String s = cameraFormatForPixelFormat(pixel_format);
@@ -1052,11 +1144,11 @@ public class Camera {
}
/**
- * Returns the image format for preview pictures got from
+ * Returns the image format for preview frames got from
* {@link PreviewCallback}.
*
- * @return the {@link android.graphics.PixelFormat} int representing
- * the preview picture format.
+ * @return the preview format.
+ * @see android.graphics.ImageFormat
*/
public int getPreviewFormat() {
return pixelFormatForCameraFormat(get(KEY_PREVIEW_FORMAT));
@@ -1065,15 +1157,16 @@ public class Camera {
/**
* Gets the supported preview formats.
*
- * @return a List of Integer objects. This method will always return a
- * list with at least one element.
+ * @return a list of supported preview formats. This method will always
+ * return a list with at least one element.
+ * @see android.graphics.ImageFormat
*/
public List<Integer> getSupportedPreviewFormats() {
String str = get(KEY_PREVIEW_FORMAT + SUPPORTED_VALUES_SUFFIX);
ArrayList<Integer> formats = new ArrayList<Integer>();
for (String s : split(str)) {
int f = pixelFormatForCameraFormat(s);
- if (f == PixelFormat.UNKNOWN) continue;
+ if (f == ImageFormat.UNKNOWN) continue;
formats.add(f);
}
return formats;
@@ -1104,8 +1197,8 @@ public class Camera {
/**
* Gets the supported picture sizes.
*
- * @return a List of Size objects. This method will always return a list
- * with at least one element.
+ * @return a list of supported picture sizes. This method will always
+ * return a list with at least one element.
*/
public List<Size> getSupportedPictureSizes() {
String str = get(KEY_PICTURE_SIZE + SUPPORTED_VALUES_SUFFIX);
@@ -1116,10 +1209,10 @@ public class Camera {
* Sets the image format for pictures.
*
* @param pixel_format the desired picture format
- * (<var>PixelFormat.YCbCr_420_SP (NV21)</var>,
- * <var>PixelFormat.RGB_565</var>, or
- * <var>PixelFormat.JPEG</var>)
- * @see android.graphics.PixelFormat
+ * (<var>ImageFormat.NV21</var>,
+ * <var>ImageFormat.RGB_565</var>, or
+ * <var>ImageFormat.JPEG</var>)
+ * @see android.graphics.ImageFormat
*/
public void setPictureFormat(int pixel_format) {
String s = cameraFormatForPixelFormat(pixel_format);
@@ -1134,7 +1227,8 @@ public class Camera {
/**
* Returns the image format for pictures.
*
- * @return the PixelFormat int representing the picture format
+ * @return the picture format
+ * @see android.graphics.ImageFormat
*/
public int getPictureFormat() {
return pixelFormatForCameraFormat(get(KEY_PICTURE_FORMAT));
@@ -1143,15 +1237,16 @@ public class Camera {
/**
* Gets the supported picture formats.
*
- * @return a List of Integer objects (values are PixelFormat.XXX). This
- * method will always return a list with at least one element.
+ * @return supported picture formats. This method will always return a
+ * list with at least one element.
+ * @see android.graphics.ImageFormat
*/
public List<Integer> getSupportedPictureFormats() {
String str = get(KEY_PICTURE_FORMAT + SUPPORTED_VALUES_SUFFIX);
ArrayList<Integer> formats = new ArrayList<Integer>();
for (String s : split(str)) {
int f = pixelFormatForCameraFormat(s);
- if (f == PixelFormat.UNKNOWN) continue;
+ if (f == ImageFormat.UNKNOWN) continue;
formats.add(f);
}
return formats;
@@ -1159,35 +1254,35 @@ public class Camera {
private String cameraFormatForPixelFormat(int pixel_format) {
switch(pixel_format) {
- case PixelFormat.YCbCr_422_SP: return PIXEL_FORMAT_YUV422SP;
- case PixelFormat.YCbCr_420_SP: return PIXEL_FORMAT_YUV420SP;
- case PixelFormat.YCbCr_422_I: return PIXEL_FORMAT_YUV422I;
- case PixelFormat.RGB_565: return PIXEL_FORMAT_RGB565;
- case PixelFormat.JPEG: return PIXEL_FORMAT_JPEG;
- default: return null;
+ case ImageFormat.NV16: return PIXEL_FORMAT_YUV422SP;
+ case ImageFormat.NV21: return PIXEL_FORMAT_YUV420SP;
+ case ImageFormat.YUY2: return PIXEL_FORMAT_YUV422I;
+ case ImageFormat.RGB_565: return PIXEL_FORMAT_RGB565;
+ case ImageFormat.JPEG: return PIXEL_FORMAT_JPEG;
+ default: return null;
}
}
private int pixelFormatForCameraFormat(String format) {
if (format == null)
- return PixelFormat.UNKNOWN;
+ return ImageFormat.UNKNOWN;
if (format.equals(PIXEL_FORMAT_YUV422SP))
- return PixelFormat.YCbCr_422_SP;
+ return ImageFormat.NV16;
if (format.equals(PIXEL_FORMAT_YUV420SP))
- return PixelFormat.YCbCr_420_SP;
+ return ImageFormat.NV21;
if (format.equals(PIXEL_FORMAT_YUV422I))
- return PixelFormat.YCbCr_422_I;
+ return ImageFormat.YUY2;
if (format.equals(PIXEL_FORMAT_RGB565))
- return PixelFormat.RGB_565;
+ return ImageFormat.RGB_565;
if (format.equals(PIXEL_FORMAT_JPEG))
- return PixelFormat.JPEG;
+ return ImageFormat.JPEG;
- return PixelFormat.UNKNOWN;
+ return ImageFormat.UNKNOWN;
}
/**
@@ -1259,6 +1354,16 @@ public class Camera {
}
/**
+ * Sets GPS processing method. It will store up to 32 characters
+ * in JPEG EXIF header.
+ *
+ * @param processing_method The processing method to get this location.
+ */
+ public void setGpsProcessingMethod(String processing_method) {
+ set(KEY_GPS_PROCESSING_METHOD, processing_method);
+ }
+
+ /**
* Removes GPS latitude, longitude, altitude, and timestamp from the
* parameters.
*/
@@ -1267,13 +1372,23 @@ public class Camera {
remove(KEY_GPS_LONGITUDE);
remove(KEY_GPS_ALTITUDE);
remove(KEY_GPS_TIMESTAMP);
+ remove(KEY_GPS_PROCESSING_METHOD);
}
/**
* Gets the current white balance setting.
*
- * @return one of WHITE_BALANCE_XXX string constant. null if white
- * balance setting is not supported.
+ * @return current white balance. null if white balance setting is not
+ * supported.
+ * @see #WHITE_BALANCE_AUTO
+ * @see #WHITE_BALANCE_INCANDESCENT
+ * @see #WHITE_BALANCE_FLUORESCENT
+ * @see #WHITE_BALANCE_WARM_FLUORESCENT
+ * @see #WHITE_BALANCE_DAYLIGHT
+ * @see #WHITE_BALANCE_CLOUDY_DAYLIGHT
+ * @see #WHITE_BALANCE_TWILIGHT
+ * @see #WHITE_BALANCE_SHADE
+ *
*/
public String getWhiteBalance() {
return get(KEY_WHITE_BALANCE);
@@ -1282,7 +1397,8 @@ public class Camera {
/**
* Sets the white balance.
*
- * @param value WHITE_BALANCE_XXX string constant.
+ * @param value new white balance.
+ * @see #getWhiteBalance()
*/
public void setWhiteBalance(String value) {
set(KEY_WHITE_BALANCE, value);
@@ -1291,8 +1407,9 @@ public class Camera {
/**
* Gets the supported white balance.
*
- * @return a List of WHITE_BALANCE_XXX string constants. null if white
- * balance setting is not supported.
+ * @return a list of supported white balance. null if white balance
+ * setting is not supported.
+ * @see #getWhiteBalance()
*/
public List<String> getSupportedWhiteBalance() {
String str = get(KEY_WHITE_BALANCE + SUPPORTED_VALUES_SUFFIX);
@@ -1302,8 +1419,17 @@ public class Camera {
/**
* Gets the current color effect setting.
*
- * @return one of EFFECT_XXX string constant. null if color effect
+ * @return current color effect. null if color effect
* setting is not supported.
+ * @see #EFFECT_NONE
+ * @see #EFFECT_MONO
+ * @see #EFFECT_NEGATIVE
+ * @see #EFFECT_SOLARIZE
+ * @see #EFFECT_SEPIA
+ * @see #EFFECT_POSTERIZE
+ * @see #EFFECT_WHITEBOARD
+ * @see #EFFECT_BLACKBOARD
+ * @see #EFFECT_AQUA
*/
public String getColorEffect() {
return get(KEY_EFFECT);
@@ -1312,7 +1438,8 @@ public class Camera {
/**
* Sets the current color effect setting.
*
- * @param value EFFECT_XXX string constants.
+ * @param value new color effect.
+ * @see #getColorEffect()
*/
public void setColorEffect(String value) {
set(KEY_EFFECT, value);
@@ -1321,8 +1448,9 @@ public class Camera {
/**
* Gets the supported color effects.
*
- * @return a List of EFFECT_XXX string constants. null if color effect
+ * @return a list of supported color effects. null if color effect
* setting is not supported.
+ * @see #getColorEffect()
*/
public List<String> getSupportedColorEffects() {
String str = get(KEY_EFFECT + SUPPORTED_VALUES_SUFFIX);
@@ -1333,8 +1461,12 @@ public class Camera {
/**
* Gets the current antibanding setting.
*
- * @return one of ANTIBANDING_XXX string constant. null if antibanding
- * setting is not supported.
+ * @return current antibanding. null if antibanding setting is not
+ * supported.
+ * @see #ANTIBANDING_AUTO
+ * @see #ANTIBANDING_50HZ
+ * @see #ANTIBANDING_60HZ
+ * @see #ANTIBANDING_OFF
*/
public String getAntibanding() {
return get(KEY_ANTIBANDING);
@@ -1343,7 +1475,8 @@ public class Camera {
/**
* Sets the antibanding.
*
- * @param antibanding ANTIBANDING_XXX string constant.
+ * @param antibanding new antibanding value.
+ * @see #getAntibanding()
*/
public void setAntibanding(String antibanding) {
set(KEY_ANTIBANDING, antibanding);
@@ -1352,8 +1485,9 @@ public class Camera {
/**
* Gets the supported antibanding values.
*
- * @return a List of ANTIBANDING_XXX string constants. null if
- * antibanding setting is not supported.
+ * @return a list of supported antibanding values. null if antibanding
+ * setting is not supported.
+ * @see #getAntibanding()
*/
public List<String> getSupportedAntibanding() {
String str = get(KEY_ANTIBANDING + SUPPORTED_VALUES_SUFFIX);
@@ -1365,19 +1499,37 @@ public class Camera {
*
* @return one of SCENE_MODE_XXX string constant. null if scene mode
* setting is not supported.
+ * @see #SCENE_MODE_AUTO
+ * @see #SCENE_MODE_ACTION
+ * @see #SCENE_MODE_PORTRAIT
+ * @see #SCENE_MODE_LANDSCAPE
+ * @see #SCENE_MODE_NIGHT
+ * @see #SCENE_MODE_NIGHT_PORTRAIT
+ * @see #SCENE_MODE_THEATRE
+ * @see #SCENE_MODE_BEACH
+ * @see #SCENE_MODE_SNOW
+ * @see #SCENE_MODE_SUNSET
+ * @see #SCENE_MODE_STEADYPHOTO
+ * @see #SCENE_MODE_FIREWORKS
+ * @see #SCENE_MODE_SPORTS
+ * @see #SCENE_MODE_PARTY
+ * @see #SCENE_MODE_CANDLELIGHT
*/
public String getSceneMode() {
return get(KEY_SCENE_MODE);
}
/**
- * Sets the scene mode. Other parameters may be changed after changing
- * scene mode. For example, flash and supported flash mode may be
- * changed to "off" in night scene mode. After setting scene mode,
+ * Sets the scene mode. Changing scene mode may override other
+ * parameters (such as flash mode, focus mode, white balance). For
+ * example, suppose originally flash mode is on and supported flash
+ * modes are on/off. In night scene mode, both flash mode and supported
+ * flash mode may be changed to off. After setting scene mode,
* applications should call getParameters to know if some parameters are
* changed.
*
- * @param value SCENE_MODE_XXX string constants.
+ * @param value scene mode.
+ * @see #getSceneMode()
*/
public void setSceneMode(String value) {
set(KEY_SCENE_MODE, value);
@@ -1386,8 +1538,9 @@ public class Camera {
/**
* Gets the supported scene modes.
*
- * @return a List of SCENE_MODE_XXX string constant. null if scene mode
- * setting is not supported.
+ * @return a list of supported scene modes. null if scene mode setting
+ * is not supported.
+ * @see #getSceneMode()
*/
public List<String> getSupportedSceneModes() {
String str = get(KEY_SCENE_MODE + SUPPORTED_VALUES_SUFFIX);
@@ -1397,8 +1550,13 @@ public class Camera {
/**
* Gets the current flash mode setting.
*
- * @return one of FLASH_MODE_XXX string constant. null if flash mode
- * setting is not supported.
+ * @return current flash mode. null if flash mode setting is not
+ * supported.
+ * @see #FLASH_MODE_OFF
+ * @see #FLASH_MODE_AUTO
+ * @see #FLASH_MODE_ON
+ * @see #FLASH_MODE_RED_EYE
+ * @see #FLASH_MODE_TORCH
*/
public String getFlashMode() {
return get(KEY_FLASH_MODE);
@@ -1407,7 +1565,8 @@ public class Camera {
/**
* Sets the flash mode.
*
- * @param value FLASH_MODE_XXX string constants.
+ * @param value flash mode.
+ * @see #getFlashMode()
*/
public void setFlashMode(String value) {
set(KEY_FLASH_MODE, value);
@@ -1416,8 +1575,9 @@ public class Camera {
/**
* Gets the supported flash modes.
*
- * @return a List of FLASH_MODE_XXX string constants. null if flash mode
- * setting is not supported.
+ * @return a list of supported flash modes. null if flash mode setting
+ * is not supported.
+ * @see #getFlashMode()
*/
public List<String> getSupportedFlashModes() {
String str = get(KEY_FLASH_MODE + SUPPORTED_VALUES_SUFFIX);
@@ -1427,11 +1587,15 @@ public class Camera {
/**
* Gets the current focus mode setting.
*
- * @return one of FOCUS_MODE_XXX string constant. If the camera does not
- * support auto-focus, this should return {@link
- * #FOCUS_MODE_FIXED}. If the focus mode is not FOCUS_MODE_FIXED
- * or {@link #FOCUS_MODE_INFINITY}, applications should call
- * {@link #autoFocus(AutoFocusCallback)} to start the focus.
+ * @return current focus mode. If the camera does not support
+ * auto-focus, this should return {@link #FOCUS_MODE_FIXED}. If
+ * the focus mode is not FOCUS_MODE_FIXED or {@link
+ * #FOCUS_MODE_INFINITY}, applications should call {@link
+ * #autoFocus(AutoFocusCallback)} to start the focus.
+ * @see #FOCUS_MODE_AUTO
+ * @see #FOCUS_MODE_INFINITY
+ * @see #FOCUS_MODE_MACRO
+ * @see #FOCUS_MODE_FIXED
*/
public String getFocusMode() {
return get(KEY_FOCUS_MODE);
@@ -1440,7 +1604,8 @@ public class Camera {
/**
* Sets the focus mode.
*
- * @param value FOCUS_MODE_XXX string constants.
+ * @param value focus mode.
+ * @see #getFocusMode()
*/
public void setFocusMode(String value) {
set(KEY_FOCUS_MODE, value);
@@ -1449,8 +1614,9 @@ public class Camera {
/**
* Gets the supported focus modes.
*
- * @return a List of FOCUS_MODE_XXX string constants. This method will
- * always return a list with at least one element.
+ * @return a list of supported focus modes. This method will always
+ * return a list with at least one element.
+ * @see #getFocusMode()
*/
public List<String> getSupportedFocusModes() {
String str = get(KEY_FOCUS_MODE + SUPPORTED_VALUES_SUFFIX);
@@ -1458,27 +1624,119 @@ public class Camera {
}
/**
+ * Gets the focal length (in millimeter) of the camera.
+ *
+ * @return the focal length. This method will always return a valid
+ * value.
+ */
+ public float getFocalLength() {
+ return Float.parseFloat(get(KEY_FOCAL_LENGTH));
+ }
+
+ /**
+ * Gets the horizontal angle of view in degrees.
+ *
+ * @return horizontal angle of view. This method will always return a
+ * valid value.
+ */
+ public float getHorizontalViewAngle() {
+ return Float.parseFloat(get(KEY_HORIZONTAL_VIEW_ANGLE));
+ }
+
+ /**
+ * Gets the vertical angle of view in degrees.
+ *
+ * @return vertical angle of view. This method will always return a
+ * valid value.
+ */
+ public float getVerticalViewAngle() {
+ return Float.parseFloat(get(KEY_VERTICAL_VIEW_ANGLE));
+ }
+
+ /**
+ * Gets the current exposure compensation index.
+ *
+ * @return current exposure compensation index. The range is {@link
+ * #getMinExposureCompensation} to {@link
+ * #getMaxExposureCompensation}. 0 means exposure is not
+ * adjusted.
+ */
+ public int getExposureCompensation() {
+ return getInt(KEY_EXPOSURE_COMPENSATION, 0);
+ }
+
+ /**
+ * Sets the exposure compensation index.
+ *
+ * @param value exposure compensation index. The valid value range is
+ * from {@link #getMinExposureCompensation} (inclusive) to {@link
+ * #getMaxExposureCompensation} (inclusive). 0 means exposure is
+ * not adjusted. Application should call
+ * getMinExposureCompensation and getMaxExposureCompensation to
+ * know if exposure compensation is supported.
+ */
+ public void setExposureCompensation(int value) {
+ set(KEY_EXPOSURE_COMPENSATION, value);
+ }
+
+ /**
+ * Gets the maximum exposure compensation index.
+ *
+ * @return maximum exposure compensation index (>=0). If both this
+ * method and {@link #getMinExposureCompensation} return 0,
+ * exposure compensation is not supported.
+ */
+ public int getMaxExposureCompensation() {
+ return getInt(KEY_MAX_EXPOSURE_COMPENSATION, 0);
+ }
+
+ /**
+ * Gets the minimum exposure compensation index.
+ *
+ * @return minimum exposure compensation index (<=0). If both this
+ * method and {@link #getMaxExposureCompensation} return 0,
+ * exposure compensation is not supported.
+ */
+ public int getMinExposureCompensation() {
+ return getInt(KEY_MIN_EXPOSURE_COMPENSATION, 0);
+ }
+
+ /**
+ * Gets the exposure compensation step.
+ *
+ * @return exposure compensation step. Applications can get EV by
+ * multiplying the exposure compensation index and step. Ex: if
+ * exposure compensation index is -6 and step is 0.333333333, EV
+ * is -2.
+ */
+ public float getExposureCompensationStep() {
+ return getFloat(KEY_EXPOSURE_COMPENSATION_STEP, 0);
+ }
+
+ /**
* Gets current zoom value. This also works when smooth zoom is in
- * progress.
+ * progress. Applications should check {@link #isZoomSupported} before
+ * using this method.
*
* @return the current zoom value. The range is 0 to {@link
- * #getMaxZoom}.
- * @hide
+ * #getMaxZoom}. 0 means the camera is not zoomed.
*/
public int getZoom() {
- return getInt("zoom");
+ return getInt(KEY_ZOOM, 0);
}
/**
- * Sets current zoom value. If {@link #startSmoothZoom(int)} has been
- * called and zoom is not stopped yet, applications should not call this
- * method.
+ * Sets current zoom value. If the camera is zoomed (value > 0), the
+ * actual picture size may be smaller than picture size setting.
+ * Applications can check the actual picture size after picture is
+ * returned from {@link PictureCallback}. The preview size remains the
+ * same in zoom. Applications should check {@link #isZoomSupported}
+ * before using this method.
*
* @param value zoom value. The valid range is 0 to {@link #getMaxZoom}.
- * @hide
*/
public void setZoom(int value) {
- set("zoom", value);
+ set(KEY_ZOOM, value);
}
/**
@@ -1486,37 +1744,37 @@ public class Camera {
* before using other zoom methods.
*
* @return true if zoom is supported.
- * @hide
*/
public boolean isZoomSupported() {
- String str = get("zoom-supported");
- return "true".equals(str);
+ String str = get(KEY_ZOOM_SUPPORTED);
+ return TRUE.equals(str);
}
/**
* Gets the maximum zoom value allowed for snapshot. This is the maximum
* value that applications can set to {@link #setZoom(int)}.
+ * Applications should call {@link #isZoomSupported} before using this
+ * method. This value may change in different preview size. Applications
+ * should call this again after setting preview size.
*
* @return the maximum zoom value supported by the camera.
- * @hide
*/
public int getMaxZoom() {
- return getInt("max-zoom");
+ return getInt(KEY_MAX_ZOOM, 0);
}
/**
- * Gets the zoom factors of all zoom values.
+ * Gets the zoom ratios of all zoom values. Applications should check
+ * {@link #isZoomSupported} before using this method.
*
- * @return the zoom factors in 1/100 increments. Ex: a zoom of 3.2x is
- * returned as 320. Accuracy of the value is dependent on the
- * hardware implementation. The first element of the list is the
- * zoom factor of first zoom value. If the first zoom value is
- * 0, the zoom factor should be 100. The last element is the
- * zoom factor of zoom value {@link #getMaxZoom}.
- * @hide
+ * @return the zoom ratios in 1/100 increments. Ex: a zoom of 3.2x is
+ * returned as 320. The number of elements is {@link
+ * #getMaxZoom} + 1. The list is sorted from small to large. The
+ * first element is always 100. The last element is the zoom
+ * ratio of the maximum zoom value.
*/
- public List<Integer> getZoomFactors() {
- return splitInt(get("zoom-factors"));
+ public List<Integer> getZoomRatios() {
+ return splitInt(get(KEY_ZOOM_RATIOS));
}
/**
@@ -1524,11 +1782,10 @@ public class Camera {
* this before using other smooth zoom methods.
*
* @return true if smooth zoom is supported.
- * @hide
*/
public boolean isSmoothZoomSupported() {
- String str = get("smooth-zoom-supported");
- return "true".equals(str);
+ String str = get(KEY_SMOOTH_ZOOM_SUPPORTED);
+ return TRUE.equals(str);
}
// Splits a comma delimited string to an ArrayList of String.
@@ -1560,6 +1817,24 @@ public class Camera {
return substrings;
}
+ // Returns the value of a float parameter.
+ private float getFloat(String key, float defaultValue) {
+ try {
+ return Float.parseFloat(mMap.get(key));
+ } catch (NumberFormatException ex) {
+ return defaultValue;
+ }
+ }
+
+ // Returns the value of a integer parameter.
+ private int getInt(String key, int defaultValue) {
+ try {
+ return Integer.parseInt(mMap.get(key));
+ } catch (NumberFormatException ex) {
+ return defaultValue;
+ }
+ }
+
// Splits a comma delimited string to an ArrayList of Size.
// Return null if the passing string is null or the size is 0.
private ArrayList<Size> splitSize(String str) {
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index f5fed4f..317e547 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -41,7 +41,10 @@ public class Sensor {
* A constant describing an orientation sensor type.
* See {@link android.hardware.SensorEvent SensorEvent}
* for more details.
+ * @deprecated use {@link android.hardware.SensorManager#getOrientation
+ * SensorManager.getOrientation()} instead.
*/
+ @Deprecated
public static final int TYPE_ORIENTATION = 3;
/** A constant describing a gyroscope sensor type */
@@ -52,10 +55,13 @@ public class Sensor {
* for more details.
*/
public static final int TYPE_LIGHT = 5;
+
/** A constant describing a pressure sensor type */
public static final int TYPE_PRESSURE = 6;
+
/** A constant describing a temperature sensor type */
public static final int TYPE_TEMPERATURE = 7;
+
/**
* A constant describing an proximity sensor type.
* See {@link android.hardware.SensorEvent SensorEvent}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 32d5691..9a9f0bf 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -68,27 +68,30 @@ public class SensorEvent {
* All values are angles in degrees.
*
* <p>values[0]: Azimuth, angle between the magnetic north direction and
- * the Y axis, around the Z axis (0 to 359).
+ * the Y axis, around the Z axis (0 to 359).
* 0=North, 90=East, 180=South, 270=West
- *
+ *
* <p>values[1]: Pitch, rotation around X axis (-180 to 180),
* with positive values when the z-axis moves <b>toward</b> the y-axis.
*
* <p>values[2]: Roll, rotation around Y axis (-90 to 90), with
- * positive values when the x-axis moves <b>away</b> from the z-axis.
+ * positive values when the x-axis moves <b>toward</b> the z-axis.
*
+ * <p><b>Important note:</b> For historical reasons the roll angle is
+ * positive in the clockwise direction (mathematically speaking, it
+ * should be positive in the counter-clockwise direction).
+ *
* <p><b>Note:</b> This definition is different from <b>yaw, pitch and
* roll</b> used in aviation where the X axis is along the long side of
* the plane (tail to nose).
- *
- * <p><b>Note:</b> It is preferable to use
+ *
+ * <p><b>Note:</b> This sensor type exists for legacy reasons, please use
* {@link android.hardware.SensorManager#getRotationMatrix
* getRotationMatrix()} in conjunction with
* {@link android.hardware.SensorManager#remapCoordinateSystem
* remapCoordinateSystem()} and
* {@link android.hardware.SensorManager#getOrientation getOrientation()}
- * to compute these values; while it may be more expensive, it is usually
- * more accurate.
+ * to compute these values instead.
*
* <p>{@link android.hardware.Sensor#TYPE_ACCELEROMETER Sensor.TYPE_ACCELEROMETER}:<p>
* All values are in SI units (m/s^2) and measure the acceleration applied
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 271f973..98172e6 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -287,6 +287,7 @@ public class SensorManager
static private class SensorThread {
Thread mThread;
+ boolean mSensorsReady;
SensorThread() {
// this gets to the sensor module. We can have only one per process.
@@ -299,17 +300,28 @@ public class SensorManager
}
// must be called with sListeners lock
- void startLocked(ISensorService service) {
+ boolean startLocked(ISensorService service) {
try {
if (mThread == null) {
Bundle dataChannel = service.getDataChannel();
- mThread = new Thread(new SensorThreadRunnable(dataChannel),
- SensorThread.class.getName());
- mThread.start();
+ if (dataChannel != null) {
+ mSensorsReady = false;
+ SensorThreadRunnable runnable = new SensorThreadRunnable(dataChannel);
+ Thread thread = new Thread(runnable, SensorThread.class.getName());
+ thread.start();
+ synchronized (runnable) {
+ while (mSensorsReady == false) {
+ runnable.wait();
+ }
+ }
+ mThread = thread;
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in startLocked: ", e);
+ } catch (InterruptedException e) {
}
+ return mThread == null ? false : true;
}
private class SensorThreadRunnable implements Runnable {
@@ -319,13 +331,9 @@ public class SensorManager
}
private boolean open() {
- if (mDataChannel == null) {
- Log.e(TAG, "mDataChannel == NULL, exiting");
- synchronized (sListeners) {
- mThread = null;
- }
- return false;
- }
+ // NOTE: this cannot synchronize on sListeners, since
+ // it's held in the main thread at least until we
+ // return from here.
// this thread is guaranteed to be unique
Parcelable[] pfds = mDataChannel.getParcelableArray("fds");
@@ -364,12 +372,18 @@ public class SensorManager
final float[] values = new float[3];
final int[] status = new int[1];
final long timestamp[] = new long[1];
- Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
if (!open()) {
return;
}
+ synchronized (this) {
+ // we've open the driver, we're ready to open the sensors
+ mSensorsReady = true;
+ this.notify();
+ }
+
while (true) {
// wait for an event
final int sensor = sensors_data_poll(values, status, timestamp);
@@ -545,8 +559,8 @@ public class SensorManager
i = sensors_module_get_next_sensor(sensor, i);
if (i>=0) {
- Log.d(TAG, "found sensor: " + sensor.getName() +
- ", handle=" + sensor.getHandle());
+ //Log.d(TAG, "found sensor: " + sensor.getName() +
+ // ", handle=" + sensor.getHandle());
sensor.setLegacyType(getLegacySensorType(sensor.getType()));
fullList.add(sensor);
sHandleToSensor.append(sensor.getHandle(), sensor);
@@ -907,14 +921,18 @@ public class SensorManager
String name = sensor.getName();
int handle = sensor.getHandle();
if (l == null) {
+ result = false;
l = new ListenerDelegate(listener, sensor, handler);
- result = mSensorService.enableSensor(l, name, handle, delay);
- if (result) {
- sListeners.add(l);
- sListeners.notify();
- }
+ sListeners.add(l);
if (!sListeners.isEmpty()) {
- sSensorThread.startLocked(mSensorService);
+ result = sSensorThread.startLocked(mSensorService);
+ if (result) {
+ result = mSensorService.enableSensor(l, name, handle, delay);
+ if (!result) {
+ // there was an error, remove the listeners
+ sListeners.remove(l);
+ }
+ }
}
} else {
result = mSensorService.enableSensor(l, name, handle, delay);
@@ -1161,23 +1179,27 @@ public class SensorManager
*
* <p>When the rotation matrix is used for drawing (for instance with
* OpenGL ES), it usually <b>doesn't need</b> to be transformed by this
- * function, unless the screen is physically rotated, such as when used
- * in landscape mode.
+ * function, unless the screen is physically rotated, in which case you
+ * can use {@link android.view.Display#getRotation() Display.getRotation()}
+ * to retrieve the current rotation of the screen. Note that because the
+ * user is generally free to rotate their screen, you often should
+ * consider the rotation in deciding the parameters to use here.
*
* <p><u>Examples:</u><p>
*
* <li>Using the camera (Y axis along the camera's axis) for an augmented
- * reality application where the rotation angles are needed :</li><p>
+ * reality application where the rotation angles are needed: </li><p>
*
* <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code><p>
*
- * <li>Using the device as a mechanical compass in landscape mode:</li><p>
+ * <li>Using the device as a mechanical compass when rotation is
+ * {@link android.view.Surface#ROTATION_90 Surface.ROTATION_90}:</li><p>
*
* <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code><p>
*
- * Beware of the above example. This call is needed only if the device is
- * physically used in landscape mode to calculate the rotation angles (see
- * {@link #getOrientation}).
+ * Beware of the above example. This call is needed only to account for
+ * a rotation from its natural orientation when calculating the
+ * rotation angles (see {@link #getOrientation}).
* If the rotation matrix is also used for rendering, it may not need to
* be transformed, for instance if your {@link android.app.Activity
* Activity} is running in landscape mode.
@@ -1284,6 +1306,8 @@ public class SensorManager
* <li>values[1]: <i>pitch</i>, rotation around the X axis.</li>
* <li>values[2]: <i>roll</i>, rotation around the Y axis.</li>
* <p>
+ * All three angles above are in <b>radians</b> and <b>positive</b> in the
+ * <b>counter-clockwise</b> direction.
*
* @param R rotation matrix see {@link #getRotationMatrix}.
* @param values an array of 3 floats to hold the result.
@@ -1423,8 +1447,9 @@ public class SensorManager
values[3] = x;
values[4] = y;
values[5] = z;
- // TODO: add support for 180 and 270 orientations
- if (orientation == Surface.ROTATION_90) {
+
+ if ((orientation & Surface.ROTATION_90) != 0) {
+ // handles 90 and 270 rotation
switch (sensor) {
case SENSOR_ACCELEROMETER:
case SENSOR_MAGNETIC_FIELD:
@@ -1440,6 +1465,26 @@ public class SensorManager
break;
}
}
+ if ((orientation & Surface.ROTATION_180) != 0) {
+ x = values[0];
+ y = values[1];
+ z = values[2];
+ // handles 180 (flip) and 270 (flip + 90) rotation
+ switch (sensor) {
+ case SENSOR_ACCELEROMETER:
+ case SENSOR_MAGNETIC_FIELD:
+ values[0] =-x;
+ values[1] =-y;
+ values[2] = z;
+ break;
+ case SENSOR_ORIENTATION:
+ case SENSOR_ORIENTATION_RAW:
+ values[0] = (x >= 180) ? (x - 180) : (x + 180);
+ values[1] =-y;
+ values[2] =-z;
+ break;
+ }
+ }
}
}
diff --git a/core/java/android/inputmethodservice/ExtractButton.java b/core/java/android/inputmethodservice/ExtractButton.java
index d6fe38d..f91cd4e 100644
--- a/core/java/android/inputmethodservice/ExtractButton.java
+++ b/core/java/android/inputmethodservice/ExtractButton.java
@@ -1,10 +1,26 @@
+/*
+ * 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.inputmethodservice;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Button;
-/***
+/**
* Specialization of {@link Button} that ignores the window not being focused.
*/
class ExtractButton extends Button {
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 0295f69..b7d53e2 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.inputmethodservice;
import android.content.Context;
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 7ee35c0..44f30f7 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.inputmethodservice;
import com.android.internal.os.HandlerCaller;
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 74ce71a..35fd46f 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.inputmethodservice;
import com.android.internal.os.HandlerCaller;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f4fbaad..1a261d3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -16,7 +16,7 @@
package android.inputmethodservice;
-import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.Dialog;
@@ -556,7 +556,7 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
initViews();
- mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
/**
@@ -803,8 +803,8 @@ public class InputMethodService extends AbstractInputMethodService {
* candidates only mode changes.
*
* <p>The default implementation makes the layout for the window
- * FILL_PARENT x FILL_PARENT when in fullscreen mode, and
- * FILL_PARENT x WRAP_CONTENT when in non-fullscreen mode.
+ * MATCH_PARENT x MATCH_PARENT when in fullscreen mode, and
+ * MATCH_PARENT x WRAP_CONTENT when in non-fullscreen mode.
*
* @param win The input method's window.
* @param isFullscreen If true, the window is running in fullscreen mode
@@ -816,9 +816,9 @@ public class InputMethodService extends AbstractInputMethodService {
public void onConfigureWindow(Window win, boolean isFullscreen,
boolean isCandidatesOnly) {
if (isFullscreen) {
- mWindow.getWindow().setLayout(FILL_PARENT, FILL_PARENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
} else {
- mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
+ mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
}
@@ -841,7 +841,14 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public boolean onEvaluateFullscreenMode() {
Configuration config = getResources().getConfiguration();
- return config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
+ return false;
+ }
+ if (mInputEditorInfo != null
+ && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0) {
+ return false;
+ }
+ return true;
}
/**
@@ -1049,8 +1056,8 @@ public class InputMethodService extends AbstractInputMethodService {
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
mExtractFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
mExtractView = view;
if (view != null) {
mExtractEditText = (ExtractEditText)view.findViewById(
@@ -1079,7 +1086,7 @@ public class InputMethodService extends AbstractInputMethodService {
public void setCandidatesView(View view) {
mCandidatesFrame.removeAllViews();
mCandidatesFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
}
@@ -1092,7 +1099,7 @@ public class InputMethodService extends AbstractInputMethodService {
public void setInputView(View view) {
mInputFrame.removeAllViews();
mInputFrame.addView(view, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mInputView = view;
}
@@ -2022,7 +2029,10 @@ public class InputMethodService extends AbstractInputMethodService {
InputConnection ic = getCurrentInputConnection();
mExtractedText = ic == null? null
: ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
-
+ if (mExtractedText == null || ic == null) {
+ Log.e(TAG, "Unexpected null in startExtractingText : mExtractedText = "
+ + mExtractedText + ", input connection = " + ic);
+ }
final EditorInfo ei = getCurrentInputEditorInfo();
try {
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 0f7ef22..4b48409 100755..100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -165,6 +165,7 @@ public class KeyboardView extends View implements View.OnClickListener {
private static final int DELAY_BEFORE_PREVIEW = 0;
private static final int DELAY_AFTER_PREVIEW = 70;
+ private static final int DEBOUNCE_TIME = 70;
private int mVerticalCorrection;
private int mProximityThreshold;
@@ -174,6 +175,7 @@ public class KeyboardView extends View implements View.OnClickListener {
private boolean mShowTouchPoints = true;
private int mPopupPreviewX;
private int mPopupPreviewY;
+ private int mWindowY;
private int mLastX;
private int mLastY;
@@ -620,7 +622,10 @@ public class KeyboardView extends View implements View.OnClickListener {
if (mBuffer == null || mKeyboardChanged) {
if (mBuffer == null || mKeyboardChanged &&
(mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+ // Make sure our bitmap is at least 1x1
+ final int width = Math.max(1, getWidth());
+ final int height = Math.max(1, getHeight());
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBuffer);
}
invalidateAllKeys();
@@ -739,6 +744,10 @@ public class KeyboardView extends View implements View.OnClickListener {
final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
boolean isInside = key.isInside(x,y);
+ if (isInside) {
+ primaryIndex = nearestKeyIndices[i];
+ }
+
if (((mProximityCorrectOn
&& (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
|| isInside)
@@ -767,10 +776,6 @@ public class KeyboardView extends View implements View.OnClickListener {
}
}
}
-
- if (isInside) {
- primaryIndex = nearestKeyIndices[i];
- }
}
if (primaryIndex == NOT_A_KEY) {
primaryIndex = closestKey;
@@ -905,20 +910,36 @@ public class KeyboardView extends View implements View.OnClickListener {
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];
}
// Set the preview background state
mPreviewText.getBackground().setState(
key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ mPopupPreviewX += mOffsetInWindow[0];
+ mPopupPreviewY += mOffsetInWindow[1];
+
+ // If the popup cannot be shown above the key, put it on the side
+ if (mPopupPreviewY + mWindowY < 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) {
+ mPopupPreviewX += (int) (key.width * 2.5);
+ } else {
+ mPopupPreviewX -= (int) (key.width * 2.5);
+ }
+ mPopupPreviewY += popupHeight;
+ }
+
if (previewPopup.isShowing()) {
- previewPopup.update(mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1],
+ previewPopup.update(mPopupPreviewX, mPopupPreviewY,
popupWidth, popupHeight);
} else {
previewPopup.setWidth(popupWidth);
previewPopup.setHeight(popupHeight);
previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
- mPopupPreviewX + mOffsetInWindow[0],
- mPopupPreviewY + mOffsetInWindow[1]);
+ mPopupPreviewX, mPopupPreviewY);
}
mPreviewText.setVisibility(VISIBLE);
}
@@ -1118,6 +1139,12 @@ public class KeyboardView extends View implements View.OnClickListener {
if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
mSwipeTracker.addMovement(me);
+ // Ignore all motion events until a DOWN.
+ if (mAbortKey
+ && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
+ return true;
+ }
+
if (mGestureDetector.onTouchEvent(me)) {
showPreview(NOT_A_KEY);
mHandler.removeMessages(MSG_REPEAT);
@@ -1127,7 +1154,7 @@ public class KeyboardView extends View implements View.OnClickListener {
// Needs to be called after the gesture detector gets a turn, as it may have
// displayed the mini keyboard
- if (mMiniKeyboardOnScreen) {
+ if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
return true;
}
@@ -1150,9 +1177,14 @@ public class KeyboardView extends View implements View.OnClickListener {
mKeys[keyIndex].codes[0] : 0);
if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
mRepeatKeyIndex = mCurrentKey;
- repeatKey();
Message msg = mHandler.obtainMessage(MSG_REPEAT);
mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+ repeatKey();
+ // Delivering the key could have caused an abort
+ if (mAbortKey) {
+ mRepeatKeyIndex = NOT_A_KEY;
+ break;
+ }
}
if (mCurrentKey != NOT_A_KEY) {
Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
@@ -1193,6 +1225,7 @@ public class KeyboardView extends View implements View.OnClickListener {
}
}
showPreview(mCurrentKey);
+ mLastMoveTime = eventTime;
break;
case MotionEvent.ACTION_UP:
@@ -1206,7 +1239,8 @@ public class KeyboardView extends View implements View.OnClickListener {
mCurrentKey = keyIndex;
mCurrentKeyTime = 0;
}
- if (mCurrentKeyTime < mLastKeyTime && mLastKey != NOT_A_KEY) {
+ if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
+ && mLastKey != NOT_A_KEY) {
mCurrentKey = mLastKey;
touchX = mLastCodeX;
touchY = mLastCodeY;
@@ -1222,6 +1256,7 @@ public class KeyboardView extends View implements View.OnClickListener {
break;
case MotionEvent.ACTION_CANCEL:
removeMessages();
+ dismissPopupKeyboard();
mAbortKey = true;
showPreview(NOT_A_KEY);
invalidateKey(mCurrentKey);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a127df0..280ded6 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -116,6 +116,32 @@ public class ConnectivityManager
"android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
/**
+ * Broadcast Action: A tetherable connection has come or gone
+ * TODO - finish the doc
+ * @hide
+ */
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * @hide
+ * gives a String[]
+ */
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+ /**
+ * @hide
+ * gives a String[]
+ */
+ public static final String EXTRA_ACTIVE_TETHER = "activeArray";
+
+ /**
+ * @hide
+ * gives a String[]
+ */
+ public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+ /**
* The Default Mobile data connection. When active, all data traffic
* will use this connection by default. Should not coexist with other
* default connections.
@@ -129,42 +155,44 @@ public class ConnectivityManager
public static final int TYPE_WIFI = 1;
/**
* An MMS-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Multimedia Messaging
* Service servers. It may coexist with default data connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
* A SUPL-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applications needing to talk to the carrier's Secure User Plane
* Location servers for help locating the device. It may coexist with
* default data connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
* A DUN-specific Mobile data connection. This connection may be the
- * same as {@link #TYPEMOBILE} but it may be different. This is used
+ * same as {@link #TYPE_MOBILE} but it may be different. This is used
* by applicaitons performing a Dial Up Networking bridge so that
* the carrier is aware of DUN traffic. It may coexist with default data
* connections.
- * {@hide}
*/
public static final int TYPE_MOBILE_DUN = 4;
/**
* A High Priority Mobile data connection. This connection is typically
- * the same as {@link #TYPEMOBILE} but the routing setup is different.
+ * the same as {@link #TYPE_MOBILE} but the routing setup is different.
* Only requesting processes will have access to the Mobile DNS servers
* and only IP's explicitly requested via {@link #requestRouteToHost}
- * will route over this interface.
- *{@hide}
+ * will route over this interface if a default route exists.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
- /** {@hide} */
+ /**
+ * The Default WiMAX data connection. When active, all data traffic
+ * will use this connection by default. Should not coexist with other
+ * default connections.
+ */
+ public static final int TYPE_WIMAX = 6;
+ /** {@hide} TODO: Need to adjust this for WiMAX. */
public static final int MAX_RADIO_TYPE = TYPE_WIFI;
- /** {@hide} */
+ /** {@hide} TODO: Need to adjust this for WiMAX. */
public static final int MAX_NETWORK_TYPE = TYPE_MOBILE_HIPRI;
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
@@ -310,10 +338,10 @@ public class ConnectivityManager
/**
* Sets the value of the setting for background data usage.
- *
+ *
* @param allowBackgroundData Whether an application should use data while
* it is in the background.
- *
+ *
* @attr ref android.Manifest.permission#CHANGE_BACKGROUND_DATA_SETTING
* @see #getBackgroundDataSetting()
* @hide
@@ -324,7 +352,35 @@ public class ConnectivityManager
} catch (RemoteException e) {
}
}
-
+
+ /**
+ * Gets the value of the setting for enabling Mobile data.
+ *
+ * @return Whether mobile data is enabled.
+ * @hide
+ */
+ public boolean getMobileDataEnabled() {
+ try {
+ return mService.getMobileDataEnabled();
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /**
+ * Sets the persisted value for enabling/disabling Mobile data.
+ *
+ * @param enabled Whether the mobile data connection should be
+ * used or not.
+ * @hide
+ */
+ public void setMobileDataEnabled(boolean enabled) {
+ try {
+ mService.setMobileDataEnabled(enabled);
+ } catch (RemoteException e) {
+ }
+ }
+
/**
* Don't allow use of default constructor.
*/
@@ -342,4 +398,131 @@ public class ConnectivityManager
}
mService = service;
}
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetherableIfaces() {
+ try {
+ return mService.getTetherableIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetheredIfaces() {
+ try {
+ return mService.getTetheredIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetheringErroredIfaces() {
+ try {
+ return mService.getTetheringErroredIfaces();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * @return error A TETHER_ERROR value indicating success or failure type
+ * {@hide}
+ */
+ public int tether(String iface) {
+ try {
+ return mService.tether(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /**
+ * @return error A TETHER_ERROR value indicating success or failure type
+ * {@hide}
+ */
+ public int untether(String iface) {
+ try {
+ return mService.untether(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public boolean isTetheringSupported() {
+ try {
+ return mService.isTetheringSupported();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetherableUsbRegexs() {
+ try {
+ return mService.getTetherableUsbRegexs();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public String[] getTetherableWifiRegexs() {
+ try {
+ return mService.getTetherableWifiRegexs();
+ } catch (RemoteException e) {
+ return new String[0];
+ }
+ }
+
+ /** {@hide} */
+ public static final int TETHER_ERROR_NO_ERROR = 0;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
+ /** {@hide} */
+ public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNSUPPORTED = 3;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNAVAIL_IFACE = 4;
+ /** {@hide} */
+ public static final int TETHER_ERROR_MASTER_ERROR = 5;
+ /** {@hide} */
+ public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6;
+ /** {@hide} */
+ public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7;
+ /** {@hide} */
+ public static final int TETHER_ERROR_ENABLE_NAT_ERROR = 8;
+ /** {@hide} */
+ public static final int TETHER_ERROR_DISABLE_NAT_ERROR = 9;
+ /** {@hide} */
+ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
+
+ /**
+ * @param iface The name of the interface we're interested in
+ * @return error The error code of the last error tethering or untethering the named
+ * interface
+ * {@hide}
+ */
+ public int getLastTetherError(String iface) {
+ try {
+ return mService.getLastTetherError(iface);
+ } catch (RemoteException e) {
+ return TETHER_ERROR_SERVICE_UNAVAIL;
+ }
+ }
}
diff --git a/core/java/android/net/Downloads.java b/core/java/android/net/Downloads.java
new file mode 100644
index 0000000..fd33781
--- /dev/null
+++ b/core/java/android/net/Downloads.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * The Download Manager
+ *
+ * @hide
+ */
+public final class Downloads {
+
+
+ /**
+ * Download status codes
+ */
+
+ /**
+ * This download hasn't started yet
+ */
+ public static final int STATUS_PENDING = 190;
+
+ /**
+ * This download has started
+ */
+ public static final int STATUS_RUNNING = 192;
+
+ /**
+ * This download has successfully completed.
+ * Warning: there might be other status values that indicate success
+ * in the future.
+ * Use isSucccess() to capture the entire category.
+ */
+ public static final int STATUS_SUCCESS = 200;
+
+ /**
+ * This download can't be performed because the content type cannot be
+ * handled.
+ */
+ public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+ /**
+ * This download has completed with an error.
+ * Warning: there will be other status values that indicate errors in
+ * the future. Use isStatusError() to capture the entire category.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = 491;
+
+ /**
+ * This download couldn't be completed because of an HTTP
+ * redirect response that the download manager couldn't
+ * handle.
+ */
+ public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+ /**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
+ * Returns whether the status is a success (i.e. 2xx).
+ */
+ public static boolean isStatusSuccess(int status) {
+ return (status >= 200 && status < 300);
+ }
+
+ /**
+ * Returns whether the status is an error (i.e. 4xx or 5xx).
+ */
+ public static boolean isStatusError(int status) {
+ return (status >= 400 && status < 600);
+ }
+
+ /**
+ * Download destinations
+ */
+
+ /**
+ * This download will be saved to the external storage. This is the
+ * default behavior, and should be used for any file that the user
+ * can freely access, copy, delete. Even with that destination,
+ * unencrypted DRM files are saved in secure internal storage.
+ * Downloads to the external destination only write files for which
+ * there is a registered handler. The resulting files are accessible
+ * by filename to all applications.
+ */
+ public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition. This is the behavior used by applications that want to
+ * download private files that are used and deleted soon after they
+ * get downloaded. All file types are allowed, and only the initiating
+ * application can access the file (indirectly through a content
+ * provider). This requires the
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+ */
+ public static final int DOWNLOAD_DESTINATION_CACHE = 2;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition and will be purged as necessary to make space. This is
+ * for private files (similar to CACHE_PARTITION) that aren't deleted
+ * immediately after they are used, and are kept around by the download
+ * manager as long as space is available.
+ */
+ public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3;
+
+
+ /**
+ * An invalid download id
+ */
+ public static final long DOWNLOAD_ID_INVALID = -1;
+
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when that download completes. The
+ * download's content: uri is specified in the intent's data.
+ */
+ public static final String ACTION_DOWNLOAD_COMPLETED =
+ "android.intent.action.DOWNLOAD_COMPLETED";
+
+ /**
+ * If extras are specified when requesting a download they will be provided in the intent that
+ * is sent to the specified class and package when a download has finished.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
+
+
+ /**
+ * Status class for a download
+ */
+ public static final class StatusInfo {
+ public boolean completed = false;
+ /** The filename of the active download. */
+ public String filename = null;
+ /** An opaque id for the download */
+ public long id = DOWNLOAD_ID_INVALID;
+ /** An opaque status code for the download */
+ public int statusCode = -1;
+ /** Approximate number of bytes downloaded so far, for debugging purposes. */
+ public long bytesSoFar = -1;
+
+ /**
+ * Returns whether the download is completed
+ * @return a boolean whether the download is complete.
+ */
+ public boolean isComplete() {
+ return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
+ }
+
+ /**
+ * Returns whether the download is successful
+ * @return a boolean whether the download is successful.
+ */
+ public boolean isSuccessful() {
+ return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
+ }
+ }
+
+ /**
+ * Class to access initiate and query download by server uri
+ */
+ public static final class ByUri extends DownloadBase {
+ /** @hide */
+ private ByUri() {}
+
+ /**
+ * Query where clause by app data.
+ * @hide
+ */
+ private static final String QUERY_WHERE_APP_DATA_CLAUSE =
+ android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?";
+
+ /**
+ * Gets a Cursor pointing to the download(s) of the current system update.
+ * @hide
+ */
+ private static final Cursor getCurrentOtaDownloads(Context context, String url) {
+ return context.getContentResolver().query(
+ android.provider.Downloads.Impl.CONTENT_URI,
+ DOWNLOADS_PROJECTION,
+ QUERY_WHERE_APP_DATA_CLAUSE,
+ new String[] {url},
+ null);
+ }
+
+ /**
+ * Returns a StatusInfo with the result of trying to download the
+ * given URL. Returns null if no attempts have been made.
+ */
+ public static final StatusInfo getStatus(
+ Context context,
+ String url,
+ long redownload_threshold) {
+ StatusInfo result = null;
+ boolean hasFailedDownload = false;
+ long failedDownloadModificationTime = 0;
+ Cursor c = getCurrentOtaDownloads(context, url);
+ try {
+ while (c != null && c.moveToNext()) {
+ if (result == null) {
+ result = new StatusInfo();
+ }
+ int status = getStatusOfDownload(c, redownload_threshold);
+ if (status == STATUS_DOWNLOADING_UPDATE ||
+ status == STATUS_DOWNLOADED_UPDATE) {
+ result.completed = (status == STATUS_DOWNLOADED_UPDATE);
+ result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
+ result.id = c.getLong(DOWNLOADS_COLUMN_ID);
+ result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
+ result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
+ return result;
+ }
+
+ long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
+ if (hasFailedDownload &&
+ modTime < failedDownloadModificationTime) {
+ // older than the one already in result; skip it.
+ continue;
+ }
+
+ hasFailedDownload = true;
+ failedDownloadModificationTime = modTime;
+ result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
+ result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
+ }
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Query where clause for general querying.
+ */
+ private static final String QUERY_WHERE_CLAUSE =
+ android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
+ android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
+
+ /**
+ * Delete all the downloads for a package/class pair.
+ */
+ public static final void removeAllDownloadsByPackage(
+ Context context,
+ String notification_package,
+ String notification_class) {
+ context.getContentResolver().delete(
+ android.provider.Downloads.Impl.CONTENT_URI,
+ QUERY_WHERE_CLAUSE,
+ new String[] { notification_package, notification_class });
+ }
+
+ /**
+ * The column for the id in the Cursor returned by
+ * getProgressCursor()
+ */
+ public static final int getProgressColumnId() {
+ return 0;
+ }
+
+ /**
+ * The column for the current byte count in the Cursor returned by
+ * getProgressCursor()
+ */
+ public static final int getProgressColumnCurrentBytes() {
+ return 1;
+ }
+
+ /**
+ * The column for the total byte count in the Cursor returned by
+ * getProgressCursor()
+ */
+ public static final int getProgressColumnTotalBytes() {
+ return 2;
+ }
+
+ /** @hide */
+ private static final String[] PROJECTION = {
+ BaseColumns._ID,
+ android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
+ android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
+ };
+
+ /**
+ * Returns a Cursor representing the progress of the download identified by the ID.
+ */
+ public static final Cursor getProgressCursor(Context context, long id) {
+ Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI,
+ String.valueOf(id));
+ return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
+ }
+ }
+
+ /**
+ * Class to access downloads by opaque download id
+ */
+ public static final class ById extends DownloadBase {
+ /** @hide */
+ private ById() {}
+
+ /**
+ * Get the mime tupe of the download specified by the download id
+ */
+ public static String getMimeTypeForId(Context context, long downloadId) {
+ ContentResolver cr = context.getContentResolver();
+
+ String mimeType = null;
+ Cursor downloadCursor = null;
+
+ try {
+ Uri downloadUri = getDownloadUri(downloadId);
+
+ downloadCursor = cr.query(
+ downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE},
+ null, null, null);
+ if (downloadCursor.moveToNext()) {
+ mimeType = downloadCursor.getString(0);
+ }
+ } finally {
+ if (downloadCursor != null) downloadCursor.close();
+ }
+ return mimeType;
+ }
+
+ /**
+ * Delete a download by Id
+ */
+ public static void deleteDownload(Context context, long downloadId) {
+ ContentResolver cr = context.getContentResolver();
+
+ String mimeType = null;
+
+ Uri downloadUri = getDownloadUri(downloadId);
+
+ cr.delete(downloadUri, null, null);
+ }
+
+ /**
+ * Open a filedescriptor to a particular download
+ */
+ public static ParcelFileDescriptor openDownload(
+ Context context, long downloadId, String mode)
+ throws FileNotFoundException
+ {
+ ContentResolver cr = context.getContentResolver();
+
+ String mimeType = null;
+
+ Uri downloadUri = getDownloadUri(downloadId);
+
+ return cr.openFileDescriptor(downloadUri, mode);
+ }
+
+ /**
+ * Open a stream to a particular download
+ */
+ public static InputStream openDownloadStream(Context context, long downloadId)
+ throws FileNotFoundException, IOException
+ {
+ ContentResolver cr = context.getContentResolver();
+
+ String mimeType = null;
+
+ Uri downloadUri = getDownloadUri(downloadId);
+
+ return cr.openInputStream(downloadUri);
+ }
+
+ private static Uri getDownloadUri(long downloadId) {
+ return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId);
+ }
+
+ /**
+ * Returns a StatusInfo with the result of trying to download the
+ * given URL. Returns null if no attempts have been made.
+ */
+ public static final StatusInfo getStatus(
+ Context context,
+ long downloadId) {
+ StatusInfo result = null;
+ boolean hasFailedDownload = false;
+ long failedDownloadModificationTime = 0;
+
+ Uri downloadUri = getDownloadUri(downloadId);
+
+ ContentResolver cr = context.getContentResolver();
+
+ Cursor c = cr.query(
+ downloadUri, DOWNLOADS_PROJECTION, null /* selection */, null /* selection args */,
+ null /* sort order */);
+ try {
+ if (!c.moveToNext()) {
+ return result;
+ }
+
+ if (result == null) {
+ result = new StatusInfo();
+ }
+ int status = getStatusOfDownload(c,0);
+ if (status == STATUS_DOWNLOADING_UPDATE ||
+ status == STATUS_DOWNLOADED_UPDATE) {
+ result.completed = (status == STATUS_DOWNLOADED_UPDATE);
+ result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
+ result.id = c.getLong(DOWNLOADS_COLUMN_ID);
+ result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
+ result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
+ return result;
+ }
+
+ long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
+
+ result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
+ result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ return result;
+ }
+ }
+
+
+ /**
+ * Base class with common functionality for the various download classes
+ */
+ public static class DownloadBase {
+ /** @hide */
+ DownloadBase() {}
+
+ /**
+ * Initiate a download where the download will be tracked by its URI.
+ */
+ public static long startDownloadByUri(
+ Context context,
+ String url,
+ String cookieData,
+ boolean showDownload,
+ int downloadDestination,
+ boolean allowRoaming,
+ boolean skipIntegrityCheck,
+ String title,
+ String notification_package,
+ String notification_class,
+ String notification_extras) {
+ ContentResolver cr = context.getContentResolver();
+
+ // Tell download manager to start downloading update.
+ ContentValues values = new ContentValues();
+ values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
+ values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
+ values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
+ showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
+ : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
+ if (title != null) {
+ values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
+ }
+ values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
+
+
+ // NOTE: destination should be seperated from whether the download
+ // can happen when roaming
+ int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
+ switch (downloadDestination) {
+ case DOWNLOAD_DESTINATION_EXTERNAL:
+ destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
+ break;
+ case DOWNLOAD_DESTINATION_CACHE:
+ if (allowRoaming) {
+ destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
+ } else {
+ destination =
+ android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
+ }
+ break;
+ case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
+ destination =
+ android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
+ break;
+ }
+ values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
+ values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
+ skipIntegrityCheck); // Don't check ETag
+ if (notification_package != null && notification_class != null) {
+ values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
+ notification_package);
+ values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
+ notification_class);
+
+ if (notification_extras != null) {
+ values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
+ notification_extras);
+ }
+ }
+
+ Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
+
+ long downloadId = DOWNLOAD_ID_INVALID;
+ if (downloadUri != null) {
+ downloadId = Long.parseLong(downloadUri.getLastPathSegment());
+ }
+ return downloadId;
+ }
+ }
+
+ /** @hide */
+ private static final int STATUS_INVALID = 0;
+ /** @hide */
+ private static final int STATUS_DOWNLOADING_UPDATE = 3;
+ /** @hide */
+ private static final int STATUS_DOWNLOADED_UPDATE = 4;
+
+ /**
+ * Column projection for the query to the download manager. This must match
+ * with the constants DOWNLOADS_COLUMN_*.
+ * @hide
+ */
+ private static final String[] DOWNLOADS_PROJECTION = {
+ BaseColumns._ID,
+ android.provider.Downloads.Impl.COLUMN_APP_DATA,
+ android.provider.Downloads.Impl.COLUMN_STATUS,
+ android.provider.Downloads.Impl._DATA,
+ android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
+ android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
+ };
+
+ /**
+ * The column index for the ID.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_ID = 0;
+ /**
+ * The column index for the URI.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_URI = 1;
+ /**
+ * The column index for the status code.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_STATUS = 2;
+ /**
+ * The column index for the filename.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_FILENAME = 3;
+ /**
+ * The column index for the last modification time.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
+ /**
+ * The column index for the number of bytes downloaded so far.
+ * @hide
+ */
+ private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
+
+ /**
+ * Gets the status of a download.
+ *
+ * @param c A Cursor pointing to a download. The URL column is assumed to be valid.
+ * @return The status of the download.
+ * @hide
+ */
+ private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
+ int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
+ long realtime = SystemClock.elapsedRealtime();
+
+ // TODO(dougz): special handling of 503, 404? (eg, special
+ // explanatory messages to user)
+
+ if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
+ // Check if it's stuck
+ long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
+ long now = System.currentTimeMillis();
+ if (now < modified || now - modified > redownload_threshold) {
+ return STATUS_INVALID;
+ }
+
+ return STATUS_DOWNLOADING_UPDATE;
+ }
+
+ if (android.provider.Downloads.Impl.isStatusError(status)) {
+ return STATUS_INVALID;
+ }
+
+ String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
+ if (filename == null) {
+ return STATUS_INVALID;
+ }
+
+ return STATUS_DOWNLOADED_UPDATE;
+ }
+
+
+ /**
+ * @hide
+ */
+ private Downloads() {}
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 9f59cce..b05c2ed 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -50,4 +50,26 @@ interface IConnectivityManager
boolean getBackgroundDataSetting();
void setBackgroundDataSetting(boolean allowBackgroundData);
+
+ boolean getMobileDataEnabled();
+
+ void setMobileDataEnabled(boolean enabled);
+
+ int tether(String iface);
+
+ int untether(String iface);
+
+ int getLastTetherError(String iface);
+
+ boolean isTetheringSupported();
+
+ String[] getTetherableIfaces();
+
+ String[] getTetheredIfaces();
+
+ String[] getTetheringErroredIfaces();
+
+ String[] getTetherableUsbRegexs();
+
+ String[] getTetherableWifiRegexs();
}
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
new file mode 100644
index 0000000..d30b63d
--- /dev/null
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.net;
+
+/**
+ * Callback class for receiving events from an INetworkManagementService
+ *
+ * @hide
+ */
+interface INetworkManagementEventObserver {
+ /**
+ * Interface link status has changed.
+ *
+ * @param iface The interface.
+ * @param link True if link is up.
+ */
+ void interfaceLinkStatusChanged(String iface, boolean link);
+
+ /**
+ * An interface has been added to the system
+ *
+ * @param iface The interface.
+ */
+ void interfaceAdded(String iface);
+
+ /**
+ * An interface has been removed from the system
+ *
+ * @param iface The interface.
+ */
+ void interfaceRemoved(String iface);
+}
diff --git a/core/java/android/net/IThrottleManager.aidl b/core/java/android/net/IThrottleManager.aidl
new file mode 100644
index 0000000..a12469d
--- /dev/null
+++ b/core/java/android/net/IThrottleManager.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.IBinder;
+
+/**
+ * Interface that answers queries about data transfer amounts and throttling
+ */
+/** {@hide} */
+interface IThrottleManager
+{
+ long getByteCount(String iface, int dir, int period, int ago);
+
+ int getThrottle(String iface);
+
+ long getResetTime(String iface);
+
+ long getPeriodStartTime(String iface);
+
+ long getCliffThreshold(String iface, int cliff);
+
+ int getCliffLevel(String iface, int cliff);
+
+ String getHelpUri();
+}
diff --git a/core/java/com/google/android/net/ParentalControlState.aidl b/core/java/android/net/InterfaceConfiguration.aidl
index ed1326a..8aa5e34 100644
--- a/core/java/com/google/android/net/ParentalControlState.aidl
+++ b/core/java/android/net/InterfaceConfiguration.aidl
@@ -1,18 +1,19 @@
/**
* Copyright (c) 2008, The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.google.android.net;
-parcelable ParentalControlState;
+package android.net;
+
+parcelable InterfaceConfiguration;
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
new file mode 100644
index 0000000..915c5d7
--- /dev/null
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * A simple object for retrieving / setting an interfaces configuration
+ * @hide
+ */
+public class InterfaceConfiguration implements Parcelable {
+ public String hwAddr;
+ public int ipAddr;
+ public int netmask;
+ public String interfaceFlags;
+
+ public InterfaceConfiguration() {
+ super();
+ }
+
+ public String toString() {
+ StringBuffer str = new StringBuffer();
+
+ str.append("ipddress "); putAddress(str, ipAddr);
+ str.append(" netmask "); putAddress(str, netmask);
+ str.append(" flags ").append(interfaceFlags);
+ str.append(" hwaddr ").append(hwAddr);
+
+ return str.toString();
+ }
+
+ private static void putAddress(StringBuffer buf, int addr) {
+ buf.append((addr >> 24) & 0xff).append('.').
+ append((addr >> 16) & 0xff).append('.').
+ append((addr >> 8) & 0xff).append('.').
+ append(addr & 0xff);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(hwAddr);
+ dest.writeInt(ipAddr);
+ dest.writeInt(netmask);
+ dest.writeString(interfaceFlags);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<InterfaceConfiguration> CREATOR =
+ new Creator<InterfaceConfiguration>() {
+ public InterfaceConfiguration createFromParcel(Parcel in) {
+ InterfaceConfiguration info = new InterfaceConfiguration();
+ info.hwAddr = in.readString();
+ info.ipAddr = in.readInt();
+ info.netmask = in.readInt();
+ info.interfaceFlags = in.readString();
+ return info;
+ }
+
+ public InterfaceConfiguration[] newArray(int size) {
+ return new InterfaceConfiguration[size];
+ }
+ };
+}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 8113aaa..214510d 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -48,6 +48,7 @@ public class MobileDataStateTracker extends NetworkStateTracker {
private ITelephony mPhoneService;
private String mApnType;
+ private String mApnTypeToWatchFor;
private String mApnName;
private boolean mEnabled;
private BroadcastReceiver mStateReceiver;
@@ -60,12 +61,17 @@ public class MobileDataStateTracker extends NetworkStateTracker {
* @param apnType the Phone apnType
* @param tag the name of this network
*/
- public MobileDataStateTracker(Context context, Handler target,
- int netType, String apnType, String tag) {
+ public MobileDataStateTracker(Context context, Handler target, int netType, String tag) {
super(context, target, netType,
TelephonyManager.getDefault().getNetworkType(), tag,
TelephonyManager.getDefault().getNetworkTypeName());
- mApnType = apnType;
+ mApnType = networkTypeToApnType(netType);
+ if (TextUtils.equals(mApnType, Phone.APN_TYPE_HIPRI)) {
+ mApnTypeToWatchFor = Phone.APN_TYPE_DEFAULT;
+ } else {
+ mApnTypeToWatchFor = mApnType;
+ }
+
mPhoneService = null;
if(netType == ConnectivityManager.TYPE_MOBILE) {
mEnabled = true;
@@ -123,7 +129,7 @@ public class MobileDataStateTracker extends NetworkStateTracker {
String[] list = typeList.split(",");
for(int i=0; i< list.length; i++) {
- if (TextUtils.equals(list[i], mApnType) ||
+ if (TextUtils.equals(list[i], mApnTypeToWatchFor) ||
TextUtils.equals(list[i], Phone.APN_TYPE_ALL)) {
return true;
}
@@ -343,7 +349,7 @@ public class MobileDataStateTracker extends NetworkStateTracker {
ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(Phone.STATE_KEY, Phone.DataState.CONNECTED.toString());
intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, Phone.REASON_APN_CHANGED);
- intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnType);
+ intent.putExtra(Phone.DATA_APN_TYPES_KEY, mApnTypeToWatchFor);
intent.putExtra(Phone.DATA_APN_KEY, mApnName);
intent.putExtra(Phone.DATA_IFACE_NAME_KEY, mInterfaceName);
intent.putExtra(Phone.NETWORK_UNAVAILABLE_KEY, false);
@@ -356,12 +362,21 @@ public class MobileDataStateTracker extends NetworkStateTracker {
case Phone.APN_REQUEST_FAILED:
if (mPhoneService == null && mApnType == Phone.APN_TYPE_DEFAULT) {
// on startup we may try to talk to the phone before it's ready
- // just leave mEnabled as it is for the default apn.
+ // since the phone will come up enabled, go with that.
+ // TODO - this also comes up on telephony crash: if we think mobile data is
+ // off and the telephony stuff crashes and has to restart it will come up
+ // enabled (making a data connection). We will then be out of sync.
+ // A possible solution is a broadcast when telephony restarts.
+ mEnabled = true;
return false;
}
// else fall through
case Phone.APN_TYPE_NOT_AVAILABLE:
- mEnabled = false;
+ // Default is always available, but may be off due to
+ // AirplaneMode or E-Call or whatever..
+ if (mApnType != Phone.APN_TYPE_DEFAULT) {
+ mEnabled = false;
+ }
break;
default:
Log.e(TAG, "Error in reconnect - unexpected response.");
@@ -504,4 +519,22 @@ public class MobileDataStateTracker extends NetworkStateTracker {
+ " APN type \"" + apnType + "\"");
return Phone.APN_REQUEST_FAILED;
}
+
+ public static String networkTypeToApnType(int netType) {
+ switch(netType) {
+ case ConnectivityManager.TYPE_MOBILE:
+ return Phone.APN_TYPE_DEFAULT; // TODO - use just one of these
+ case ConnectivityManager.TYPE_MOBILE_MMS:
+ return Phone.APN_TYPE_MMS;
+ case ConnectivityManager.TYPE_MOBILE_SUPL:
+ return Phone.APN_TYPE_SUPL;
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ return Phone.APN_TYPE_DUN;
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ return Phone.APN_TYPE_HIPRI;
+ default:
+ Log.e(TAG, "Error mapping networkType " + netType + " to apnType.");
+ return null;
+ }
+ }
}
diff --git a/core/java/android/net/NetworkConnectivityListener.java b/core/java/android/net/NetworkConnectivityListener.java
deleted file mode 100644
index 858fc77..0000000
--- a/core/java/android/net/NetworkConnectivityListener.java
+++ /dev/null
@@ -1,220 +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.net;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * A wrapper for a broadcast receiver that provides network connectivity
- * state information, independent of network type (mobile, Wi-Fi, etc.).
- * {@hide}
- */
-public class NetworkConnectivityListener {
- private static final String TAG = "NetworkConnectivityListener";
- private static final boolean DBG = false;
-
- private Context mContext;
- private HashMap<Handler, Integer> mHandlers = new HashMap<Handler, Integer>();
- private State mState;
- private boolean mListening;
- private String mReason;
- private boolean mIsFailover;
-
- /** Network connectivity information */
- private NetworkInfo mNetworkInfo;
-
- /**
- * In case of a Disconnect, the connectivity manager may have
- * already established, or may be attempting to establish, connectivity
- * with another network. If so, {@code mOtherNetworkInfo} will be non-null.
- */
- private NetworkInfo mOtherNetworkInfo;
-
- private ConnectivityBroadcastReceiver mReceiver;
-
- private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
- mListening == false) {
- Log.w(TAG, "onReceived() called with " + mState.toString() + " and " + intent);
- return;
- }
-
- boolean noConnectivity =
- intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
-
- if (noConnectivity) {
- mState = State.NOT_CONNECTED;
- } else {
- mState = State.CONNECTED;
- }
-
- mNetworkInfo = (NetworkInfo)
- intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
- mOtherNetworkInfo = (NetworkInfo)
- intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
-
- mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
- mIsFailover =
- intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
-
- if (DBG) {
- Log.d(TAG, "onReceive(): mNetworkInfo=" + mNetworkInfo + " mOtherNetworkInfo = "
- + (mOtherNetworkInfo == null ? "[none]" : mOtherNetworkInfo +
- " noConn=" + noConnectivity) + " mState=" + mState.toString());
- }
-
- // Notifiy any handlers.
- Iterator<Handler> it = mHandlers.keySet().iterator();
- while (it.hasNext()) {
- Handler target = it.next();
- Message message = Message.obtain(target, mHandlers.get(target));
- target.sendMessage(message);
- }
- }
- };
-
- public enum State {
- UNKNOWN,
-
- /** This state is returned if there is connectivity to any network **/
- CONNECTED,
- /**
- * This state is returned if there is no connectivity to any network. This is set
- * to true under two circumstances:
- * <ul>
- * <li>When connectivity is lost to one network, and there is no other available
- * network to attempt to switch to.</li>
- * <li>When connectivity is lost to one network, and the attempt to switch to
- * another network fails.</li>
- */
- NOT_CONNECTED
- }
-
- /**
- * Create a new NetworkConnectivityListener.
- */
- public NetworkConnectivityListener() {
- mState = State.UNKNOWN;
- mReceiver = new ConnectivityBroadcastReceiver();
- }
-
- /**
- * This method starts listening for network connectivity state changes.
- * @param context
- */
- public synchronized void startListening(Context context) {
- if (!mListening) {
- mContext = context;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(mReceiver, filter);
- mListening = true;
- }
- }
-
- /**
- * This method stops this class from listening for network changes.
- */
- public synchronized void stopListening() {
- if (mListening) {
- mContext.unregisterReceiver(mReceiver);
- mContext = null;
- mNetworkInfo = null;
- mOtherNetworkInfo = null;
- mIsFailover = false;
- mReason = null;
- mListening = false;
- }
- }
-
- /**
- * This methods registers a Handler to be called back onto with the specified what code when
- * the network connectivity state changes.
- *
- * @param target The target handler.
- * @param what The what code to be used when posting a message to the handler.
- */
- public void registerHandler(Handler target, int what) {
- mHandlers.put(target, what);
- }
-
- /**
- * This methods unregisters the specified Handler.
- * @param target
- */
- public void unregisterHandler(Handler target) {
- mHandlers.remove(target);
- }
-
- public State getState() {
- return mState;
- }
-
- /**
- * Return the NetworkInfo associated with the most recent connectivity event.
- * @return {@code NetworkInfo} for the network that had the most recent connectivity event.
- */
- public NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
- }
-
- /**
- * If the most recent connectivity event was a DISCONNECT, return
- * any information supplied in the broadcast about an alternate
- * network that might be available. If this returns a non-null
- * value, then another broadcast should follow shortly indicating
- * whether connection to the other network succeeded.
- *
- * @return NetworkInfo
- */
- public NetworkInfo getOtherNetworkInfo() {
- return mOtherNetworkInfo;
- }
-
- /**
- * Returns true if the most recent event was for an attempt to switch over to
- * a new network following loss of connectivity on another network.
- * @return {@code true} if this was a failover attempt, {@code false} otherwise.
- */
- public boolean isFailover() {
- return mIsFailover;
- }
-
- /**
- * An optional reason for the connectivity state change may have been supplied.
- * This returns it.
- * @return the reason for the state change, if available, or {@code null}
- * otherwise.
- */
- public String getReason() {
- return mReason;
- }
-}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index a97b9e5..a8c6f9b 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,11 +16,13 @@
package android.net;
-import android.net.http.DomainNameChecker;
+import com.android.internal.net.DomainNameValidator;
+
import android.os.SystemProperties;
import android.util.Config;
import android.util.Log;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
@@ -39,224 +41,195 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
/**
- * SSLSocketFactory that provides optional (on debug devices, only) skipping of ssl certificfate
- * chain validation and custom read timeouts used just when connecting to the server/negotiating
- * an ssl session.
- *
- * You can skip the ssl certificate checking at runtime by setting socket.relaxsslcheck=yes on
- * devices that do not have have ro.secure set.
+ * SSLSocketFactory implementation with several extra features:
+ * <ul>
+ * <li>Timeout specification for SSL handshake operations
+ * <li>Optional SSL session caching with {@link SSLSessionCache}
+ * <li>Optionally bypass all SSL certificate checks
+ * </ul>
+ * Note that the handshake timeout does not apply to actual connection.
+ * If you want a connection timeout as well, use {@link #createSocket()} and
+ * {@link Socket#connect(SocketAddress, int)}.
+ * <p>
+ * On development devices, "setprop socket.relaxsslcheck yes" bypasses all
+ * SSL certificate checks, for testing with development servers.
*/
public class SSLCertificateSocketFactory extends SSLSocketFactory {
+ private static final String TAG = "SSLCertificateSocketFactory";
- private static final String LOG_TAG = "SSLCertificateSocketFactory";
-
- private static final TrustManager[] TRUST_MANAGER = new TrustManager[] {
+ private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] {
new X509TrustManager() {
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
-
- public void checkClientTrusted(X509Certificate[] certs,
- String authType) { }
-
- public void checkServerTrusted(X509Certificate[] certs,
- String authType) { }
+ public X509Certificate[] getAcceptedIssuers() { return null; }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) { }
+ public void checkServerTrusted(X509Certificate[] certs, String authType) { }
}
};
- private final SSLSocketFactory mFactory;
+ private SSLSocketFactory mInsecureFactory = null;
+ private SSLSocketFactory mSecureFactory = null;
- private final int mSocketReadTimeoutForSslHandshake;
+ private final int mHandshakeTimeoutMillis;
+ private final SSLClientSessionCache mSessionCache;
+ private final boolean mSecure;
+
+ /** @deprecated Use {@link #getDefault(int)} instead. */
+ public SSLCertificateSocketFactory(int handshakeTimeoutMillis) {
+ this(handshakeTimeoutMillis, null, true);
+ }
+
+ private SSLCertificateSocketFactory(
+ int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) {
+ mHandshakeTimeoutMillis = handshakeTimeoutMillis;
+ mSessionCache = cache == null ? null : cache.mSessionCache;
+ mSecure = secure;
+ }
/**
- * Do not use this constructor (will be deprecated). Use {@link #getDefault(int)} instead.
+ * Returns a new socket factory instance with an optional handshake timeout.
+ *
+ * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0
+ * for none. The socket timeout is reset to 0 after the handshake.
+ * @return a new SocketFactory with the specified parameters
*/
- public SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake)
- throws NoSuchAlgorithmException, KeyManagementException {
- this(socketReadTimeoutForSslHandshake, null /* cache */);
+ public static SocketFactory getDefault(int handshakeTimeoutMillis) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true);
}
- private SSLCertificateSocketFactory(int socketReadTimeoutForSslHandshake,
- SSLClientSessionCache cache) throws NoSuchAlgorithmException, KeyManagementException {
- SSLContextImpl sslContext = new SSLContextImpl();
- sslContext.engineInit(null /* kms */,
- TRUST_MANAGER, new java.security.SecureRandom(),
- cache /* client cache */, null /* server cache */);
- this.mFactory = sslContext.engineGetSocketFactory();
- this.mSocketReadTimeoutForSslHandshake = socketReadTimeoutForSslHandshake;
+ /**
+ * Returns a new socket factory instance with an optional handshake timeout
+ * and SSL session cache.
+ *
+ * @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.
+ * @return a new SocketFactory with the specified parameters
+ */
+ public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true);
}
/**
- * Returns a new instance of a socket factory using the specified socket read
- * timeout while connecting with the server/negotiating an ssl session.
+ * Returns a new instance of a socket factory with all SSL security checks
+ * disabled, using an optional handshake timeout and SSL session cache.
+ * Sockets created using this factory are vulnerable to man-in-the-middle
+ * attacks!
*
- * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
- * ssl handshake. The socket read timeout is set back to 0 after the handshake.
- * @return a new SocketFactory, or null on error
+ * @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.
+ * @return an insecure SocketFactory with the specified parameters
*/
- public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake) {
- return getDefault(socketReadTimeoutForSslHandshake, null /* cache */);
+ public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) {
+ return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false);
}
/**
- * Returns a new instance of a socket factory using the specified socket read
- * timeout while connecting with the server/negotiating an ssl session.
- * Persists ssl sessions using the provided {@link SSLClientSessionCache}.
- *
- * @param socketReadTimeoutForSslHandshake the socket read timeout used for performing
- * ssl handshake. The socket read timeout is set back to 0 after the handshake.
- * @param cache The {@link SSLClientSessionCache} to use, if any.
- * @return a new SocketFactory, or null on error
+ * Returns a socket factory (also named SSLSocketFactory, but in a different
+ * namespace) for use with the Apache HTTP stack.
*
- * @hide
- */
- public static SocketFactory getDefault(int socketReadTimeoutForSslHandshake,
- SSLClientSessionCache cache) {
+ * @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.
+ * @return a new SocketFactory with the specified parameters
+ */
+ public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
+ int handshakeTimeoutMillis,
+ SSLSessionCache cache) {
+ return new org.apache.http.conn.ssl.SSLSocketFactory(
+ new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true));
+ }
+
+ private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) {
try {
- return new SSLCertificateSocketFactory(socketReadTimeoutForSslHandshake, cache);
- } catch (NoSuchAlgorithmException e) {
- Log.e(LOG_TAG,
- "SSLCertifcateSocketFactory.getDefault" +
- " NoSuchAlgorithmException " , e);
- return null;
+ SSLContextImpl sslContext = new SSLContextImpl();
+ sslContext.engineInit(null, trustManagers, null, mSessionCache, null);
+ return sslContext.engineGetSocketFactory();
} catch (KeyManagementException e) {
- Log.e(LOG_TAG,
- "SSLCertifcateSocketFactory.getDefault" +
- " KeyManagementException " , e);
- return null;
+ Log.wtf(TAG, e);
+ return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback
}
}
- private boolean hasValidCertificateChain(Certificate[] certs)
- throws IOException {
- boolean trusted = (certs != null && (certs.length > 0));
-
- if (trusted) {
- try {
- // the authtype we pass in doesn't actually matter
- SSLParameters.getDefaultTrustManager()
- .checkServerTrusted((X509Certificate[]) certs, "RSA");
- } catch (GeneralSecurityException e) {
- String exceptionMessage = e != null ? e.getMessage() : "none";
- if (Config.LOGD) {
- Log.d(LOG_TAG,"hasValidCertificateChain(): sec. exception: "
- + exceptionMessage);
+ private synchronized SSLSocketFactory getDelegate() {
+ // Relax the SSL check if instructed (for this factory, or systemwide)
+ if (!mSecure || ("1".equals(SystemProperties.get("ro.debuggable")) &&
+ "yes".equals(SystemProperties.get("socket.relaxsslcheck")))) {
+ if (mInsecureFactory == null) {
+ if (mSecure) {
+ Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***");
+ } else {
+ Log.w(TAG, "Bypassing SSL security checks at caller's request");
}
- trusted = false;
- }
- }
-
- return trusted;
- }
-
- private void validateSocket(SSLSocket sslSock, String destHost)
- throws IOException
- {
- if (Config.LOGV) {
- Log.v(LOG_TAG,"validateSocket() to host "+destHost);
- }
-
- String relaxSslCheck = SystemProperties.get("socket.relaxsslcheck");
- String secure = SystemProperties.get("ro.secure");
-
- // only allow relaxing the ssl check on non-secure builds where the relaxation is
- // specifically requested.
- if ("0".equals(secure) && "yes".equals(relaxSslCheck)) {
- if (Config.LOGD) {
- Log.d(LOG_TAG,"sys prop socket.relaxsslcheck is set," +
- " ignoring invalid certs");
+ mInsecureFactory = makeSocketFactory(INSECURE_TRUST_MANAGER);
}
- return;
- }
-
- Certificate[] certs = null;
- sslSock.setUseClientMode(true);
- sslSock.startHandshake();
- certs = sslSock.getSession().getPeerCertificates();
-
- // check that the root certificate in the chain belongs to
- // a CA we trust
- if (certs == null) {
- Log.e(LOG_TAG,
- "[SSLCertificateSocketFactory] no trusted root CA");
- throw new IOException("no trusted root CA");
- }
-
- if (Config.LOGV) {
- Log.v(LOG_TAG,"validateSocket # certs = " +certs.length);
- }
-
- if (!hasValidCertificateChain(certs)) {
- if (Config.LOGD) {
- Log.d(LOG_TAG,"validateSocket(): certificate untrusted!");
+ return mInsecureFactory;
+ } else {
+ if (mSecureFactory == null) {
+ mSecureFactory = makeSocketFactory(null);
}
- throw new IOException("Certificate untrusted");
- }
-
- X509Certificate lastChainCert = (X509Certificate) certs[0];
-
- if (!DomainNameChecker.match(lastChainCert, destHost)) {
- if (Config.LOGD) {
- Log.d(LOG_TAG,"validateSocket(): domain name check failed");
- }
- throw new IOException("Domain Name check failed");
+ return mSecureFactory;
}
}
- public Socket createSocket(Socket socket, String s, int i, boolean flag)
- throws IOException
- {
- throw new IOException("Cannot validate certification without a hostname");
+ @Override
+ public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
}
- public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
- throws IOException
- {
- throw new IOException("Cannot validate certification without a hostname");
+ @Override
+ public Socket createSocket() throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
}
- public Socket createSocket(InetAddress inaddr, int i) throws IOException {
- throw new IOException("Cannot validate certification without a hostname");
+ @Override
+ public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort)
+ throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
+ addr, port, localAddr, localPort);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
}
- public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
- SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i, inaddr, j);
-
- if (mSocketReadTimeoutForSslHandshake >= 0) {
- sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
- }
-
- validateSocket(sslSock,s);
- sslSock.setSoTimeout(0);
-
- return sslSock;
+ @Override
+ public Socket createSocket(InetAddress addr, int port) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
}
- public Socket createSocket(String s, int i) throws IOException {
- SSLSocket sslSock = (SSLSocket) mFactory.createSocket(s, i);
-
- if (mSocketReadTimeoutForSslHandshake >= 0) {
- sslSock.setSoTimeout(mSocketReadTimeoutForSslHandshake);
- }
-
- validateSocket(sslSock,s);
- sslSock.setSoTimeout(0);
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
+ throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
+ host, port, localAddr, localPort);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
+ }
- return sslSock;
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
+ s.setHandshakeTimeout(mHandshakeTimeoutMillis);
+ return s;
}
+ @Override
public String[] getDefaultCipherSuites() {
- return mFactory.getSupportedCipherSuites();
+ return getDelegate().getSupportedCipherSuites();
}
+ @Override
public String[] getSupportedCipherSuites() {
- return mFactory.getSupportedCipherSuites();
+ return getDelegate().getSupportedCipherSuites();
}
}
-
-
diff --git a/core/java/android/net/SSLSessionCache.java b/core/java/android/net/SSLSessionCache.java
new file mode 100644
index 0000000..4cbeb94
--- /dev/null
+++ b/core/java/android/net/SSLSessionCache.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * File-based cache of established SSL sessions. When re-establishing a
+ * connection to the same server, using an SSL session cache can save some time,
+ * power, and bandwidth by skipping directly to an encrypted stream.
+ * This is a persistent cache which can span executions of the application.
+ *
+ * @see SSLCertificateSocketFactory
+ */
+public final class SSLSessionCache {
+ private static final String TAG = "SSLSessionCache";
+ /* package */ final SSLClientSessionCache mSessionCache;
+
+ /**
+ * Create a session cache using the specified directory.
+ * Individual session entries will be files within the directory.
+ * Multiple instances for the same directory share data internally.
+ *
+ * @param dir to store session files in (created if necessary)
+ * @throws IOException if the cache can't be opened
+ */
+ public SSLSessionCache(File dir) throws IOException {
+ mSessionCache = FileClientSessionCache.usingDirectory(dir);
+ }
+
+ /**
+ * Create a session cache at the default location for this app.
+ * Multiple instances share data internally.
+ *
+ * @param context for the application
+ */
+ public SSLSessionCache(Context context) {
+ File dir = context.getDir("sslcache", Context.MODE_PRIVATE);
+ SSLClientSessionCache cache = null;
+ try {
+ cache = FileClientSessionCache.usingDirectory(dir);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to create SSL session cache in " + dir, e);
+ }
+ mSessionCache = cache;
+ }
+}
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index 28134b2..f607ee9 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -79,7 +79,7 @@ public class SntpClient
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
- // set mode = 3 (client) and version = 3
+ // set mode = 3 (client) and version = 3
// mode is in low 3 bits of first byte
// version is in bits 3-5 of first byte
buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
@@ -90,7 +90,7 @@ public class SntpClient
writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
socket.send(request);
-
+
// read the response
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
@@ -103,13 +103,22 @@ public class SntpClient
long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
- long clockOffset = (receiveTime - originateTime) + (transmitTime - responseTime);
- if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
- if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");
-
- // save our results
- mNtpTime = requestTime + clockOffset;
- mNtpTimeReference = requestTicks;
+ // receiveTime = originateTime + transit + skew
+ // responseTime = transmitTime + transit - skew
+ // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
+ // = ((originateTime + transit + skew - originateTime) +
+ // (transmitTime - (transmitTime + transit - skew)))/2
+ // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
+ // = (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");
+
+ // save our results - use the times on this side of the network latency
+ // (response rather than request time)
+ mNtpTime = responseTime + clockOffset;
+ mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
if (Config.LOGD) Log.d(TAG, "request time failed: " + e);
diff --git a/core/java/android/net/ThrottleManager.java b/core/java/android/net/ThrottleManager.java
new file mode 100644
index 0000000..5fdac58
--- /dev/null
+++ b/core/java/android/net/ThrottleManager.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.os.Binder;
+import android.os.RemoteException;
+
+/**
+ * Class that handles throttling. It provides read/write numbers per interface
+ * and methods to apply throttled rates.
+ * {@hide}
+ */
+public class ThrottleManager
+{
+ /**
+ * Broadcast each polling period to indicate new data counts.
+ *
+ * Includes four extras:
+ * EXTRA_CYCLE_READ - a long of the read bytecount for the current cycle
+ * EXTRA_CYCLE_WRITE -a long of the write bytecount for the current cycle
+ * EXTRA_CYLCE_START -a long of MS for the cycle start time
+ * EXTRA_CYCLE_END -a long of MS for the cycle stop time
+ * {@hide}
+ */
+ public static final String THROTTLE_POLL_ACTION = "android.net.thrott.POLL_ACTION";
+ /**
+ * The lookup key for a long for the read bytecount for this period. Retrieve with
+ * {@link android.content.Intent#getLongExtra(String)}.
+ * {@hide}
+ */
+ public static final String EXTRA_CYCLE_READ = "cycleRead";
+ /**
+ * contains a long of the number of bytes written in the cycle
+ * {@hide}
+ */
+ public static final String EXTRA_CYCLE_WRITE = "cycleWrite";
+ /**
+ * contains a long of the number of bytes read in the cycle
+ * {@hide}
+ */
+ public static final String EXTRA_CYCLE_START = "cycleStart";
+ /**
+ * contains a long of the ms since 1970 used to init a calendar, etc for the end
+ * of the cycle
+ * {@hide}
+ */
+ public static final String EXTRA_CYCLE_END = "cycleEnd";
+
+ /**
+ * Broadcast when the thottle level changes.
+ * {@hide}
+ */
+ public static final String THROTTLE_ACTION = "android.net.thrott.THROTTLE_ACTION";
+ /**
+ * int of the current bandwidth in TODO
+ * {@hide}
+ */
+ public static final String EXTRA_THROTTLE_LEVEL = "level";
+
+ /**
+ * Broadcast on boot and whenever the settings change.
+ * {@hide}
+ */
+ public static final String POLICY_CHANGED_ACTION = "android.net.thrott.POLICY_CHANGED_ACTION";
+
+ // {@hide}
+ public static final int DIRECTION_TX = 0;
+ // {@hide}
+ public static final int DIRECTION_RX = 1;
+
+ // {@hide}
+ public static final int PERIOD_CYCLE = 0;
+ // {@hide}
+ public static final int PERIOD_YEAR = 1;
+ // {@hide}
+ public static final int PERIOD_MONTH = 2;
+ // {@hide}
+ public static final int PERIOD_WEEK = 3;
+ // @hide
+ public static final int PERIOD_7DAY = 4;
+ // @hide
+ public static final int PERIOD_DAY = 5;
+ // @hide
+ public static final int PERIOD_24HOUR = 6;
+ // @hide
+ public static final int PERIOD_HOUR = 7;
+ // @hide
+ public static final int PERIOD_60MIN = 8;
+ // @hide
+ public static final int PERIOD_MINUTE = 9;
+ // @hide
+ public static final int PERIOD_60SEC = 10;
+ // @hide
+ public static final int PERIOD_SECOND = 11;
+
+
+
+ /**
+ * returns a long of the ms from the epoch to the time the current cycle ends for the
+ * named interface
+ * {@hide}
+ */
+ public long getResetTime(String iface) {
+ try {
+ return mService.getResetTime(iface);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * returns a long of the ms from the epoch to the time the current cycle started for the
+ * named interface
+ * {@hide}
+ */
+ public long getPeriodStartTime(String iface) {
+ try {
+ return mService.getPeriodStartTime(iface);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * returns a long of the byte count either read or written on the named interface
+ * for the period described. Direction is either DIRECTION_RX or DIRECTION_TX and
+ * period may only be PERIOD_CYCLE for the current cycle (other periods may be supported
+ * in the future). Ago indicates the number of periods in the past to lookup - 0 means
+ * the current period, 1 is the last one, 2 was two periods ago..
+ * {@hide}
+ */
+ public long getByteCount(String iface, int direction, int period, int ago) {
+ try {
+ return mService.getByteCount(iface, direction, period, ago);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * returns the number of bytes read+written after which a particular cliff
+ * takes effect on the named iface. Currently only cliff #1 is supported (1 step)
+ * {@hide}
+ */
+ public long getCliffThreshold(String iface, int cliff) {
+ try {
+ return mService.getCliffThreshold(iface, cliff);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * returns the thottling bandwidth (bps) for a given cliff # on the named iface.
+ * only cliff #1 is currently supported.
+ * {@hide}
+ */
+ public int getCliffLevel(String iface, int cliff) {
+ try {
+ return mService.getCliffLevel(iface, cliff);
+ } catch (RemoteException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * returns the help URI for throttling
+ * {@hide}
+ */
+ public String getHelpUri() {
+ try {
+ return mService.getHelpUri();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+
+ private IThrottleManager mService;
+
+ /**
+ * Don't allow use of default constructor.
+ */
+ @SuppressWarnings({"UnusedDeclaration"})
+ private ThrottleManager() {
+ }
+
+ /**
+ * {@hide}
+ */
+ public ThrottleManager(IThrottleManager service) {
+ if (service == null) {
+ throw new IllegalArgumentException(
+ "ThrottleManager() cannot be constructed with null service");
+ }
+ mService = service;
+ }
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
new file mode 100644
index 0000000..0d64dab
--- /dev/null
+++ b/core/java/android/net/TrafficStats.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.io.IOException;
+
+/**
+ * Class that provides network traffic statistics. These statistics include
+ * bytes transmitted and received and network packets transmitted and received,
+ * over all interfaces, over the mobile interface, and on a per-UID basis.
+ * <p>
+ * These statistics may not be available on all platforms. If the statistics
+ * are not supported by this device, {@link #UNSUPPORTED} will be returned.
+ */
+public class TrafficStats {
+ /**
+ * The return value to indicate that the device does not support the statistic.
+ */
+ public final static int UNSUPPORTED = -1;
+
+ /**
+ * Get the total number of packets transmitted through the mobile interface.
+ *
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getMobileTxPackets();
+
+ /**
+ * Get the total number of packets received through the mobile interface.
+ *
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getMobileRxPackets();
+
+ /**
+ * Get the total number of bytes transmitted through the mobile interface.
+ *
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getMobileTxBytes();
+
+ /**
+ * Get the total number of bytes received through the mobile interface.
+ *
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getMobileRxBytes();
+
+ /**
+ * Get the total number of packets sent through all network interfaces.
+ *
+ * @return the number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getTotalTxPackets();
+
+ /**
+ * Get the total number of packets received through all network interfaces.
+ *
+ * @return number of packets. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getTotalRxPackets();
+
+ /**
+ * Get the total number of bytes sent through all network interfaces.
+ *
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getTotalTxBytes();
+
+ /**
+ * Get the total number of bytes received through all network interfaces.
+ *
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getTotalRxBytes();
+
+ /**
+ * Get the number of bytes sent through the network for this UID.
+ * The statistics are across all interfaces.
+ *
+ * {@see android.os.Process#myUid()}.
+ *
+ * @param uid The UID of the process to examine.
+ * @return number of bytes. If the statistics are not supported by this device,
+ * {@link #UNSUPPORTED} will be returned.
+ */
+ public static native long getUidTxBytes(int uid);
+
+ /**
+ * Get the number of bytes received through the network for this UID.
+ * The statistics are across all interfaces.
+ *
+ * {@see android.os.Process#myUid()}.
+ *
+ * @param uid The UID of the process to examine.
+ * @return number of bytes
+ */
+ public static native long getUidRxBytes(int uid);
+}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 9a1b65d..47faaba 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1567,51 +1567,45 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
if (isOpaque()) {
throw new UnsupportedOperationException(NOT_HIERARCHICAL);
}
+ if (key == null) {
+ throw new NullPointerException("key");
+ }
- String query = getEncodedQuery();
-
+ final String query = getEncodedQuery();
if (query == null) {
return null;
}
- String encodedKey;
- try {
- encodedKey = URLEncoder.encode(key, DEFAULT_ENCODING);
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError(e);
- }
-
- String prefix = encodedKey + "=";
+ final String encodedKey = encode(key, null);
+ final int encodedKeyLength = encodedKey.length();
- if (query.length() < prefix.length()) {
- return null;
- }
-
- int start;
- if (query.startsWith(prefix)) {
- // It's the first parameter.
- start = prefix.length();
- } else {
- // It must be later in the query string.
- prefix = "&" + prefix;
- start = query.indexOf(prefix);
+ int encodedKeySearchIndex = 0;
+ final int encodedKeySearchEnd = query.length() - (encodedKeyLength + 1);
- if (start == -1) {
- // Not found.
- return null;
+ while (encodedKeySearchIndex <= encodedKeySearchEnd) {
+ int keyIndex = query.indexOf(encodedKey, encodedKeySearchIndex);
+ if (keyIndex == -1) {
+ break;
+ }
+ final int equalsIndex = keyIndex + encodedKeyLength;
+ if (equalsIndex >= query.length()) {
+ break;
+ }
+ if (query.charAt(equalsIndex) != '=') {
+ encodedKeySearchIndex = equalsIndex + 1;
+ continue;
+ }
+ if (keyIndex == 0 || query.charAt(keyIndex - 1) == '&') {
+ int end = query.indexOf('&', equalsIndex);
+ if (end == -1) {
+ end = query.length();
+ }
+ return decode(query.substring(equalsIndex + 1, end));
+ } else {
+ encodedKeySearchIndex = equalsIndex + 1;
}
-
- start += prefix.length();
- }
-
- // Find end of value.
- int end = query.indexOf('&', start);
- if (end == -1) {
- end = query.length();
}
-
- String value = query.substring(start, end);
- return decode(value);
+ return null;
}
/** Identifies a null parcelled Uri. */
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index f4ae66a..4101ab4 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.util.Patterns.GOOD_IRI_CHAR;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,11 +54,12 @@ public class WebAddress {
static final int MATCH_GROUP_PATH = 5;
static Pattern sAddressPattern = Pattern.compile(
- /* scheme */ "(?:(http|HTTP|https|HTTPS|file|FILE)\\:\\/\\/)?" +
+ /* scheme */ "(?:(http|https|file)\\:\\/\\/)?" +
/* authority */ "(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" +
- /* host */ "([-A-Za-z0-9%_]+(?:\\.[-A-Za-z0-9%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
- /* port */ "(?:\\:([0-9]+))?" +
- /* path */ "(\\/?.*)?");
+ /* host */ "([-" + GOOD_IRI_CHAR + "%_]+(?:\\.[-" + GOOD_IRI_CHAR + "%_]+)*|\\[[0-9a-fA-F:\\.]+\\])?" +
+ /* port */ "(?:\\:([0-9]*))?" +
+ /* path */ "(\\/?[^#]*)?" +
+ /* anchor */ ".*", Pattern.CASE_INSENSITIVE);
/** parses given uriString. */
public WebAddress(String address) throws ParseException {
@@ -76,13 +79,14 @@ public class WebAddress {
String t;
if (m.matches()) {
t = m.group(MATCH_GROUP_SCHEME);
- if (t != null) mScheme = t;
+ if (t != null) mScheme = t.toLowerCase();
t = m.group(MATCH_GROUP_AUTHORITY);
if (t != null) mAuthInfo = t;
t = m.group(MATCH_GROUP_HOST);
if (t != null) mHost = t;
t = m.group(MATCH_GROUP_PORT);
- if (t != null) {
+ if (t != null && t.length() > 0) {
+ // The ':' character is not returned by the regex.
try {
mPort = Integer.parseInt(t);
} catch (NumberFormatException ex) {
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index c2013d5..e07ee59 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -16,6 +16,7 @@
package android.net.http;
+import com.android.internal.http.HttpDateTime;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -36,7 +37,6 @@ import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
@@ -47,8 +47,6 @@ import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.BasicHttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import java.io.IOException;
import java.io.InputStream;
@@ -57,13 +55,13 @@ import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.net.URI;
-import java.security.KeyManagementException;
-import android.util.Log;
+import android.content.Context;
import android.content.ContentResolver;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.os.SystemProperties;
+import android.net.SSLCertificateSocketFactory;
+import android.net.SSLSessionCache;
+import android.os.Looper;
+import android.util.Log;
/**
* Subclass of the Apache {@link DefaultHttpClient} that is configured with
@@ -75,26 +73,21 @@ import android.os.SystemProperties;
* To retain cookies, simply add a cookie store to the HttpContext:</p>
*
* <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
- *
- * {@hide}
*/
public final class AndroidHttpClient implements HttpClient {
-
+
// Gzip of data shorter than this probably won't be worthwhile
public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
private static final String TAG = "AndroidHttpClient";
- /** Set if HTTP requests are blocked from being executed on this thread */
- private static final ThreadLocal<Boolean> sThreadBlocked =
- new ThreadLocal<Boolean>();
-
/** Interceptor throws an exception if the executing thread is blocked */
private static final HttpRequestInterceptor sThreadCheckInterceptor =
new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) {
- if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
+ // Prevent the HttpRequest from being sent on the main thread
+ if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) {
throw new RuntimeException("This thread forbids HTTP requests");
}
}
@@ -103,12 +96,11 @@ public final class AndroidHttpClient implements HttpClient {
/**
* Create a new HttpClient with reasonable defaults (which you can update).
*
- * @param userAgent to report in your HTTP requests.
- * @param sessionCache persistent session cache
+ * @param userAgent to report in your HTTP requests
+ * @param context to use for caching SSL sessions (may be null for no caching)
* @return AndroidHttpClient for you to use for all your requests.
*/
- public static AndroidHttpClient newInstance(String userAgent,
- SSLClientSessionCache sessionCache) {
+ public static AndroidHttpClient newInstance(String userAgent, Context context) {
HttpParams params = new BasicHttpParams();
// Turn off stale checking. Our connections break all the time anyway,
@@ -124,13 +116,16 @@ public final class AndroidHttpClient implements HttpClient {
// often wants to re-POST after a redirect, which we must do ourselves.
HttpClientParams.setRedirecting(params, false);
+ // Use a session cache for SSL sockets
+ SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context);
+
// Set the specified user agent and register standard protocols.
HttpProtocolParams.setUserAgent(params, userAgent);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https",
- socketFactoryWithCache(sessionCache), 443));
+ SSLCertificateSocketFactory.getHttpSocketFactory(30 * 1000, sessionCache), 443));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
@@ -141,32 +136,6 @@ public final class AndroidHttpClient implements HttpClient {
}
/**
- * Returns a socket factory backed by the given persistent session cache.
- *
- * @param sessionCache to retrieve sessions from, null for no cache
- */
- private static SSLSocketFactory socketFactoryWithCache(
- SSLClientSessionCache sessionCache) {
- if (sessionCache == null) {
- // Use the default factory which doesn't support persistent
- // caching.
- return SSLSocketFactory.getSocketFactory();
- }
-
- // Create a new SSL context backed by the cache.
- // TODO: Keep a weak *identity* hash map of caches to engines. In the
- // mean time, if we have two engines for the same cache, they'll still
- // share sessions but will have to do so through the persistent cache.
- SSLContextImpl sslContext = new SSLContextImpl();
- try {
- sslContext.engineInit(null, null, null, sessionCache, null);
- } catch (KeyManagementException e) {
- throw new AssertionError(e);
- }
- return new SSLSocketFactory(sslContext.engineGetSocketFactory());
- }
-
- /**
* Create a new HttpClient with reasonable defaults (which you can update).
* @param userAgent to report in your HTTP requests.
* @return AndroidHttpClient for you to use for all your requests.
@@ -221,15 +190,6 @@ public final class AndroidHttpClient implements HttpClient {
}
/**
- * Block this thread from executing HTTP requests.
- * Used to guard against HTTP requests blocking the main application thread.
- * @param blocked if HTTP requests run on this thread should be denied
- */
- public static void setThreadBlocked(boolean blocked) {
- sThreadBlocked.set(blocked);
- }
-
- /**
* Modifies a request to indicate to the server that we would like a
* gzipped response. (Uses the "Accept-Encoding" HTTP header.)
* @param request the request to modify
@@ -350,19 +310,7 @@ public final class AndroidHttpClient implements HttpClient {
* Shorter data will not be compressed.
*/
public static long getMinGzipSize(ContentResolver resolver) {
- String sMinGzipBytes = Settings.Gservices.getString(resolver,
- Settings.Gservices.SYNC_MIN_GZIP_BYTES);
-
- if (!TextUtils.isEmpty(sMinGzipBytes)) {
- try {
- return Long.parseLong(sMinGzipBytes);
- } catch (NumberFormatException nfe) {
- Log.w(TAG, "Unable to parse " +
- Settings.Gservices.SYNC_MIN_GZIP_BYTES + " " +
- sMinGzipBytes, nfe);
- }
- }
- return DEFAULT_SYNC_MIN_GZIP_BYTES;
+ return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant.
}
/* cURL logging support. */
@@ -388,15 +336,6 @@ public final class AndroidHttpClient implements HttpClient {
}
/**
- * Returns true if auth logging is turned on for this configuration. Can only be set on
- * insecure devices.
- */
- private boolean isAuthLoggable() {
- String secure = SystemProperties.get("ro.secure");
- return "0".equals(secure) && Log.isLoggable(tag + "-auth", level);
- }
-
- /**
* Prints a message using this configuration.
*/
private void println(String message) {
@@ -442,8 +381,9 @@ public final class AndroidHttpClient implements HttpClient {
if (configuration != null
&& configuration.isLoggable()
&& request instanceof HttpUriRequest) {
- configuration.println(toCurl((HttpUriRequest) request,
- configuration.isAuthLoggable()));
+ // Never print auth token -- we used to check ro.secure=0 to
+ // enable that, but can't do that in unbundled code.
+ configuration.println(toCurl((HttpUriRequest) request, false));
}
}
}
@@ -505,4 +445,22 @@ public final class AndroidHttpClient implements HttpClient {
return builder.toString();
}
+
+ /**
+ * Returns the date of the given HTTP date string. This method can identify
+ * and parse the date formats emitted by common HTTP servers, such as
+ * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>,
+ * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>,
+ * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and
+ * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI
+ * C's asctime()</a>.
+ *
+ * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
+ * @throws IllegalArgumentException if {@code dateString} is not a date or
+ * of an unsupported format.
+ */
+ public static long parseDate(String dateString) {
+ return HttpDateTime.parse(dateString);
+ }
}
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index ed6b4c2..c527fe4 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -16,6 +16,9 @@
package android.net.http;
+
+import com.android.internal.net.DomainNameValidator;
+
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
import java.io.IOException;
@@ -27,6 +30,7 @@ import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
+import java.util.Date;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
@@ -49,20 +53,24 @@ class CertificateChainValidator {
= new CertificateChainValidator();
/**
- * @return The singleton instance of the certificator chain validator
+ * @return The singleton instance of the certificates chain validator
*/
public static CertificateChainValidator getInstance() {
return sInstance;
}
/**
- * Creates a new certificate chain validator. This is a pivate constructor.
+ * Creates a new certificate chain validator. This is a private constructor.
* If you need a Certificate chain validator, call getInstance().
*/
private CertificateChainValidator() {}
/**
* Performs the handshake and server certificates validation
+ * Notice a new chain will be rebuilt by tracing the issuer and subject
+ * before calling checkServerTrusted().
+ * And if the last traced certificate is self issued and it is expired, it
+ * will be dropped.
* @param sslSocket The secure connection socket
* @param domain The website domain
* @return An SSL error object if there is an error and null otherwise
@@ -112,7 +120,7 @@ class CertificateChainValidator {
closeSocketThrowException(
sslSocket, "certificate for this site is null");
} else {
- if (!DomainNameChecker.match(currCertificate, domain)) {
+ if (!DomainNameValidator.match(currCertificate, domain)) {
String errorMessage = "certificate not for this host: " + domain;
if (HttpLog.LOGV) {
@@ -125,7 +133,58 @@ class CertificateChainValidator {
}
}
- // first, we validate the chain using the standard validation
+ // Clean up the certificates chain and build a new one.
+ // Theoretically, we shouldn't have to do this, but various web servers
+ // in practice are mis-configured to have out-of-order certificates or
+ // expired self-issued root certificate.
+ int chainLength = serverCertificates.length;
+ if (serverCertificates.length > 1) {
+ // 1. we clean the received certificates chain.
+ // We start from the end-entity certificate, tracing down by matching
+ // the "issuer" field and "subject" field until we can't continue.
+ // This helps when the certificates are out of order or
+ // some certificates are not related to the site.
+ int currIndex;
+ for (currIndex = 0; currIndex < serverCertificates.length; ++currIndex) {
+ boolean foundNext = false;
+ for (int nextIndex = currIndex + 1;
+ nextIndex < serverCertificates.length;
+ ++nextIndex) {
+ if (serverCertificates[currIndex].getIssuerDN().equals(
+ serverCertificates[nextIndex].getSubjectDN())) {
+ foundNext = true;
+ // Exchange certificates so that 0 through currIndex + 1 are in proper order
+ if (nextIndex != currIndex + 1) {
+ X509Certificate tempCertificate = serverCertificates[nextIndex];
+ serverCertificates[nextIndex] = serverCertificates[currIndex + 1];
+ serverCertificates[currIndex + 1] = tempCertificate;
+ }
+ break;
+ }
+ }
+ if (!foundNext) break;
+ }
+
+ // 2. we exam if the last traced certificate is self issued and it is expired.
+ // If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
+ // have a similar but unexpired trusted root.
+ chainLength = currIndex + 1;
+ X509Certificate lastCertificate = serverCertificates[chainLength - 1];
+ Date now = new Date();
+ if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
+ && now.after(lastCertificate.getNotAfter())) {
+ --chainLength;
+ }
+ }
+
+ // 3. Now we copy the newly built chain into an appropriately sized array.
+ X509Certificate[] newServerCertificates = null;
+ newServerCertificates = new X509Certificate[chainLength];
+ for (int i = 0; i < chainLength; ++i) {
+ newServerCertificates[i] = serverCertificates[i];
+ }
+
+ // first, we validate the new chain using the standard validation
// solution; if we do not find any errors, we are done; if we
// fail the standard validation, we re-validate again below,
// this time trying to retrieve any individual errors we can
@@ -133,167 +192,21 @@ class CertificateChainValidator {
//
try {
SSLParameters.getDefaultTrustManager().checkServerTrusted(
- serverCertificates, "RSA");
+ newServerCertificates, "RSA");
// no errors!!!
return null;
} catch (CertificateException e) {
+ sslSocket.getSession().invalidate();
+
if (HttpLog.LOGV) {
HttpLog.v(
"failed to pre-validate the certificate chain, error: " +
e.getMessage());
}
- }
-
- sslSocket.getSession().invalidate();
-
- SslError error = null;
-
- // we check the root certificate separately from the rest of the
- // chain; this is because we need to know what certificate in
- // the chain resulted in an error if any
- currCertificate =
- serverCertificates[serverCertificates.length - 1];
- if (currCertificate == null) {
- closeSocketThrowException(
- sslSocket, "root certificate is null");
- }
-
- // check if the last certificate in the chain (root) is trusted
- X509Certificate[] rootCertificateChain = { currCertificate };
- try {
- SSLParameters.getDefaultTrustManager().checkServerTrusted(
- rootCertificateChain, "RSA");
- } catch (CertificateExpiredException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "root certificate has expired";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- error = new SslError(
- SslError.SSL_EXPIRED, currCertificate);
- } catch (CertificateNotYetValidException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "root certificate not valid yet";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- error = new SslError(
- SslError.SSL_NOTYETVALID, currCertificate);
- } catch (CertificateException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "root certificate not trusted";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
return new SslError(
SslError.SSL_UNTRUSTED, currCertificate);
}
-
- // Then go through the certificate chain checking that each
- // certificate trusts the next and that each certificate is
- // within its valid date range. Walk the chain in the order
- // from the CA to the end-user
- X509Certificate prevCertificate =
- serverCertificates[serverCertificates.length - 1];
-
- for (int i = serverCertificates.length - 2; i >= 0; --i) {
- currCertificate = serverCertificates[i];
-
- // if a certificate is null, we cannot verify the chain
- if (currCertificate == null) {
- closeSocketThrowException(
- sslSocket, "null certificate in the chain");
- }
-
- // verify if trusted by chain
- if (!prevCertificate.getSubjectDN().equals(
- currCertificate.getIssuerDN())) {
- String errorMessage = "not trusted by chain";
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- return new SslError(
- SslError.SSL_UNTRUSTED, currCertificate);
- }
-
- try {
- currCertificate.verify(prevCertificate.getPublicKey());
- } catch (GeneralSecurityException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "not trusted by chain";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- return new SslError(
- SslError.SSL_UNTRUSTED, currCertificate);
- }
-
- // verify if the dates are valid
- try {
- currCertificate.checkValidity();
- } catch (CertificateExpiredException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "certificate expired";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- if (error == null ||
- error.getPrimaryError() < SslError.SSL_EXPIRED) {
- error = new SslError(
- SslError.SSL_EXPIRED, currCertificate);
- }
- } catch (CertificateNotYetValidException e) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "certificate not valid yet";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v(errorMessage);
- }
-
- if (error == null ||
- error.getPrimaryError() < SslError.SSL_NOTYETVALID) {
- error = new SslError(
- SslError.SSL_NOTYETVALID, currCertificate);
- }
- }
-
- prevCertificate = currCertificate;
- }
-
- // if we do not have an error to report back to the user, throw
- // an exception (a generic error will be reported instead)
- if (error == null) {
- closeSocketThrowException(
- sslSocket,
- "failed to pre-validate the certificate chain due to a non-standard error");
- }
-
- return error;
}
private void closeSocketThrowException(
diff --git a/core/java/android/net/http/Connection.java b/core/java/android/net/http/Connection.java
index 2d39e39..43fb5f1 100644
--- a/core/java/android/net/http/Connection.java
+++ b/core/java/android/net/http/Connection.java
@@ -94,7 +94,6 @@ abstract class Connection {
*/
private static final String HTTP_CONNECTION = "http.connection";
- RequestQueue.ConnectionManager mConnectionManager;
RequestFeeder mRequestFeeder;
/**
@@ -104,11 +103,9 @@ abstract class Connection {
private byte[] mBuf;
protected Connection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
mContext = context;
mHost = host;
- mConnectionManager = connectionManager;
mRequestFeeder = requestFeeder;
mCanPersist = false;
@@ -124,18 +121,15 @@ abstract class Connection {
* necessary
*/
static Connection getConnection(
- Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
if (host.getSchemeName().equals("http")) {
- return new HttpConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpConnection(context, host, requestFeeder);
}
// Otherwise, default to https
- return new HttpsConnection(context, host, connectionManager,
- requestFeeder);
+ return new HttpsConnection(context, host, proxy, requestFeeder);
}
/**
@@ -228,6 +222,12 @@ abstract class Connection {
}
}
+ /* we have a connection, let the event handler
+ * know of any associated certificate,
+ * potentially none.
+ */
+ req.mEventHandler.certificate(mCertificate);
+
try {
/* FIXME: don't increment failure count if old
connection? There should not be a penalty for
@@ -338,7 +338,7 @@ abstract class Connection {
mRequestFeeder.requeueRequest(tReq);
empty = false;
}
- if (empty) empty = mRequestFeeder.haveRequest(mHost);
+ if (empty) empty = !mRequestFeeder.haveRequest(mHost);
}
return empty;
}
diff --git a/core/java/android/net/http/ConnectionThread.java b/core/java/android/net/http/ConnectionThread.java
index 0b30e58..32191d2 100644
--- a/core/java/android/net/http/ConnectionThread.java
+++ b/core/java/android/net/http/ConnectionThread.java
@@ -108,24 +108,11 @@ class ConnectionThread extends Thread {
if (HttpLog.LOGV) HttpLog.v("ConnectionThread: new request " +
request.mHost + " " + request );
- HttpHost proxy = mConnectionManager.getProxyHost();
-
- HttpHost host;
- if (false) {
- // Allow https proxy
- host = proxy == null ? request.mHost : proxy;
- } else {
- // Disallow https proxy -- tmob proxy server
- // serves a request loop for https reqs
- host = (proxy == null ||
- request.mHost.getSchemeName().equals("https")) ?
- request.mHost : proxy;
- }
- mConnection = mConnectionManager.getConnection(mContext, host);
+ mConnection = mConnectionManager.getConnection(mContext,
+ request.mHost);
mConnection.processRequests(request);
if (mConnection.getCanPersist()) {
- if (!mConnectionManager.recycleConnection(host,
- mConnection)) {
+ if (!mConnectionManager.recycleConnection(mConnection)) {
mConnection.closeConnection();
}
} else {
diff --git a/core/java/android/net/http/EventHandler.java b/core/java/android/net/http/EventHandler.java
index a035c19..2aa05eb 100644
--- a/core/java/android/net/http/EventHandler.java
+++ b/core/java/android/net/http/EventHandler.java
@@ -125,8 +125,8 @@ public interface EventHandler {
public void endData();
/**
- * SSL certificate callback called every time a resource is
- * loaded via a secure connection
+ * SSL certificate callback called before resource request is
+ * made, which will be null for insecure connection.
*/
public void certificate(SslCertificate certificate);
diff --git a/core/java/android/net/http/Headers.java b/core/java/android/net/http/Headers.java
index b0923d1..09f6f4f 100644
--- a/core/java/android/net/http/Headers.java
+++ b/core/java/android/net/http/Headers.java
@@ -72,6 +72,7 @@ public final class Headers {
public final static String SET_COOKIE = "set-cookie";
public final static String PRAGMA = "pragma";
public final static String REFRESH = "refresh";
+ public final static String X_PERMITTED_CROSS_DOMAIN_POLICIES = "x-permitted-cross-domain-policies";
// following hash are generated by String.hashCode()
private final static int HASH_TRANSFER_ENCODING = 1274458357;
@@ -92,6 +93,7 @@ public final class Headers {
private final static int HASH_SET_COOKIE = 1237214767;
private final static int HASH_PRAGMA = -980228804;
private final static int HASH_REFRESH = 1085444827;
+ private final static int HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES = -1345594014;
// keep any headers that require direct access in a presized
// string array
@@ -113,8 +115,9 @@ public final class Headers {
private final static int IDX_SET_COOKIE = 15;
private final static int IDX_PRAGMA = 16;
private final static int IDX_REFRESH = 17;
+ private final static int IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES = 18;
- private final static int HEADER_COUNT = 18;
+ private final static int HEADER_COUNT = 19;
/* parsed values */
private long transferEncoding;
@@ -141,7 +144,8 @@ public final class Headers {
ETAG,
SET_COOKIE,
PRAGMA,
- REFRESH
+ REFRESH,
+ X_PERMITTED_CROSS_DOMAIN_POLICIES
};
// Catch-all for headers not explicitly handled
@@ -287,6 +291,11 @@ public final class Headers {
mHeaders[IDX_REFRESH] = val;
}
break;
+ case HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES:
+ if (name.equals(X_PERMITTED_CROSS_DOMAIN_POLICIES)) {
+ mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = val;
+ }
+ break;
default:
mExtraHeaderNames.add(name);
mExtraHeaderValues.add(val);
@@ -361,6 +370,10 @@ public final class Headers {
return mHeaders[IDX_REFRESH];
}
+ public String getXPermittedCrossDomainPolicies() {
+ return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
+ }
+
public void setContentLength(long value) {
this.contentLength = value;
}
@@ -409,6 +422,10 @@ public final class Headers {
mHeaders[IDX_ETAG] = value;
}
+ public void setXPermittedCrossDomainPolicies(String value) {
+ mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
+ }
+
public interface HeaderCallback {
public void header(String name, String value);
}
diff --git a/core/java/android/net/http/HttpConnection.java b/core/java/android/net/http/HttpConnection.java
index 8b12d0b..6df86bf 100644
--- a/core/java/android/net/http/HttpConnection.java
+++ b/core/java/android/net/http/HttpConnection.java
@@ -35,9 +35,8 @@ import org.apache.http.params.HttpConnectionParams;
class HttpConnection extends Connection {
HttpConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
}
/**
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index 8a69d0d..e512a1df 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -59,7 +59,7 @@ public class HttpsConnection extends Connection {
private static SSLSocketFactory mSslSocketFactory = null;
static {
- // This intiialization happens in the zygote. It triggers some
+ // This initialization happens in the zygote. It triggers some
// lazy initialization that can will benefit later invocations of
// initializeEngine().
initializeEngine(null);
@@ -131,13 +131,16 @@ public class HttpsConnection extends Connection {
*/
private boolean mAborted = false;
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
/**
* Contructor for a https connection.
*/
- HttpsConnection(Context context, HttpHost host,
- RequestQueue.ConnectionManager connectionManager,
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
RequestFeeder requestFeeder) {
- super(context, host, connectionManager, requestFeeder);
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
}
/**
@@ -159,8 +162,7 @@ public class HttpsConnection extends Connection {
AndroidHttpClientConnection openConnection(Request req) throws IOException {
SSLSocket sslSock = null;
- HttpHost proxyHost = mConnectionManager.getProxyHost();
- if (proxyHost != null) {
+ if (mProxyHost != null) {
// If we have a proxy set, we first send a CONNECT request
// to the proxy; if the proxy returns 200 OK, we negotiate
// a secure connection to the target server via the proxy.
@@ -172,7 +174,7 @@ public class HttpsConnection extends Connection {
Socket proxySock = null;
try {
proxySock = new Socket
- (proxyHost.getHostName(), proxyHost.getPort());
+ (mProxyHost.getHostName(), mProxyHost.getPort());
proxySock.setSoTimeout(60 * 1000);
@@ -306,12 +308,6 @@ public class HttpsConnection extends Connection {
SslError error = CertificateChainValidator.getInstance().
doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
- EventHandler eventHandler = req.getEventHandler();
-
- // Update the certificate info (to be consistent, it is better to do it
- // here, before we start handling SSL errors, if any)
- eventHandler.certificate(mCertificate);
-
// Inform the user if there is a problem
if (error != null) {
// handleSslErrorRequest may immediately unsuspend if it wants to
@@ -323,7 +319,7 @@ public class HttpsConnection extends Connection {
mSuspended = true;
}
// don't hold the lock while calling out to the event handler
- boolean canHandle = eventHandler.handleSslErrorRequest(error);
+ boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
if(!canHandle) {
throw new IOException("failed to handle "+ error);
}
diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java
index 1b6568e..8c0d503 100644
--- a/core/java/android/net/http/Request.java
+++ b/core/java/android/net/http/Request.java
@@ -34,6 +34,7 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
import org.apache.http.ParseException;
import org.apache.http.ProtocolVersion;
@@ -72,6 +73,10 @@ class Request {
int mFailCount = 0;
+ // This will be used to set the Range field if we retry a connection. This
+ // is http/1.1 feature.
+ private int mReceivedBytes = 0;
+
private InputStream mBodyProvider;
private int mBodyLength;
@@ -82,6 +87,9 @@ class Request {
/* Used to synchronize waitUntilComplete() requests */
private final Object mClientResource = new Object();
+ /** True if loading should be paused **/
+ private boolean mLoadingPaused = false;
+
/**
* Processor used to set content-length and transfer-encoding
* headers.
@@ -133,6 +141,18 @@ class Request {
}
/**
+ * @param pause True if the load should be paused.
+ */
+ synchronized void setLoadingPaused(boolean pause) {
+ mLoadingPaused = pause;
+
+ // Wake up the paused thread if we're unpausing the load.
+ if (!mLoadingPaused) {
+ notify();
+ }
+ }
+
+ /**
* @param connection Request served by this connection
*/
void setConnection(Connection connection) {
@@ -226,7 +246,6 @@ class Request {
StatusLine statusLine = null;
boolean hasBody = false;
- boolean reuse = false;
httpClientConnection.flush();
int statusCode = 0;
@@ -249,6 +268,11 @@ class Request {
if (hasBody)
entity = httpClientConnection.receiveResponseEntity(header);
+ // restrict the range request to the servers claiming that they are
+ // accepting ranges in bytes
+ boolean supportPartialContent = "bytes".equalsIgnoreCase(header
+ .getAcceptRanges());
+
if (entity != null) {
InputStream is = entity.getContent();
@@ -271,9 +295,27 @@ class Request {
int len = 0;
int lowWater = buf.length / 2;
while (len != -1) {
+ synchronized(this) {
+ while (mLoadingPaused) {
+ // Put this (network loading) thread to sleep if WebCore
+ // has asked us to. This can happen with plugins for
+ // example, if we are streaming data but the plugin has
+ // filled its internal buffers.
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ HttpLog.e("Interrupted exception whilst "
+ + "network thread paused at WebCore's request."
+ + " " + e.getMessage());
+ }
+ }
+ }
+
len = nis.read(buf, count, buf.length - count);
+
if (len != -1) {
count += len;
+ if (supportPartialContent) mReceivedBytes += len;
}
if (len == -1 || count >= lowWater) {
if (HttpLog.LOGV) HttpLog.v("Request.readResponse() " + count);
@@ -292,7 +334,13 @@ class Request {
if (HttpLog.LOGV) HttpLog.v( "readResponse() handling " + e);
} catch(IOException e) {
// don't throw if we have a non-OK status code
- if (statusCode == HttpStatus.SC_OK) {
+ if (statusCode == HttpStatus.SC_OK
+ || statusCode == HttpStatus.SC_PARTIAL_CONTENT) {
+ if (supportPartialContent && count > 0) {
+ // if there is uncommited content, we should commit them
+ // as we will continue the request
+ mEventHandler.data(buf, count);
+ }
throw e;
}
} finally {
@@ -316,10 +364,16 @@ class Request {
*
* Called by RequestHandle from non-network thread
*/
- void cancel() {
+ synchronized void cancel() {
if (HttpLog.LOGV) {
HttpLog.v("Request.cancel(): " + getUri());
}
+
+ // Ensure that the network thread is not blocked by a hanging request from WebCore to
+ // pause the load.
+ mLoadingPaused = false;
+ notify();
+
mCancelled = true;
if (mConnection != null) {
mConnection.cancel();
@@ -373,6 +427,15 @@ class Request {
}
setBodyProvider(mBodyProvider, mBodyLength);
}
+
+ if (mReceivedBytes > 0) {
+ // reset the fail count as we continue the request
+ mFailCount = 0;
+ // set the "Range" header to indicate that the retry will continue
+ // instead of restarting the request
+ HttpLog.v("*** Request.reset() to range:" + mReceivedBytes);
+ mHttpRequest.setHeader("Range", "bytes=" + mReceivedBytes + "-");
+ }
}
/**
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index 190ae7a..103fd94 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -42,15 +42,13 @@ public class RequestHandle {
private WebAddress mUri;
private String mMethod;
private Map<String, String> mHeaders;
-
private RequestQueue mRequestQueue;
-
private Request mRequest;
-
private InputStream mBodyProvider;
private int mBodyLength;
-
private int mRedirectCount = 0;
+ // Used only with synchronous requests.
+ private Connection mConnection;
private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String PROXY_AUTHORIZATION_HEADER = "Proxy-Authorization";
@@ -81,6 +79,19 @@ public class RequestHandle {
}
/**
+ * Creates a new request session with a given Connection. This connection
+ * is used during a synchronous load to handle this request.
+ */
+ public RequestHandle(RequestQueue requestQueue, String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ InputStream bodyProvider, int bodyLength, Request request,
+ Connection conn) {
+ this(requestQueue, url, uri, method, headers, bodyProvider, bodyLength,
+ request);
+ mConnection = conn;
+ }
+
+ /**
* Cancels this request
*/
public void cancel() {
@@ -90,6 +101,16 @@ public class RequestHandle {
}
/**
+ * Pauses the loading of this request. For example, called from the WebCore thread
+ * when the plugin can take no more data.
+ */
+ public void pauseRequest(boolean pause) {
+ if (mRequest != null) {
+ mRequest.setLoadingPaused(pause);
+ }
+ }
+
+ /**
* Handles SSL error(s) on the way down from the user (the user
* has already provided their feedback).
*/
@@ -179,7 +200,7 @@ public class RequestHandle {
if (mBodyProvider != null) mBodyProvider.reset();
} catch (java.io.IOException ex) {
if (HttpLog.LOGV) {
- HttpLog.v("setupAuthResponse() failed to reset body provider");
+ HttpLog.v("setupRedirect() failed to reset body provider");
}
return false;
}
@@ -262,6 +283,12 @@ public class RequestHandle {
mRequest.waitUntilComplete();
}
+ public void processRequest() {
+ if (mConnection != null) {
+ mConnection.processRequests(mRequest);
+ }
+ }
+
/**
* @return Digest-scheme authentication response.
*/
@@ -416,6 +443,16 @@ public class RequestHandle {
* Creates and queues new request.
*/
private void createAndQueueNewRequest() {
+ // mConnection is non-null if and only if the requests are synchronous.
+ if (mConnection != null) {
+ RequestHandle newHandle = mRequestQueue.queueSynchronousRequest(
+ mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
+ mBodyProvider, mBodyLength);
+ mRequest = newHandle.mRequest;
+ mConnection = newHandle.mConnection;
+ newHandle.processRequest();
+ return;
+ }
mRequest = mRequestQueue.queueRequest(
mUrl, mUri, mMethod, mHeaders, mRequest.mEventHandler,
mBodyProvider,
diff --git a/core/java/android/net/http/RequestQueue.java b/core/java/android/net/http/RequestQueue.java
index 875caa0..a31639f 100644
--- a/core/java/android/net/http/RequestQueue.java
+++ b/core/java/android/net/http/RequestQueue.java
@@ -171,16 +171,17 @@ public class RequestQueue implements RequestFeeder {
}
public Connection getConnection(Context context, HttpHost host) {
+ host = RequestQueue.this.determineHost(host);
Connection con = mIdleCache.getConnection(host);
if (con == null) {
mTotalConnection++;
- con = Connection.getConnection(
- mContext, host, this, RequestQueue.this);
+ con = Connection.getConnection(mContext, host, mProxyHost,
+ RequestQueue.this);
}
return con;
}
- public boolean recycleConnection(HttpHost host, Connection connection) {
- return mIdleCache.cacheConnection(host, connection);
+ public boolean recycleConnection(Connection connection) {
+ return mIdleCache.cacheConnection(connection.getHost(), connection);
}
}
@@ -237,6 +238,8 @@ public class RequestQueue implements RequestFeeder {
mContext.registerReceiver(mProxyChangeReceiver,
new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
}
+ // we need to resample the current proxy setup
+ setProxyConfig();
}
/**
@@ -342,6 +345,66 @@ public class RequestQueue implements RequestFeeder {
req);
}
+ private static class SyncFeeder implements RequestFeeder {
+ // This is used in the case where the request fails and needs to be
+ // requeued into the RequestFeeder.
+ private Request mRequest;
+ SyncFeeder() {
+ }
+ public Request getRequest() {
+ Request r = mRequest;
+ mRequest = null;
+ return r;
+ }
+ public Request getRequest(HttpHost host) {
+ return getRequest();
+ }
+ public boolean haveRequest(HttpHost host) {
+ return mRequest != null;
+ }
+ public void requeueRequest(Request r) {
+ mRequest = r;
+ }
+ }
+
+ public RequestHandle queueSynchronousRequest(String url, WebAddress uri,
+ String method, Map<String, String> headers,
+ EventHandler eventHandler, InputStream bodyProvider,
+ int bodyLength) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("RequestQueue.dispatchSynchronousRequest " + uri);
+ }
+
+ HttpHost host = new HttpHost(uri.mHost, uri.mPort, uri.mScheme);
+
+ Request req = new Request(method, host, mProxyHost, uri.mPath,
+ bodyProvider, bodyLength, eventHandler, headers);
+
+ // Open a new connection that uses our special RequestFeeder
+ // implementation.
+ host = determineHost(host);
+ Connection conn = Connection.getConnection(mContext, host, mProxyHost,
+ new SyncFeeder());
+
+ // TODO: I would like to process the request here but LoadListener
+ // needs a RequestHandle to process some messages.
+ return new RequestHandle(this, url, uri, method, headers, bodyProvider,
+ bodyLength, req, conn);
+
+ }
+
+ // Chooses between the proxy and the request's host.
+ private HttpHost determineHost(HttpHost host) {
+ // There used to be a comment in ConnectionThread about t-mob's proxy
+ // being really bad about https. But, HttpsConnection actually looks
+ // for a proxy and connects through it anyway. I think that this check
+ // is still valid because if a site is https, we will use
+ // HttpsConnection rather than HttpConnection if the proxy address is
+ // not secure.
+ return (mProxyHost == null || "https".equals(host.getSchemeName()))
+ ? host : mProxyHost;
+ }
+
/**
* @return true iff there are any non-active requests pending
*/
@@ -478,6 +541,6 @@ public class RequestQueue implements RequestFeeder {
interface ConnectionManager {
HttpHost getProxyHost();
Connection getConnection(Context context, HttpHost host);
- boolean recycleConnection(HttpHost host, Connection connection);
+ boolean recycleConnection(Connection connection);
}
}
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 46b2bee..c29926c 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -18,7 +18,9 @@ package android.net.http;
import android.os.Bundle;
-import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.Vector;
import java.security.cert.X509Certificate;
@@ -32,6 +34,11 @@ import org.bouncycastle.asn1.x509.X509Name;
public class SslCertificate {
/**
+ * SimpleDateFormat pattern for an ISO 8601 date
+ */
+ private static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
+
+ /**
* Name of the entity this certificate is issued to
*/
private DName mIssuedTo;
@@ -44,12 +51,12 @@ public class SslCertificate {
/**
* Not-before date from the validity period
*/
- private String mValidNotBefore;
+ private Date mValidNotBefore;
/**
* Not-after date from the validity period
*/
- private String mValidNotAfter;
+ private Date mValidNotAfter;
/**
* Bundle key names
@@ -101,16 +108,28 @@ public class SslCertificate {
* Creates a new SSL certificate object
* @param issuedTo The entity this certificate is issued to
* @param issuedBy The entity that issued this certificate
+ * @param validNotBefore The not-before date from the certificate validity period in ISO 8601 format
+ * @param validNotAfter The not-after date from the certificate validity period in ISO 8601 format
+ * @deprecated Use {@link #SslCertificate(String, String, Date, Date)}
+ */
+ public SslCertificate(
+ String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
+ this(issuedTo, issuedBy, parseDate(validNotBefore), parseDate(validNotAfter));
+ }
+
+ /**
+ * Creates a new SSL certificate object
+ * @param issuedTo The entity this certificate is issued to
+ * @param issuedBy The entity that issued this certificate
* @param validNotBefore The not-before date from the certificate validity period
* @param validNotAfter The not-after date from the certificate validity period
*/
public SslCertificate(
- String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
+ String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter) {
mIssuedTo = new DName(issuedTo);
mIssuedBy = new DName(issuedBy);
-
- mValidNotBefore = validNotBefore;
- mValidNotAfter = validNotAfter;
+ mValidNotBefore = cloneDate(validNotBefore);
+ mValidNotAfter = cloneDate(validNotAfter);
}
/**
@@ -120,24 +139,44 @@ public class SslCertificate {
public SslCertificate(X509Certificate certificate) {
this(certificate.getSubjectDN().getName(),
certificate.getIssuerDN().getName(),
- DateFormat.getInstance().format(certificate.getNotBefore()),
- DateFormat.getInstance().format(certificate.getNotAfter()));
+ certificate.getNotBefore(),
+ certificate.getNotAfter());
}
/**
* @return Not-before date from the certificate validity period or
* "" if none has been set
*/
+ public Date getValidNotBeforeDate() {
+ return cloneDate(mValidNotBefore);
+ }
+
+ /**
+ * @return Not-before date from the certificate validity period in
+ * ISO 8601 format or "" if none has been set
+ *
+ * @deprecated Use {@link #getValidNotBeforeDate()}
+ */
public String getValidNotBefore() {
- return mValidNotBefore != null ? mValidNotBefore : "";
+ return formatDate(mValidNotBefore);
}
/**
* @return Not-after date from the certificate validity period or
* "" if none has been set
*/
+ public Date getValidNotAfterDate() {
+ return cloneDate(mValidNotAfter);
+ }
+
+ /**
+ * @return Not-after date from the certificate validity period in
+ * ISO 8601 format or "" if none has been set
+ *
+ * @deprecated Use {@link #getValidNotAfterDate()}
+ */
public String getValidNotAfter() {
- return mValidNotAfter != null ? mValidNotAfter : "";
+ return formatDate(mValidNotAfter);
}
/**
@@ -164,6 +203,37 @@ public class SslCertificate {
}
/**
+ * Parse an ISO 8601 date converting ParseExceptions to a null result;
+ */
+ private static Date parseDate(String string) {
+ try {
+ return new SimpleDateFormat(ISO_8601_DATE_FORMAT).parse(string);
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Format a date as an ISO 8601 string, return "" for a null date
+ */
+ private static String formatDate(Date date) {
+ if (date == null) {
+ return "";
+ }
+ return new SimpleDateFormat(ISO_8601_DATE_FORMAT).format(date);
+ }
+
+ /**
+ * Clone a possibly null Date
+ */
+ private static Date cloneDate(Date date) {
+ if (date == null) {
+ return null;
+ }
+ return (Date) date.clone();
+ }
+
+ /**
* A distinguished name helper class: a 3-tuple of:
* - common name (CN),
* - organization (O),
@@ -196,26 +266,31 @@ public class SslCertificate {
*/
public DName(String dName) {
if (dName != null) {
- X509Name x509Name = new X509Name(mDName = dName);
-
- Vector val = x509Name.getValues();
- Vector oid = x509Name.getOIDs();
-
- for (int i = 0; i < oid.size(); i++) {
- if (oid.elementAt(i).equals(X509Name.CN)) {
- mCName = (String) val.elementAt(i);
- continue;
- }
-
- if (oid.elementAt(i).equals(X509Name.O)) {
- mOName = (String) val.elementAt(i);
- continue;
- }
-
- if (oid.elementAt(i).equals(X509Name.OU)) {
- mUName = (String) val.elementAt(i);
- continue;
+ mDName = dName;
+ try {
+ X509Name x509Name = new X509Name(dName);
+
+ Vector val = x509Name.getValues();
+ Vector oid = x509Name.getOIDs();
+
+ for (int i = 0; i < oid.size(); i++) {
+ if (oid.elementAt(i).equals(X509Name.CN)) {
+ mCName = (String) val.elementAt(i);
+ continue;
+ }
+
+ if (oid.elementAt(i).equals(X509Name.O)) {
+ mOName = (String) val.elementAt(i);
+ continue;
+ }
+
+ if (oid.elementAt(i).equals(X509Name.OU)) {
+ mUName = (String) val.elementAt(i);
+ continue;
+ }
}
+ } catch (IllegalArgumentException ex) {
+ // thrown if there is an error parsing the string
}
}
}
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index 2788cb1..e1b9deb 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -20,8 +20,6 @@ import java.security.cert.X509Certificate;
/**
* One or more individual SSL errors and the associated SSL certificate
- *
- * {@hide}
*/
public class SslError {
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 7d2c698..d28148c 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -82,7 +82,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* <li><code>Result</code>, the type of the result of the background
* computation.</li>
* </ol>
- * <p>Not all types are always used by am asynchronous task. To mark a type as unused,
+ * <p>Not all types are always used by an asynchronous task. To mark a type as unused,
* simply use the type {@link Void}:</p>
* <pre>
* private class MyTask extends AsyncTask&lt;Void, Void, Void&gt; { ... }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b706c5c..d114bff 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.os;
import java.io.PrintWriter;
@@ -750,11 +766,8 @@ public abstract class BatteryStats implements Parcelable {
* Checkin server version of dump to produce more compact, computer-readable log.
*
* NOTE: all times are expressed in 'ms'.
- * @param fd
- * @param pw
- * @param which
*/
- private final void dumpCheckinLocked(PrintWriter pw, int which) {
+ public final void dumpCheckinLocked(PrintWriter pw, int which, int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
@@ -856,19 +869,24 @@ public abstract class BatteryStats implements Parcelable {
getDischargeCurrentLevel());
}
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
- if (kernelWakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
- sb.setLength(0);
- printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
-
- dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
- sb.toString());
+ if (reqUid < 0) {
+ Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+ sb.setLength(0);
+ printWakeLockCheckin(sb, ent.getValue(), batteryRealtime, null, which, "");
+
+ dumpLine(pw, 0 /* uid */, category, KERNEL_WAKELOCK_DATA, ent.getKey(),
+ sb.toString());
+ }
}
}
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
+ if (reqUid >= 0 && uid != reqUid) {
+ continue;
+ }
Uid u = uidStats.valueAt(iu);
// Dump Network stats per uid, if any
long rx = u.getTcpBytesReceived(which);
@@ -987,7 +1005,7 @@ public abstract class BatteryStats implements Parcelable {
}
@SuppressWarnings("unused")
- private final void dumpLocked(PrintWriter pw, String prefix, int which) {
+ public final void dumpLocked(PrintWriter pw, String prefix, int which, int reqUid) {
final long rawUptime = SystemClock.uptimeMillis() * 1000;
final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptime(rawUptime);
@@ -1063,23 +1081,25 @@ public abstract class BatteryStats implements Parcelable {
long fullWakeLockTimeTotalMicros = 0;
long partialWakeLockTimeTotalMicros = 0;
- Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
- if (kernelWakelocks.size() > 0) {
- for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
-
- String linePrefix = ": ";
- sb.setLength(0);
- sb.append(prefix);
- sb.append(" Kernel Wake lock ");
- sb.append(ent.getKey());
- linePrefix = printWakeLock(sb, ent.getValue(), batteryRealtime, null, which,
- linePrefix);
- if (!linePrefix.equals(": ")) {
- sb.append(" realtime");
- } else {
- sb.append(": (nothing executed)");
+ if (reqUid < 0) {
+ Map<String, ? extends BatteryStats.Timer> kernelWakelocks = getKernelWakelockStats();
+ if (kernelWakelocks.size() > 0) {
+ for (Map.Entry<String, ? extends BatteryStats.Timer> ent : kernelWakelocks.entrySet()) {
+
+ String linePrefix = ": ";
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Kernel Wake lock ");
+ sb.append(ent.getKey());
+ linePrefix = printWakeLock(sb, ent.getValue(), batteryRealtime, null, which,
+ linePrefix);
+ if (!linePrefix.equals(": ")) {
+ sb.append(" realtime");
+ } else {
+ sb.append(": (nothing executed)");
+ }
+ pw.println(sb.toString());
}
- pw.println(sb.toString());
}
}
@@ -1212,7 +1232,12 @@ public abstract class BatteryStats implements Parcelable {
for (int iu=0; iu<NU; iu++) {
final int uid = uidStats.keyAt(iu);
+ if (reqUid >= 0 && uid != reqUid) {
+ continue;
+ }
+
Uid u = uidStats.valueAt(iu);
+
pw.println(prefix + " #" + uid + ":");
boolean uidActivity = false;
@@ -1421,16 +1446,16 @@ public abstract class BatteryStats implements Parcelable {
pw.println("Total Statistics (Current and Historic):");
pw.println(" System starts: " + getStartCount()
+ ", currently on battery: " + getIsOnBattery());
- dumpLocked(pw, "", STATS_TOTAL);
+ dumpLocked(pw, "", STATS_TOTAL, -1);
pw.println("");
pw.println("Last Run Statistics (Previous run of system):");
- dumpLocked(pw, "", STATS_LAST);
+ dumpLocked(pw, "", STATS_LAST, -1);
pw.println("");
pw.println("Current Battery Statistics (Currently running system):");
- dumpLocked(pw, "", STATS_CURRENT);
+ dumpLocked(pw, "", STATS_CURRENT, -1);
pw.println("");
pw.println("Unplugged Statistics (Since last unplugged from power):");
- dumpLocked(pw, "", STATS_UNPLUGGED);
+ dumpLocked(pw, "", STATS_UNPLUGGED, -1);
}
@SuppressWarnings("unused")
@@ -1445,13 +1470,13 @@ public abstract class BatteryStats implements Parcelable {
}
if (isUnpluggedOnly) {
- dumpCheckinLocked(pw, STATS_UNPLUGGED);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
}
else {
- dumpCheckinLocked(pw, STATS_TOTAL);
- dumpCheckinLocked(pw, STATS_LAST);
- dumpCheckinLocked(pw, STATS_UNPLUGGED);
- dumpCheckinLocked(pw, STATS_CURRENT);
+ dumpCheckinLocked(pw, STATS_TOTAL, -1);
+ dumpCheckinLocked(pw, STATS_LAST, -1);
+ dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
+ dumpCheckinLocked(pw, STATS_CURRENT, -1);
}
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index df10c6a..c9df567 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -73,30 +73,31 @@ public class Binder implements IBinder {
public static final native int getCallingUid();
/**
- * Reset the identity of the incoming IPC to the local process. This can
+ * Reset the identity of the incoming IPC on the current thread. This can
* be useful if, while handling an incoming call, you will be calling
* on interfaces of other objects that may be local to your process and
* need to do permission checks on the calls coming into them (so they
* will check the permission of your own local process, and not whatever
* process originally called you).
- *
+ *
* @return Returns an opaque token that can be used to restore the
* original calling identity by passing it to
* {@link #restoreCallingIdentity(long)}.
- *
+ *
* @see #getCallingPid()
* @see #getCallingUid()
* @see #restoreCallingIdentity(long)
*/
public static final native long clearCallingIdentity();
-
+
/**
- * Restore the identity of the incoming IPC back to a previously identity
- * that was returned by {@link #clearCallingIdentity}.
- *
+ * Restore the identity of the incoming IPC on the current thread
+ * back to a previously identity that was returned by {@link
+ * #clearCallingIdentity}.
+ *
* @param token The opaque token that was previously returned by
* {@link #clearCallingIdentity}.
- *
+ *
* @see #clearCallingIdentity
*/
public static final native void restoreCallingIdentity(long token);
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index e9353d8..3e9fd42 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -21,7 +21,7 @@ package android.os;
*/
public class Build {
/** Value used for when a build property is unknown. */
- private static final String UNKNOWN = "unknown";
+ public static final String UNKNOWN = "unknown";
/** Either a changelist number, or a label like "M4-rc20". */
public static final String ID = getString("ro.build.id");
@@ -41,6 +41,9 @@ public class Build {
/** The name of the instruction set (CPU type + ABI convention) of native code. */
public static final String CPU_ABI = getString("ro.product.cpu.abi");
+ /** The name of the second instruction set (CPU type + ABI convention) of native code. */
+ public static final String CPU_ABI2 = getString("ro.product.cpu.abi2");
+
/** The manufacturer of the product/hardware. */
public static final String MANUFACTURER = getString("ro.product.manufacturer");
@@ -50,6 +53,15 @@ public class Build {
/** The end-user-visible name for the end product. */
public static final String MODEL = getString("ro.product.model");
+ /** The system bootloader version number. */
+ public static final String BOOTLOADER = getString("ro.bootloader");
+
+ /** The radio firmware version number. */
+ public static final String RADIO = getString("gsm.version.baseband");
+
+ /** The name of the hardware (from the kernel command line or /proc). */
+ public static final String HARDWARE = getString("ro.hardware");
+
/** Various version strings. */
public static class VERSION {
/**
@@ -166,6 +178,8 @@ public class Build {
* January 2010: Android 2.1
*/
public static final int ECLAIR_MR1 = 7;
+
+ public static final int FROYO = 8;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index a91655f..0ec1c74 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -132,6 +132,45 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Make a Bundle for a single key/value pair.
+ *
+ * @hide
+ */
+ public static Bundle forPair(String key, String value) {
+ // TODO: optimize this case.
+ Bundle b = new Bundle(1);
+ b.putString(key, value);
+ return b;
+ }
+
+ /**
+ * TODO: optimize this later (getting just the value part of a Bundle
+ * with a single pair) once Bundle.forPair() above is implemented
+ * with a special single-value Map implementation/serialization.
+ *
+ * Note: value in single-pair Bundle may be null.
+ *
+ * @hide
+ */
+ public String getPairValue() {
+ unparcel();
+ int size = mMap.size();
+ if (size > 1) {
+ Log.w(LOG_TAG, "getPairValue() used on Bundle with multiple pairs.");
+ }
+ if (size == 0) {
+ return null;
+ }
+ Object o = mMap.values().iterator().next();
+ try {
+ return (String) o;
+ } catch (ClassCastException e) {
+ typeWarning("getPairValue()", o, "String", e);
+ return null;
+ }
+ }
+
+ /**
* Changes the ClassLoader this Bundle uses when instantiating objects.
*
* @param loader An explicit ClassLoader to use when instantiating objects
@@ -525,6 +564,18 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Inserts an ArrayList<CharSequence> value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value an ArrayList<CharSequence> object, or null
+ */
+ public void putCharSequenceArrayList(String key, ArrayList<CharSequence> value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
* Inserts a Serializable value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -645,6 +696,18 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Inserts a CharSequence array value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * @param key a String, or null
+ * @param value a CharSequence array object, or null
+ */
+ public void putCharSequenceArray(String key, CharSequence[] value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
* Inserts a Bundle value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -1186,6 +1249,28 @@ public final class Bundle implements Parcelable, Cloneable {
* value is explicitly associated with the key.
*
* @param key a String, or null
+ * @return an ArrayList<CharSequence> value, or null
+ */
+ public ArrayList<CharSequence> getCharSequenceArrayList(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (ArrayList<CharSequence>) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "ArrayList<CharSequence>", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
* @return a boolean[] value, or null
*/
public boolean[] getBooleanArray(String key) {
@@ -1384,6 +1469,28 @@ public final class Bundle implements Parcelable, Cloneable {
* value is explicitly associated with the key.
*
* @param key a String, or null
+ * @return a CharSequence[] value, or null
+ */
+ public CharSequence[] getCharSequenceArray(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (CharSequence[]) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "CharSequence[]", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
* @return an IBinder value, or null
*
* @deprecated
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b4f64b6..2e14667 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -57,6 +57,8 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
*/
public final class Debug
{
+ private static final String TAG = "Debug";
+
/**
* Flags for startMethodTracing(). These can be ORed together.
*
@@ -264,6 +266,17 @@ public final class Debug
}
/**
+ * Returns an array of strings that identify VM features. This is
+ * used by DDMS to determine what sorts of operations the VM can
+ * perform.
+ *
+ * @hide
+ */
+ public static String[] getVmFeatureList() {
+ return VMDebug.getVmFeatureList();
+ }
+
+ /**
* Change the JDWP port.
*
* @deprecated no longer needed or useful
@@ -458,6 +471,17 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
+ * Starts method tracing without a backing file. When stopMethodTracing
+ * is called, the result is sent directly to DDMS. (If DDMS is not
+ * attached when tracing ends, the profiling data will be discarded.)
+ *
+ * @hide
+ */
+ public static void startMethodTracingDdms(int bufferSize, int flags) {
+ VMDebug.startMethodTracingDdms(bufferSize, flags);
+ }
+
+ /**
* Determine whether method tracing is currently active.
* @hide
*/
@@ -519,6 +543,14 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
public static int getGlobalFreedSize() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
+ public static int getGlobalClassInitCount() {
+ /* number of classes that have been successfully initialized */
+ return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+ }
+ public static int getGlobalClassInitTime() {
+ /* cumulative elapsed time for class initialization, in usec */
+ return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+ }
public static int getGlobalExternalAllocCount() {
return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
}
@@ -562,6 +594,12 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
public static void resetGlobalFreedSize() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES);
}
+ public static void resetGlobalClassInitCount() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT);
+ }
+ public static void resetGlobalClassInitTime() {
+ VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME);
+ }
public static void resetGlobalExternalAllocCount() {
VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS);
}
@@ -704,6 +742,18 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
+ * Collect "hprof" and send it to DDMS. This will cause a GC.
+ *
+ * @throws UnsupportedOperationException if the VM was built without
+ * HPROF support.
+ *
+ * @hide
+ */
+ public static void dumpHprofDataDdms() {
+ VMDebug.dumpHprofDataDdms();
+ }
+
+ /**
* Returns the number of sent transactions from this process.
* @return The number of sent transactions or -1 if it could not read t.
*/
@@ -753,6 +803,16 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
+ * Dumps the contents of VM reference tables (e.g. JNI locals and
+ * globals) to the log file.
+ *
+ * @hide
+ */
+ public static final void dumpReferenceTables() {
+ VMDebug.dumpReferenceTables();
+ }
+
+ /**
* API for gathering and querying instruction counts.
*
* Example usage:
@@ -1053,7 +1113,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
}
} else {
- Log.w("android.os.Debug",
+ Log.wtf(TAG,
"setFieldsOn(" + (cl == null ? "null" : cl.getName()) +
") called in non-DEBUG build");
}
@@ -1069,4 +1129,31 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugProperty {
}
+
+ /**
+ * Get a debugging dump of a system service by name.
+ *
+ * <p>Most services require the caller to hold android.permission.DUMP.
+ *
+ * @param name of the service to dump
+ * @param fd to write dump output to (usually an output log file)
+ * @param args to pass to the service's dump method, may be null
+ * @return true if the service was dumped successfully, false if
+ * the service could not be found or had an error while dumping
+ */
+ public static boolean dumpService(String name, FileDescriptor fd, String[] args) {
+ IBinder service = ServiceManager.getService(name);
+ if (service == null) {
+ Log.e(TAG, "Can't find service to dump: " + name);
+ return false;
+ }
+
+ try {
+ service.dump(fd, args);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't dump service: " + name, e);
+ return false;
+ }
+ }
}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/os/DropBoxManager.aidl
index 59e53ab..6474ec2 100644
--- a/core/java/android/speech/RecognitionResult.aidl
+++ b/core/java/android/os/DropBoxManager.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.speech;
+package android.os;
-parcelable RecognitionResult;
+parcelable DropBoxManager.Entry;
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
new file mode 100644
index 0000000..7889a92
--- /dev/null
+++ b/core/java/android/os/DropBoxManager.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Enqueues chunks of data (from various sources -- application crashes, kernel
+ * log records, etc.). The queue is size bounded and will drop old data if the
+ * enqueued data exceeds the maximum size. You can think of this as a
+ * persistent, system-wide, blob-oriented "logcat".
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService}
+ * with {@link android.content.Context#DROPBOX_SERVICE}.
+ *
+ * <p>DropBoxManager entries are not sent anywhere directly, but other system
+ * services and debugging tools may scan and upload entries for processing.
+ */
+public class DropBoxManager {
+ private static final String TAG = "DropBoxManager";
+ private final IDropBoxManagerService mService;
+
+ /** Flag value: Entry's content was deleted to save space. */
+ public static final int IS_EMPTY = 1;
+
+ /** Flag value: Content is human-readable UTF-8 text (can be combined with IS_GZIPPED). */
+ public static final int IS_TEXT = 2;
+
+ /** Flag value: Content can be decompressed with {@link java.util.zip.GZIPOutputStream}. */
+ public static final int IS_GZIPPED = 4;
+
+ /**
+ * A single entry retrieved from the drop box.
+ * This may include a reference to a stream, so you must call
+ * {@link #close()} when you are done using it.
+ */
+ public static class Entry implements Parcelable {
+ private final String mTag;
+ private final long mTimeMillis;
+
+ private final byte[] mData;
+ private final ParcelFileDescriptor mFileDescriptor;
+ private final int mFlags;
+
+ /** Create a new empty Entry with no contents. */
+ public Entry(String tag, long millis) {
+ this(tag, millis, (Object) null, IS_EMPTY);
+ }
+
+ /** Create a new Entry with plain text contents. */
+ public Entry(String tag, long millis, String text) {
+ this(tag, millis, (Object) text.getBytes(), IS_TEXT);
+ }
+
+ /**
+ * Create a new Entry with byte array contents.
+ * The data array must not be modified after creating this entry.
+ */
+ public Entry(String tag, long millis, byte[] data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with streaming data contents.
+ * Takes ownership of the ParcelFileDescriptor.
+ */
+ public Entry(String tag, long millis, ParcelFileDescriptor data, int flags) {
+ this(tag, millis, (Object) data, flags);
+ }
+
+ /**
+ * Create a new Entry with the contents read from a file.
+ * The file will be read when the entry's contents are requested.
+ */
+ public Entry(String tag, long millis, File data, int flags) throws IOException {
+ this(tag, millis, (Object) ParcelFileDescriptor.open(
+ data, ParcelFileDescriptor.MODE_READ_ONLY), flags);
+ }
+
+ /** Internal constructor for CREATOR.createFromParcel(). */
+ private Entry(String tag, long millis, Object value, int flags) {
+ if (tag == null) throw new NullPointerException();
+ if (((flags & IS_EMPTY) != 0) != (value == null)) throw new IllegalArgumentException();
+
+ mTag = tag;
+ mTimeMillis = millis;
+ mFlags = flags;
+
+ if (value == null) {
+ mData = null;
+ mFileDescriptor = null;
+ } else if (value instanceof byte[]) {
+ mData = (byte[]) value;
+ mFileDescriptor = null;
+ } else if (value instanceof ParcelFileDescriptor) {
+ mData = null;
+ mFileDescriptor = (ParcelFileDescriptor) value;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /** Close the input stream associated with this entry. */
+ public void close() {
+ try { if (mFileDescriptor != null) mFileDescriptor.close(); } catch (IOException e) { }
+ }
+
+ /** @return the tag originally attached to the entry. */
+ public String getTag() { return mTag; }
+
+ /** @return time when the entry was originally created. */
+ public long getTimeMillis() { return mTimeMillis; }
+
+ /** @return flags describing the content returned by @{link #getInputStream()}. */
+ public int getFlags() { return mFlags & ~IS_GZIPPED; } // getInputStream() decompresses.
+
+ /**
+ * @param maxBytes of string to return (will truncate at this length).
+ * @return the uncompressed text contents of the entry, null if the entry is not text.
+ */
+ public String getText(int maxBytes) {
+ if ((mFlags & IS_TEXT) == 0) return null;
+ if (mData != null) return new String(mData, 0, Math.min(maxBytes, mData.length));
+
+ InputStream is = null;
+ try {
+ is = getInputStream();
+ byte[] buf = new byte[maxBytes];
+ return new String(buf, 0, Math.max(0, is.read(buf)));
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try { if (is != null) is.close(); } catch (IOException e) {}
+ }
+ }
+
+ /** @return the uncompressed contents of the entry, or null if the contents were lost */
+ public InputStream getInputStream() throws IOException {
+ InputStream is;
+ if (mData != null) {
+ is = new ByteArrayInputStream(mData);
+ } else if (mFileDescriptor != null) {
+ is = new ParcelFileDescriptor.AutoCloseInputStream(mFileDescriptor);
+ } else {
+ return null;
+ }
+ return (mFlags & IS_GZIPPED) != 0 ? new GZIPInputStream(is) : is;
+ }
+
+ public static final Parcelable.Creator<Entry> CREATOR = new Parcelable.Creator() {
+ public Entry[] newArray(int size) { return new Entry[size]; }
+ public Entry createFromParcel(Parcel in) {
+ return new Entry(
+ in.readString(), in.readLong(), in.readValue(null), in.readInt());
+ }
+ };
+
+ public int describeContents() {
+ return mFileDescriptor != null ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mTag);
+ out.writeLong(mTimeMillis);
+ if (mFileDescriptor != null) {
+ out.writeValue(mFileDescriptor);
+ } else {
+ out.writeValue(mData);
+ }
+ out.writeInt(mFlags);
+ }
+ }
+
+ /** {@hide} */
+ public DropBoxManager(IDropBoxManagerService service) { mService = service; }
+
+ /**
+ * Create a dummy instance for testing. All methods will fail unless
+ * overridden with an appropriate mock implementation. To obtain a
+ * functional instance, use {@link android.content.Context#getSystemService}.
+ */
+ protected DropBoxManager() { mService = null; }
+
+ /**
+ * Stores human-readable text. The data may be discarded eventually (or even
+ * immediately) if space is limited, or ignored entirely if the tag has been
+ * blocked (see {@link #isTagEnabled}).
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ */
+ public void addText(String tag, String data) {
+ try { mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores binary data, which may be ignored or discarded as with {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param data value to store
+ * @param flags describing the data
+ */
+ public void addData(String tag, byte[] data, int flags) {
+ if (data == null) throw new NullPointerException();
+ try { mService.add(new Entry(tag, 0, data, flags)); } catch (RemoteException e) {}
+ }
+
+ /**
+ * Stores the contents of a file, which may be ignored or discarded as with
+ * {@link #addText}.
+ *
+ * @param tag describing the type of entry being stored
+ * @param file to read from
+ * @param flags describing the data
+ * @throws IOException if the file can't be opened
+ */
+ public void addFile(String tag, File file, int flags) throws IOException {
+ if (file == null) throw new NullPointerException();
+ Entry entry = new Entry(tag, 0, file, flags);
+ try {
+ mService.add(new Entry(tag, 0, file, flags));
+ } catch (RemoteException e) {
+ // ignore
+ } finally {
+ entry.close();
+ }
+ }
+
+ /**
+ * Checks any blacklists (set in system settings) to see whether a certain
+ * tag is allowed. Entries with disabled tags will be dropped immediately,
+ * so you can save the work of actually constructing and sending the data.
+ *
+ * @param tag that would be used in {@link #addText} or {@link #addFile}
+ * @return whether events with that tag would be accepted
+ */
+ public boolean isTagEnabled(String tag) {
+ try { return mService.isTagEnabled(tag); } catch (RemoteException e) { return false; }
+ }
+
+ /**
+ * Gets the next entry from the drop box *after* the specified time.
+ * Requires android.permission.READ_LOGS. You must always call
+ * {@link Entry#close()} on the return value!
+ *
+ * @param tag of entry to look for, null for all tags
+ * @param msec time of the last entry seen
+ * @return the next entry, or null if there are no more entries
+ */
+ public Entry getNextEntry(String tag, long msec) {
+ try { return mService.getNextEntry(tag, msec); } catch (RemoteException e) { return null; }
+ }
+
+ // TODO: It may be useful to have some sort of notification mechanism
+ // when data is added to the dropbox, for demand-driven readers --
+ // for now readers need to poll the dropbox to find new data.
+}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index f761e8e..812391c 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,6 +18,8 @@ package android.os;
import java.io.File;
+import android.os.storage.IMountService;
+
/**
* Provides access to environment variables.
*/
@@ -26,6 +28,8 @@ public class Environment {
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
+ private static IMountService mMntSvc = null;
+
/**
* Gets the Android root directory.
*/
@@ -39,6 +43,14 @@ public class Environment {
private static final File EXTERNAL_STORAGE_DIRECTORY
= getDirectory("EXTERNAL_STORAGE", "/sdcard");
+ private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY
+ = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"),
+ "Android"), "data");
+
+ private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY
+ = new File (new File(getDirectory("EXTERNAL_STORAGE", "/sdcard"),
+ "Android"), "media");
+
private static final File DOWNLOAD_CACHE_DIRECTORY
= getDirectory("DOWNLOAD_CACHE", "/cache");
@@ -50,13 +62,194 @@ public class Environment {
}
/**
- * Gets the Android external storage directory.
+ * Gets the Android external storage directory. This directory may not
+ * currently be accessible if it has been mounted by the user on their
+ * computer, has been removed from the device, or some other problem has
+ * happened. You can determine its current state with
+ * {@link #getExternalStorageState()}.
+ *
+ * <p>Applications should not directly use this top-level directory, in
+ * order to avoid polluting the user's root namespace. Any files that are
+ * private to the application should be placed in a directory returned
+ * by {@link android.content.Context#getExternalFilesDir
+ * Context.getExternalFilesDir}, which the system will take care of deleting
+ * if the application is uninstalled. Other shared files should be placed
+ * in one of the directories returned by
+ * {@link #getExternalStoragePublicDirectory}.
+ *
+ * <p>Here is an example of typical code to monitor the state of
+ * external storage:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * monitor_storage}
*/
public static File getExternalStorageDirectory() {
return EXTERNAL_STORAGE_DIRECTORY;
}
/**
+ * Standard directory in which to place any audio files that should be
+ * in the regular list of music for the user.
+ * This may be combined with
+ * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+ * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+ * of directories to categories a particular audio file as more than one
+ * type.
+ */
+ public static String DIRECTORY_MUSIC = "Music";
+
+ /**
+ * Standard directory in which to place any audio files that should be
+ * in the list of podcasts that the user can select (not as regular
+ * music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_NOTIFICATIONS},
+ * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+ * of directories to categories a particular audio file as more than one
+ * type.
+ */
+ public static String DIRECTORY_PODCASTS = "Podcasts";
+
+ /**
+ * Standard directory in which to place any audio files that should be
+ * in the list of ringtones that the user can select (not as regular
+ * music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and
+ * {@link #DIRECTORY_ALARMS} as a series
+ * of directories to categories a particular audio file as more than one
+ * type.
+ */
+ public static String DIRECTORY_RINGTONES = "Ringtones";
+
+ /**
+ * Standard directory in which to place any audio files that should be
+ * in the list of alarms that the user can select (not as regular
+ * music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
+ * and {@link #DIRECTORY_RINGTONES} as a series
+ * of directories to categories a particular audio file as more than one
+ * type.
+ */
+ public static String DIRECTORY_ALARMS = "Alarms";
+
+ /**
+ * Standard directory in which to place any audio files that should be
+ * in the list of notifications that the user can select (not as regular
+ * music).
+ * This may be combined with {@link #DIRECTORY_MUSIC},
+ * {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
+ * of directories to categories a particular audio file as more than one
+ * type.
+ */
+ public static String DIRECTORY_NOTIFICATIONS = "Notifications";
+
+ /**
+ * Standard directory in which to place pictures that are available to
+ * the user. Note that this is primarily a convention for the top-level
+ * public directory, as the media scanner will find and collect pictures
+ * in any directory.
+ */
+ public static String DIRECTORY_PICTURES = "Pictures";
+
+ /**
+ * Standard directory in which to place movies that are available to
+ * the user. Note that this is primarily a convention for the top-level
+ * public directory, as the media scanner will find and collect movies
+ * in any directory.
+ */
+ public static String DIRECTORY_MOVIES = "Movies";
+
+ /**
+ * Standard directory in which to place files that have been downloaded by
+ * the user. Note that this is primarily a convention for the top-level
+ * public directory, you are free to download files anywhere in your own
+ * private directories. Also note that though the constant here is
+ * named DIRECTORY_DOWNLOADS (plural), the actual file name is non-plural for
+ * backwards compatibility reasons.
+ */
+ public static String DIRECTORY_DOWNLOADS = "Download";
+
+ /**
+ * The traditional location for pictures and videos when mounting the
+ * device as a camera. Note that this is primarily a convention for the
+ * top-level public directory, as this convention makes no sense elsewhere.
+ */
+ public static String DIRECTORY_DCIM = "DCIM";
+
+ /**
+ * Get a top-level public external storage directory for placing files of
+ * a particular type. This is where the user will typically place and
+ * manage their own files, so you should be careful about what you put here
+ * to ensure you don't erase their files or get in the way of their own
+ * organization.
+ *
+ * <p>Here is an example of typical code to manipulate a picture on
+ * the public external storage:</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
+ * public_picture}
+ *
+ * @param type The type of storage directory to return. Should be one of
+ * {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
+ * {@link #DIRECTORY_RINGTONES}, {@link #DIRECTORY_ALARMS},
+ * {@link #DIRECTORY_NOTIFICATIONS}, {@link #DIRECTORY_PICTURES},
+ * {@link #DIRECTORY_MOVIES}, {@link #DIRECTORY_DOWNLOADS}, or
+ * {@link #DIRECTORY_DCIM}. May not be null.
+ *
+ * @return Returns the File path for the directory. Note that this
+ * directory may not yet exist, so you must make sure it exists before
+ * using it such as with {@link File#mkdirs File.mkdirs()}.
+ */
+ public static File getExternalStoragePublicDirectory(String type) {
+ return new File(getExternalStorageDirectory(), type);
+ }
+
+ /**
+ * Returns the path for android-specific data on the SD card.
+ * @hide
+ */
+ public static File getExternalStorageAndroidDataDir() {
+ return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY;
+ }
+
+ /**
+ * Generates the raw path to an application's data
+ * @hide
+ */
+ public static File getExternalStorageAppDataDirectory(String packageName) {
+ return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName);
+ }
+
+ /**
+ * Generates the raw path to an application's media
+ * @hide
+ */
+ public static File getExternalStorageAppMediaDirectory(String packageName) {
+ return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName);
+ }
+
+ /**
+ * Generates the path to an application's files.
+ * @hide
+ */
+ public static File getExternalStorageAppFilesDirectory(String packageName) {
+ return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
+ packageName), "files");
+ }
+
+ /**
+ * Generates the path to an application's cache.
+ * @hide
+ */
+ public static File getExternalStorageAppCacheDirectory(String packageName) {
+ return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
+ packageName), "cache");
+ }
+
+ /**
* Gets the Android Download/Cache content directory.
*/
public static File getDownloadCacheDirectory() {
@@ -119,9 +312,21 @@ public class Environment {
/**
* Gets the current state of the external storage device.
+ * Note: This call should be deprecated as it doesn't support
+ * multiple volumes.
+ *
+ * <p>See {@link #getExternalStorageDirectory()} for an example of its use.
*/
public static String getExternalStorageState() {
- return SystemProperties.get("EXTERNAL_STORAGE_STATE", MEDIA_REMOVED);
+ try {
+ if (mMntSvc == null) {
+ mMntSvc = IMountService.Stub.asInterface(ServiceManager
+ .getService("mount"));
+ }
+ return mMntSvc.getVolumeState(getExternalStorageDirectory().toString());
+ } catch (Exception rex) {
+ return Environment.MEDIA_REMOVED;
+ }
}
static File getDirectory(String variableName, String defaultPath) {
diff --git a/core/java/android/os/FileObserver.java b/core/java/android/os/FileObserver.java
index 38d252e..7e99f38 100644
--- a/core/java/android/os/FileObserver.java
+++ b/core/java/android/os/FileObserver.java
@@ -52,75 +52,75 @@ public abstract class FileObserver {
public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE
| CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE
- | DELETE_SELF | MOVE_SELF;
+ | DELETE_SELF | MOVE_SELF;
private static final String LOG_TAG = "FileObserver";
private static class ObserverThread extends Thread {
- private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
- private int m_fd;
-
- public ObserverThread() {
- super("FileObserver");
- m_fd = init();
- }
-
- public void run() {
- observe(m_fd);
- }
-
- public int startWatching(String path, int mask, FileObserver observer) {
- int wfd = startWatching(m_fd, path, mask);
-
- Integer i = new Integer(wfd);
- if (wfd >= 0) {
- synchronized (m_observers) {
- m_observers.put(i, new WeakReference(observer));
- }
- }
-
- return i;
- }
-
- public void stopWatching(int descriptor) {
- stopWatching(m_fd, descriptor);
- }
-
- public void onEvent(int wfd, int mask, String path) {
- // look up our observer, fixing up the map if necessary...
- FileObserver observer;
-
- synchronized (m_observers) {
- WeakReference weak = m_observers.get(wfd);
- observer = (FileObserver) weak.get();
- if (observer == null) {
- m_observers.remove(wfd);
+ private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
+ private int m_fd;
+
+ public ObserverThread() {
+ super("FileObserver");
+ m_fd = init();
+ }
+
+ public void run() {
+ observe(m_fd);
+ }
+
+ public int startWatching(String path, int mask, FileObserver observer) {
+ int wfd = startWatching(m_fd, path, mask);
+
+ Integer i = new Integer(wfd);
+ if (wfd >= 0) {
+ synchronized (m_observers) {
+ m_observers.put(i, new WeakReference(observer));
+ }
}
+
+ return i;
}
- // ...then call out to the observer without the sync lock held
- if (observer != null) {
- try {
- observer.onEvent(mask, path);
- } catch (Throwable throwable) {
- Log.e(LOG_TAG, "Unhandled throwable " + throwable.toString() +
- " (returned by observer " + observer + ")", throwable);
- RuntimeInit.crash("FileObserver", throwable);
+ public void stopWatching(int descriptor) {
+ stopWatching(m_fd, descriptor);
+ }
+
+ public void onEvent(int wfd, int mask, String path) {
+ // look up our observer, fixing up the map if necessary...
+ FileObserver observer = null;
+
+ synchronized (m_observers) {
+ WeakReference weak = m_observers.get(wfd);
+ if (weak != null) { // can happen with lots of events from a dead wfd
+ observer = (FileObserver) weak.get();
+ if (observer == null) {
+ m_observers.remove(wfd);
+ }
+ }
+ }
+
+ // ...then call out to the observer without the sync lock held
+ if (observer != null) {
+ try {
+ observer.onEvent(mask, path);
+ } catch (Throwable throwable) {
+ Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
+ }
}
}
- }
- private native int init();
- private native void observe(int fd);
- private native int startWatching(int fd, String path, int mask);
- private native void stopWatching(int fd, int wfd);
+ private native int init();
+ private native void observe(int fd);
+ private native int startWatching(int fd, String path, int mask);
+ private native void stopWatching(int fd, int wfd);
}
private static ObserverThread s_observerThread;
static {
- s_observerThread = new ObserverThread();
- s_observerThread.start();
+ s_observerThread = new ObserverThread();
+ s_observerThread.start();
}
// instance
@@ -129,30 +129,30 @@ public abstract class FileObserver {
private int m_mask;
public FileObserver(String path) {
- this(path, ALL_EVENTS);
+ this(path, ALL_EVENTS);
}
public FileObserver(String path, int mask) {
- m_path = path;
- m_mask = mask;
- m_descriptor = -1;
+ m_path = path;
+ m_mask = mask;
+ m_descriptor = -1;
}
protected void finalize() {
- stopWatching();
+ stopWatching();
}
public void startWatching() {
- if (m_descriptor < 0) {
- m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
- }
+ if (m_descriptor < 0) {
+ m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
+ }
}
public void stopWatching() {
- if (m_descriptor >= 0) {
- s_observerThread.stopWatching(m_descriptor);
- m_descriptor = -1;
- }
+ if (m_descriptor >= 0) {
+ s_observerThread.stopWatching(m_descriptor);
+ m_descriptor = -1;
+ }
}
public abstract void onEvent(int event, String path);
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 51dfb5b..a17b7fe 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -115,6 +115,9 @@ public class FileUtils
*/
public static boolean copyToFile(InputStream inputStream, File destFile) {
try {
+ if (destFile.exists()) {
+ destFile.delete();
+ }
OutputStream out = new FileOutputStream(destFile);
try {
byte[] buffer = new byte[4096];
@@ -153,14 +156,16 @@ public class FileUtils
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
InputStream input = new FileInputStream(file);
try {
- if (max > 0) { // "head" mode: read the first N bytes
+ long size = file.length();
+ if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
+ if (size > 0 && (max == 0 || size < max)) max = (int) size;
byte[] data = new byte[max + 1];
int length = input.read(data);
if (length <= 0) return "";
if (length <= max) return new String(data, 0, length);
if (ellipsis == null) return new String(data, 0, max);
return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: read it all, keep the last N
+ } else if (max < 0) { // "tail" mode: keep the last N
int len;
boolean rolled = false;
byte[] last = null, data = null;
@@ -180,7 +185,7 @@ public class FileUtils
}
if (ellipsis == null || !rolled) return new String(last);
return ellipsis + new String(last);
- } else { // "cat" mode: read it all
+ } else { // "cat" mode: size unknown, read it all in streaming fashion
ByteArrayOutputStream contents = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java
deleted file mode 100644
index 9e7902b..0000000
--- a/core/java/android/os/HandlerStateMachine.java
+++ /dev/null
@@ -1,290 +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.os;
-
-import android.util.Log;
-import android.util.LogPrinter;
-
-/**
- * {@hide}
- *
- * Implement a state machine where each state is an object,
- * HandlerState. Each HandlerState must implement processMessage
- * and optionally enter/exit. When a state machine is created
- * the initial state must be set. When messages are sent to
- * a state machine the current state's processMessage method is
- * invoked. If this is the first message for this state the
- * enter method is called prior to processMessage and when
- * transtionTo is invoked the state's exit method will be
- * called after returning from processMessage.
- *
- * If a message should be handled in a different state the
- * processMessage method may call deferMessage. This causes
- * the message to be saved on a list until transitioning
- * to a new state, at which time all of the deferred messages
- * will be put on the front of the state machines queue and
- * processed by the new current state's processMessage
- * method.
- *
- * Below is an example state machine with two state's, S1 and S2.
- * The initial state is S1 which defers all messages and only
- * transition to S2 when message.what == TEST_WHAT_2. State S2
- * will process each messages until it receives TEST_WHAT_2
- * where it will transition back to S1:
-<code>
- class StateMachine1 extends HandlerStateMachine {
- private static final int TEST_WHAT_1 = 1;
- private static final int TEST_WHAT_2 = 2;
-
- StateMachine1(String name) {
- super(name);
- setInitialState(mS1);
- }
-
- class S1 extends HandlerState {
- &amp;#064;Override public void enter(Message message) {
- }
-
- &amp;#064;Override public void processMessage(Message message) {
- deferMessage(message);
- if (message.what == TEST_WHAT_2) {
- transitionTo(mS2);
- }
- }
-
- &amp;#064;Override public void exit(Message message) {
- }
- }
-
- class S2 extends HandlerState {
- &amp;#064;Override public void processMessage(Message message) {
- // Do some processing
- if (message.what == TEST_WHAT_2) {
- transtionTo(mS1);
- }
- }
- }
-
- private S1 mS1 = new S1();
- private S2 mS2 = new S2();
- }
-</code>
- */
-public class HandlerStateMachine {
-
- private boolean mDbg = false;
- private static final String TAG = "HandlerStateMachine";
- private String mName;
- private SmHandler mHandler;
- private HandlerThread mHandlerThread;
-
- /**
- * Handle messages sent to the state machine by calling
- * the current state's processMessage. It also handles
- * the enter/exit calls and placing any deferred messages
- * back onto the queue when transitioning to a new state.
- */
- class SmHandler extends Handler {
-
- SmHandler(Looper looper) {
- super(looper);
- }
-
- /**
- * This will dispatch the message to the
- * current state's processMessage.
- */
- @Override
- final public void handleMessage(Message msg) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage E");
- if (mDestState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter");
- mCurrentState = mDestState;
- mDestState = null;
- mCurrentState.enter(msg);
- }
- if (mCurrentState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage");
- mCurrentState.processMessage(msg);
- } else {
- /* Strange no state to execute */
- Log.e(TAG, "handleMessage: no current state, did you call setInitialState");
- }
-
- if (mDestState != null) {
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit");
- mCurrentState.exit(msg);
-
- /**
- * Place the messages from the deferred queue:t
- * on to the Handler's message queue in the
- * same order that they originally arrived.
- *
- * We set cur.when = 0 to circumvent the check
- * that this message has already been sent.
- */
- while (mDeferredMessages != null) {
- Message cur = mDeferredMessages;
- mDeferredMessages = mDeferredMessages.next;
- cur.when = 0;
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what="
- + cur.what + " target=" + cur.target);
- sendMessageAtFrontOfQueue(cur);
- }
- if (mDbg) Log.d(TAG, "SmHandler.handleMessage X");
- }
- }
-
- public HandlerState mCurrentState;
- public HandlerState mDestState;
- public Message mDeferredMessages;
- }
-
- /**
- * Create an active StateMachine, one that has a
- * dedicated thread/looper/queue.
- */
- public HandlerStateMachine(String name) {
- mName = name;
- mHandlerThread = new HandlerThread(name);
- mHandlerThread.start();
- mHandler = new SmHandler(mHandlerThread.getLooper());
- }
-
- /**
- * Get a message and set Message.target = this.
- */
- public final Message obtainMessage()
- {
- Message msg = Message.obtain(mHandler);
- if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target);
- return msg;
- }
-
- /**
- * Get a message and set Message.target = this and
- * Message.what = what.
- */
- public final Message obtainMessage(int what) {
- Message msg = Message.obtain(mHandler, what);
- if (mDbg) {
- Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what +
- " target=" + msg.target);
- }
- return msg;
- }
-
- /**
- * Enqueue a message to this state machine.
- */
- public final void sendMessage(Message msg) {
- if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what);
- mHandler.sendMessage(msg);
- }
-
- /**
- * Enqueue a message to this state machine after a delay.
- */
- public final void sendMessageDelayed(Message msg, long delayMillis) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what="
- + msg.what + " delay=" + delayMillis);
- }
- mHandler.sendMessageDelayed(msg, delayMillis);
- }
-
- /**
- * Set the initial state. This must be invoked before
- * and messages are sent to the state machine.
- */
- public void setInitialState(HandlerState initialState) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.setInitialState EX initialState"
- + initialState.getClass().getName());
- }
- mHandler.mDestState = initialState;
- }
-
- /**
- * transition to destination state. Upon returning
- * from processMessage the current state's exit will
- * be executed and upon the next message arriving
- * destState.enter will be invoked.
- */
- final public void transitionTo(HandlerState destState) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.transitionTo EX destState"
- + destState.getClass().getName());
- }
- mHandler.mDestState = destState;
- }
-
- /**
- * Defer this message until next state transition.
- * Upon transitioning all deferred messages will be
- * placed on the queue and reprocessed in the original
- * order. (i.e. The next state the oldest messages will
- * be processed first)
- */
- final public void deferMessage(Message msg) {
- if (mDbg) {
- Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages="
- + mHandler.mDeferredMessages);
- }
-
- /* Copy the "msg" to "newMsg" as "msg" will be recycled */
- Message newMsg = obtainMessage();
- newMsg.copyFrom(msg);
-
- /* Place on front of queue */
- newMsg.next = mHandler.mDeferredMessages;
- mHandler.mDeferredMessages = newMsg;
- }
-
- /**
- * @return the name
- */
- public String getName() {
- return mName;
- }
-
- /**
- * @return Handler
- */
- public Handler getHandler() {
- return mHandler;
- }
-
- /**
- * @return if debugging is enabled
- */
- public boolean isDbg() {
- return mDbg;
- }
-
- /**
- * Set debug enable/disabled.
- */
- public void setDbg(boolean dbg) {
- mDbg = dbg;
- if (mDbg) {
- mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
- } else {
- mHandlerThread.getLooper().setMessageLogging(null);
- }
- }
-}
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 65301e4..911439a 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -53,9 +53,9 @@ public class HandlerThread extends Thread {
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
- Process.setThreadPriority(mPriority);
notifyAll();
}
+ Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 5c40c9a0..174f3b6 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -112,7 +112,8 @@ public interface IBinder {
/**
* Flag to {@link #transact}: this is a one-way call, meaning that the
* caller returns immediately, without waiting for a result from the
- * callee.
+ * callee. Applies only if the caller and callee are in different
+ * processes.
*/
int FLAG_ONEWAY = 0x00000001;
diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl
deleted file mode 100644
index e56b55d..0000000
--- a/core/java/android/os/ICheckinService.aidl
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.os.IParentalControlCallback;
-
-/**
- * System private API for direct access to the checkin service.
- * Users should use the content provider instead.
- *
- * @see android.provider.Checkin
- * {@hide}
- */
-interface ICheckinService {
- /** Synchronously attempt a checkin with the server, return true
- * on success.
- * @throws IllegalStateException whenever an error occurs. The
- * cause of the exception will be the real exception:
- * IOException for network errors, JSONException for invalid
- * server responses, etc.
- */
- boolean checkin();
-
- /** Direct submission of crash data; returns after writing the crash. */
- void reportCrashSync(in byte[] crashData);
-
- /** Asynchronous "fire and forget" version of crash reporting. */
- oneway void reportCrashAsync(in byte[] crashData);
-
- /** Reboot into the recovery system and wipe all user data. */
- void masterClear();
-
- /**
- * Determine if the device is under parental control. Return null if
- * we are unable to check the parental control status.
- */
- void getParentalControlState(IParentalControlCallback p,
- String requestingApp);
-}
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
deleted file mode 100644
index 4491a8a..0000000
--- a/core/java/android/os/IMountService.aidl
+++ /dev/null
@@ -1,83 +0,0 @@
-/* //device/java/android/android/os/IUsb.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.os;
-
-/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
- * In particular, the ordering of the methods below must match the
- * _TRANSACTION enum in IMountService.cpp
- * @hide
- */
-interface IMountService
-{
- /**
- * Is mass storage support enabled?
- */
- boolean getMassStorageEnabled();
-
- /**
- * Enable or disable mass storage support.
- */
- void setMassStorageEnabled(boolean enabled);
-
- /**
- * Is mass storage connected?
- */
- boolean getMassStorageConnected();
-
- /**
- * Mount external storage at given mount point.
- */
- void mountMedia(String mountPoint);
-
- /**
- * Safely unmount external storage at given mount point.
- */
- void unmountMedia(String mountPoint);
-
- /**
- * Format external storage given a mount point.
- */
- void formatMedia(String mountPoint);
-
- /**
- * Returns true if media notification sounds are enabled.
- */
- boolean getPlayNotificationSounds();
-
- /**
- * Sets whether or not media notification sounds are played.
- */
- void setPlayNotificationSounds(boolean value);
-
- /**
- * Returns true if USB Mass Storage is automatically started
- * when a UMS host is detected.
- */
- boolean getAutoStartUms();
-
- /**
- * Sets whether or not USB Mass Storage is automatically started
- * when a UMS host is detected.
- */
- void setAutoStartUms(boolean value);
-
- /**
- * Shuts down the MountService and gracefully unmounts all external media.
- */
- void shutdown();
-}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
new file mode 100644
index 0000000..212c5fb
--- /dev/null
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -0,0 +1,210 @@
+/* //device/java/android/android/os/INetworkManagementService.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.net.InterfaceConfiguration;
+import android.net.INetworkManagementEventObserver;
+import android.net.wifi.WifiConfiguration;
+
+/**
+ * @hide
+ */
+interface INetworkManagementService
+{
+ /**
+ ** GENERAL
+ **/
+
+ /**
+ * Register an observer to receive events
+ */
+ void registerObserver(INetworkManagementEventObserver obs);
+
+ /**
+ * Unregister an observer from receiving events.
+ */
+ void unregisterObserver(INetworkManagementEventObserver obs);
+
+ /**
+ * Returns a list of currently known network interfaces
+ */
+ String[] listInterfaces();
+
+ /**
+ * Retrieves the specified interface config
+ *
+ */
+ InterfaceConfiguration getInterfaceConfig(String iface);
+
+ /**
+ * Sets the configuration of the specified interface
+ */
+ void setInterfaceConfig(String iface, in InterfaceConfiguration cfg);
+
+ /**
+ * Shuts down the service
+ */
+ void shutdown();
+
+ /**
+ ** TETHERING RELATED
+ **/
+
+
+ /**
+ * Returns true if IP forwarding is enabled
+ */
+ boolean getIpForwardingEnabled();
+
+ /**
+ * Enables/Disables IP Forwarding
+ */
+ void setIpForwardingEnabled(boolean enabled);
+
+ /**
+ * Start tethering services with the specified dhcp server range
+ * arg is a set of start end pairs defining the ranges.
+ */
+ void startTethering(in String[] dhcpRanges);
+
+ /**
+ * Stop currently running tethering services
+ */
+ void stopTethering();
+
+ /**
+ * Returns true if tethering services are started
+ */
+ boolean isTetheringStarted();
+
+ /**
+ * Tethers the specified interface
+ */
+ void tetherInterface(String iface);
+
+ /**
+ * Untethers the specified interface
+ */
+ void untetherInterface(String iface);
+
+ /**
+ * Returns a list of currently tethered interfaces
+ */
+ String[] listTetheredInterfaces();
+
+ /**
+ * Sets the list of DNS forwarders (in order of priority)
+ */
+ void setDnsForwarders(in String[] dns);
+
+ /**
+ * Returns the list of DNS fowarders (in order of priority)
+ */
+ String[] getDnsForwarders();
+
+ /**
+ * Enables Network Address Translation between two interfaces.
+ * The address and netmask of the external interface is used for
+ * the NAT'ed network.
+ */
+ void enableNat(String internalInterface, String externalInterface);
+
+ /**
+ * Disables Network Address Translation between two interfaces.
+ */
+ void disableNat(String internalInterface, String externalInterface);
+
+ /**
+ ** PPPD
+ **/
+
+ /**
+ * Returns the list of currently known TTY devices on the system
+ */
+ String[] listTtys();
+
+ /**
+ * Attaches a PPP server daemon to the specified TTY with the specified
+ * local/remote addresses.
+ */
+ void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr,
+ String dns2Addr);
+
+ /**
+ * Detaches a PPP server daemon from the specified TTY.
+ */
+ void detachPppd(String tty);
+
+ /**
+ * Turn on USB RNDIS support - this will turn off thinks like adb/mass-storage
+ */
+ void startUsbRNDIS();
+
+ /**
+ * Turn off USB RNDIS support
+ */
+ void stopUsbRNDIS();
+
+ /**
+ * Check the status of USB RNDIS support
+ */
+ boolean isUsbRNDISStarted();
+
+ /**
+ * Start Wifi Access Point
+ */
+ void startAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface);
+
+ /**
+ * Stop Wifi Access Point
+ */
+ void stopAccessPoint();
+
+ /**
+ * Set Access Point config
+ */
+ void setAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface);
+
+ /**
+ * Read number of bytes sent over an interface
+ */
+ long getInterfaceTxCounter(String iface);
+
+ /**
+ * Read number of bytes received over an interface
+ */
+ long getInterfaceRxCounter(String iface);
+
+ /**
+ * Configures bandwidth throttling on an interface
+ */
+ void setInterfaceThrottle(String iface, int rxKbps, int txKbps);
+
+ /**
+ * Returns the currently configured RX throttle values
+ * for the specified interface
+ */
+ int getInterfaceRxThrottle(String iface);
+
+ /**
+ * Returns the currently configured TX throttle values
+ * for the specified interface
+ */
+ int getInterfaceTxThrottle(String iface);
+
+}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index b9dc860..dedc347 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -22,17 +22,20 @@ interface IPowerManager
{
void acquireWakeLock(int flags, IBinder lock, String tag);
void goToSleep(long time);
+ void goToSleepWithReason(long time, int reason);
void releaseWakeLock(IBinder lock, int flags);
void userActivity(long when, boolean noChangeLights);
void userActivityWithForce(long when, boolean noChangeLights, boolean force);
void setPokeLock(int pokey, IBinder lock, String tag);
int getSupportedWakeLockFlags();
void setStayOnSetting(int val);
- long getScreenOnTime();
+ void setMaximumScreenOffTimeount(int timeMs);
void preventScreenOn(boolean prevent);
- void setScreenBrightnessOverride(int brightness);
boolean isScreenOn();
+ void reboot(String reason);
+ void crash(String message);
// sets the brightness of the backlights (screen, keyboard, button) 0-255
void setBacklightBrightness(int brightness);
+ void setAttentionLight(boolean on, int color);
}
diff --git a/core/java/android/os/IRemoteCallback.aidl b/core/java/android/os/IRemoteCallback.aidl
new file mode 100644
index 0000000..f0c6c73
--- /dev/null
+++ b/core/java/android/os/IRemoteCallback.aidl
@@ -0,0 +1,25 @@
+/* //device/java/android/android/app/IActivityPendingResult.aidl
+**
+** Copyright 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.os;
+
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IRemoteCallback {
+ void sendResult(in Bundle data);
+}
diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IVibratorService.aidl
index 34f30a7..c98fb56 100755
--- a/core/java/android/os/IHardwareService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -17,19 +17,10 @@
package android.os;
/** {@hide} */
-interface IHardwareService
+interface IVibratorService
{
- // Vibrator support
void vibrate(long milliseconds, IBinder token);
void vibratePattern(in long[] pattern, int repeat, IBinder token);
void cancelVibrate(IBinder token);
-
- // flashlight support
- boolean getFlashlightEnabled();
- void setFlashlightEnabled(boolean on);
- void enableCameraFlash(int milliseconds);
-
- // for the phone
- void setAttentionLight(boolean on, int color);
}
diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java
index 3fe21d9..d348f07 100644
--- a/core/java/android/os/LocalPowerManager.java
+++ b/core/java/android/os/LocalPowerManager.java
@@ -44,7 +44,10 @@ public interface LocalPowerManager {
void enableUserActivity(boolean enabled);
// the same as the method on PowerManager
- public void userActivity(long time, boolean noChangeLights, int eventType);
+ void userActivity(long time, boolean noChangeLights, int eventType);
boolean isScreenOn();
+
+ void setScreenBrightnessOverride(int brightness);
+ void setButtonBrightnessOverride(int brightness);
}
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index 03542dd..a81e16b 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -52,7 +52,7 @@ public class MemoryFile
private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
- private static native int native_get_mapped_size(FileDescriptor fd) throws IOException;
+ private static native int native_get_size(FileDescriptor fd) throws IOException;
private FileDescriptor mFD; // ashmem file descriptor
private int mAddress; // address of ashmem memory
@@ -273,7 +273,8 @@ public class MemoryFile
* @hide
*/
public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
- return new ParcelFileDescriptor(getFileDescriptor());
+ FileDescriptor fd = getFileDescriptor();
+ return fd != null ? new ParcelFileDescriptor(fd) : null;
}
/**
@@ -300,20 +301,19 @@ public class MemoryFile
* @hide
*/
public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
- return (native_get_mapped_size(fd) >= 0);
+ return (native_get_size(fd) >= 0);
}
/**
- * Returns the size of the memory file, rounded up to a page boundary, that
- * the file descriptor refers to, or -1 if the file descriptor does not
- * refer to a memory file.
+ * Returns the size of the memory file that the file descriptor refers to,
+ * or -1 if the file descriptor does not refer to a memory file.
*
* @throws IOException If <code>fd</code> is not a valid file descriptor.
*
* @hide
*/
- public static int getMappedSize(FileDescriptor fd) throws IOException {
- return native_get_mapped_size(fd);
+ public static int getSize(FileDescriptor fd) throws IOException {
+ return native_get_size(fd);
}
/**
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 4130109..476da1d 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -40,20 +40,36 @@ public final class Message implements Parcelable {
*/
public int what;
- // Use these fields instead of using the class's Bundle if you can.
- /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
- if you only need to store a few integer values. */
+ /**
+ * arg1 and arg2 are lower-cost alternatives to using
+ * {@link #setData(Bundle) setData()} if you only need to store a
+ * few integer values.
+ */
public int arg1;
- /** arg1 and arg2 are lower-cost alternatives to using {@link #setData(Bundle) setData()}
- if you only need to store a few integer values.*/
+ /**
+ * arg1 and arg2 are lower-cost alternatives to using
+ * {@link #setData(Bundle) setData()} if you only need to store a
+ * few integer values.
+ */
public int arg2;
- /** An arbitrary object to send to the recipient. This must be null when
- * sending messages across processes. */
+ /**
+ * An arbitrary object to send to the recipient. When using
+ * {@link Messenger} to send the message across processes this can only
+ * be non-null if it contains a Parcelable of a framework class (not one
+ * implemented by the application). For other data transfer use
+ * {@link #setData}.
+ *
+ * <p>Note that Parcelable objects here are not supported prior to
+ * the {@link android.os.Build.VERSION_CODES#FROYO} release.
+ */
public Object obj;
- /** Optional Messenger where replies to this message can be sent.
+ /**
+ * Optional Messenger where replies to this message can be sent. The
+ * semantics of exactly how this is used are up to the sender and
+ * receiver.
*/
public Messenger replyTo;
@@ -278,14 +294,22 @@ public final class Message implements Parcelable {
* the <em>target</em> {@link Handler} that is receiving this Message to
* dispatch it. If
* not set, the message will be dispatched to the receiving Handler's
- * {@link Handler#handleMessage(Message Handler.handleMessage())}. */
+ * {@link Handler#handleMessage(Message Handler.handleMessage())}.
+ */
public Runnable getCallback() {
return callback;
}
/**
* Obtains a Bundle of arbitrary data associated with this
- * event, lazily creating it if necessary. Set this value by calling {@link #setData(Bundle)}.
+ * event, lazily creating it if necessary. Set this value by calling
+ * {@link #setData(Bundle)}. Note that when transferring data across
+ * processes via {@link Messenger}, you will need to set your ClassLoader
+ * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
+ * Bundle.setClassLoader()} so that it can instantiate your objects when
+ * you retrieve them.
+ * @see #peekData()
+ * @see #setData(Bundle)
*/
public Bundle getData() {
if (data == null) {
@@ -297,14 +321,21 @@ public final class Message implements Parcelable {
/**
* Like getData(), but does not lazily create the Bundle. A null
- * is returned if the Bundle does not already exist.
+ * is returned if the Bundle does not already exist. See
+ * {@link #getData} for further information on this.
+ * @see #getData()
+ * @see #setData(Bundle)
*/
public Bundle peekData() {
return data;
}
- /** Sets a Bundle of arbitrary data values. Use arg1 and arg1 members
- * as a lower cost way to send a few simple integer values, if you can. */
+ /**
+ * Sets a Bundle of arbitrary data values. Use arg1 and arg1 members
+ * as a lower cost way to send a few simple integer values, if you can.
+ * @see #getData()
+ * @see #peekData()
+ */
public void setData(Bundle data) {
this.data = data;
}
@@ -381,13 +412,25 @@ public final class Message implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
- if (obj != null || callback != null) {
+ if (callback != null) {
throw new RuntimeException(
- "Can't marshal objects across processes.");
+ "Can't marshal callbacks across processes.");
}
dest.writeInt(what);
dest.writeInt(arg1);
dest.writeInt(arg2);
+ if (obj != null) {
+ try {
+ Parcelable p = (Parcelable)obj;
+ dest.writeInt(1);
+ dest.writeParcelable(p, flags);
+ } catch (ClassCastException e) {
+ throw new RuntimeException(
+ "Can't marshal non-Parcelable objects across processes.");
+ }
+ } else {
+ dest.writeInt(0);
+ }
dest.writeLong(when);
dest.writeBundle(data);
Messenger.writeMessengerOrNullToParcel(replyTo, dest);
@@ -397,6 +440,9 @@ public final class Message implements Parcelable {
what = source.readInt();
arg1 = source.readInt();
arg2 = source.readInt();
+ if (source.readInt() != 0) {
+ obj = source.readParcelable(getClass().getClassLoader());
+ }
when = source.readLong();
data = source.readBundle();
replyTo = Messenger.readMessengerOrNullFromParcel(source);
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index caf0923..bc653d6 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -115,9 +115,7 @@ public class MessageQueue {
didIdle = true;
keep = ((IdleHandler)idler).queueIdle();
} catch (Throwable t) {
- Log.e("MessageQueue",
- "IdleHandler threw exception", t);
- RuntimeInit.crash("MessageQueue", t);
+ Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}
if (!keep) {
diff --git a/core/java/android/os/Messenger.java b/core/java/android/os/Messenger.java
index 1bc554e..ad55abdd 100644
--- a/core/java/android/os/Messenger.java
+++ b/core/java/android/os/Messenger.java
@@ -29,7 +29,7 @@ public final class Messenger implements Parcelable {
* Create a new Messenger pointing to the given Handler. Any Message
* objects sent through this Messenger will appear in the Handler as if
* {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
- * be called directly.
+ * been called directly.
*
* @param target The Handler that will receive sent messages.
*/
diff --git a/core/java/android/os/NetStat.java b/core/java/android/os/NetStat.java
deleted file mode 100644
index e294cdf..0000000
--- a/core/java/android/os/NetStat.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.util.Log;
-
-import java.io.File;
-import java.io.RandomAccessFile;
-import java.io.IOException;
-
-/** @hide */
-public class NetStat {
-
- // Logging tag.
- private final static String TAG = "netstat";
-
- // We pre-create all the File objects so we don't spend a lot of
- // CPU at runtime converting from Java Strings to byte[] for the
- // kernel calls.
- private final static File[] MOBILE_TX_PACKETS = mobileFiles("tx_packets");
- private final static File[] MOBILE_RX_PACKETS = mobileFiles("rx_packets");
- private final static File[] MOBILE_TX_BYTES = mobileFiles("tx_bytes");
- private final static File[] MOBILE_RX_BYTES = mobileFiles("rx_bytes");
- private final static File SYS_CLASS_NET_DIR = new File("/sys/class/net");
-
- /**
- * Get total number of tx packets sent through rmnet0 or ppp0
- *
- * @return number of Tx packets through rmnet0 or ppp0
- */
- public static long getMobileTxPkts() {
- return getMobileStat(MOBILE_TX_PACKETS);
- }
-
- /**
- * Get total number of rx packets received through rmnet0 or ppp0
- *
- * @return number of Rx packets through rmnet0 or ppp0
- */
- public static long getMobileRxPkts() {
- return getMobileStat(MOBILE_RX_PACKETS);
- }
-
- /**
- * Get total number of tx bytes received through rmnet0 or ppp0
- *
- * @return number of Tx bytes through rmnet0 or ppp0
- */
- public static long getMobileTxBytes() {
- return getMobileStat(MOBILE_TX_BYTES);
- }
-
- /**
- * Get total number of rx bytes received through rmnet0 or ppp0
- *
- * @return number of Rx bytes through rmnet0 or ppp0
- */
- public static long getMobileRxBytes() {
- return getMobileStat(MOBILE_RX_BYTES);
- }
-
- /**
- * Get the total number of packets sent through all network interfaces.
- *
- * @return the number of packets sent through all network interfaces
- */
- public static long getTotalTxPkts() {
- return getTotalStat("tx_packets");
- }
-
- /**
- * Get the total number of packets received through all network interfaces.
- *
- * @return the number of packets received through all network interfaces
- */
- public static long getTotalRxPkts() {
- return getTotalStat("rx_packets");
- }
-
- /**
- * Get the total number of bytes sent through all network interfaces.
- *
- * @return the number of bytes sent through all network interfaces
- */
- public static long getTotalTxBytes() {
- return getTotalStat("tx_bytes");
- }
-
- /**
- * Get the total number of bytes received through all network interfaces.
- *
- * @return the number of bytes received through all network interfaces
- */
- public static long getTotalRxBytes() {
- return getTotalStat("rx_bytes");
- }
-
- /**
- * Gets network bytes sent for this UID.
- * The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid
- * @return byte count
- */
- public static long getUidTxBytes(int uid) {
- return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_snd");
- }
-
- /**
- * Gets network bytes received for this UID.
- * The statistics are across all interfaces.
- * The statistics come from /proc/uid_stat.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid
- * @return byte count
- */
- public static long getUidRxBytes(int uid) {
- return getNumberFromFilePath("/proc/uid_stat/" + uid + "/tcp_rcv");
- }
-
- /**
- * Returns the array of two possible File locations for a given
- * statistic.
- */
- private static File[] mobileFiles(String whatStat) {
- // Note that we stat them at runtime to see which is
- // available, rather than here, to guard against the files
- // coming & going later as modules shut down (e.g. airplane
- // mode) and whatnot. The runtime stat() isn't expensive compared
- // to the previous charset conversion that happened before we
- // were reusing File instances.
- File[] files = new File[2];
- files[0] = new File("/sys/class/net/rmnet0/statistics/" + whatStat);
- files[1] = new File("/sys/class/net/ppp0/statistics/" + whatStat);
- return files;
- }
-
- private static long getTotalStat(String whatStat) {
- File netdir = new File("/sys/class/net");
-
- File[] nets = SYS_CLASS_NET_DIR.listFiles();
- if (nets == null) {
- return 0;
- }
- long total = 0;
- StringBuffer strbuf = new StringBuffer();
- for (File net : nets) {
- strbuf.append(net.getPath()).append(File.separator).append("statistics")
- .append(File.separator).append(whatStat);
- total += getNumberFromFilePath(strbuf.toString());
- strbuf.setLength(0);
- }
- return total;
- }
-
- private static long getMobileStat(File[] files) {
- for (int i = 0; i < files.length; i++) {
- File file = files[i];
- if (!file.exists()) {
- continue;
- }
- try {
- RandomAccessFile raf = new RandomAccessFile(file, "r");
- return getNumberFromFile(raf, file.getAbsolutePath());
- } catch (IOException e) {
- Log.w(TAG,
- "Exception opening TCP statistics file " + file.getAbsolutePath(),
- e);
- }
- }
- return 0L;
- }
-
- // File will have format <number><newline>
- private static long getNumberFromFilePath(String filename) {
- RandomAccessFile raf = getFile(filename);
- if (raf == null) {
- return 0L;
- }
- return getNumberFromFile(raf, filename);
- }
-
- // Private buffer for getNumberFromFile. Safe for re-use because
- // getNumberFromFile is synchronized.
- private final static byte[] buf = new byte[16];
-
- private static synchronized long getNumberFromFile(RandomAccessFile raf, String filename) {
- try {
- raf.read(buf);
- raf.close();
- } catch (IOException e) {
- Log.w(TAG, "Exception getting TCP bytes from " + filename, e);
- return 0L;
- } finally {
- if (raf != null) {
- try {
- raf.close();
- } catch (IOException e) {
- Log.w(TAG, "Exception closing " + filename, e);
- }
- }
- }
-
- long num = 0L;
- for (int i = 0; i < buf.length; i++) {
- if (buf[i] < '0' || buf[i] > '9') {
- break;
- }
- num *= 10;
- num += buf[i] - '0';
- }
- return num;
- }
-
- private static RandomAccessFile getFile(String filename) {
- File f = new File(filename);
- if (!f.canRead()) {
- return null;
- }
-
- try {
- return new RandomAccessFile(f, "r");
- } catch (IOException e) {
- Log.w(TAG, "Exception opening TCP statistics file " + filename, e);
- return null;
- }
- }
-}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6cfccee..8ad600c 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -212,6 +212,7 @@ public final class Parcel {
private static final int VAL_SERIALIZABLE = 21;
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
+ private static final int VAL_CHARSEQUENCEARRAY = 24;
private static final int EX_SECURITY = -1;
private static final int EX_BAD_PARCELABLE = -2;
@@ -411,6 +412,15 @@ public final class Parcel {
public final native void writeString(String val);
/**
+ * Write a CharSequence value into the parcel at the current dataPosition(),
+ * growing dataCapacity() if needed.
+ * @hide
+ */
+ public final void writeCharSequence(CharSequence val) {
+ TextUtils.writeToParcel(val, this, 0);
+ }
+
+ /**
* Write an object into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
*/
@@ -827,6 +837,21 @@ public final class Parcel {
}
}
+ /**
+ * @hide
+ */
+ public final void writeCharSequenceArray(CharSequence[] val) {
+ if (val != null) {
+ int N = val.length;
+ writeInt(N);
+ for (int i=0; i<N; i++) {
+ writeCharSequence(val[i]);
+ }
+ } else {
+ writeInt(-1);
+ }
+ }
+
public final IBinder[] createBinderArray() {
int N = readInt();
if (N >= 0) {
@@ -1045,7 +1070,7 @@ public final class Parcel {
} else if (v instanceof CharSequence) {
// Must be after String
writeInt(VAL_CHARSEQUENCE);
- TextUtils.writeToParcel((CharSequence) v, this, 0);
+ writeCharSequence((CharSequence) v);
} else if (v instanceof List) {
writeInt(VAL_LIST);
writeList((List) v);
@@ -1061,6 +1086,10 @@ public final class Parcel {
} else if (v instanceof String[]) {
writeInt(VAL_STRINGARRAY);
writeStringArray((String[]) v);
+ } else if (v instanceof CharSequence[]) {
+ // Must be after String[] and before Object[]
+ writeInt(VAL_CHARSEQUENCEARRAY);
+ writeCharSequenceArray((CharSequence[]) v);
} else if (v instanceof IBinder) {
writeInt(VAL_IBINDER);
writeStrongBinder((IBinder) v);
@@ -1257,6 +1286,14 @@ public final class Parcel {
public final native String readString();
/**
+ * Read a CharSequence value from the parcel at the current dataPosition().
+ * @hide
+ */
+ public final CharSequence readCharSequence() {
+ return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this);
+ }
+
+ /**
* Read an object from the parcel at the current dataPosition().
*/
public final native IBinder readStrongBinder();
@@ -1389,6 +1426,27 @@ public final class Parcel {
}
/**
+ * Read and return a CharSequence[] object from the parcel.
+ * {@hide}
+ */
+ public final CharSequence[] readCharSequenceArray() {
+ CharSequence[] array = null;
+
+ int length = readInt();
+ if (length >= 0)
+ {
+ array = new CharSequence[length];
+
+ for (int i = 0 ; i < length ; i++)
+ {
+ array[i] = readCharSequence();
+ }
+ }
+
+ return array;
+ }
+
+ /**
* Read and return a new ArrayList object from the parcel at the current
* dataPosition(). Returns null if the previously written list object was
* null. The given class loader will be used to load any enclosed
@@ -1728,7 +1786,7 @@ public final class Parcel {
return readInt() == 1;
case VAL_CHARSEQUENCE:
- return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this);
+ return readCharSequence();
case VAL_LIST:
return readArrayList(loader);
@@ -1742,6 +1800,9 @@ public final class Parcel {
case VAL_STRINGARRAY:
return readStringArray();
+ case VAL_CHARSEQUENCEARRAY:
+ return readCharSequenceArray();
+
case VAL_IBINDER:
return readStrongBinder();
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 3fcb18e..0a3b2cf 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -113,7 +113,7 @@ public class ParcelFileDescriptor implements Parcelable {
}
FileDescriptor fd = Parcel.openFileDescriptor(path, mode);
- return new ParcelFileDescriptor(fd);
+ return fd != null ? new ParcelFileDescriptor(fd) : null;
}
/**
@@ -127,7 +127,7 @@ public class ParcelFileDescriptor implements Parcelable {
*/
public static ParcelFileDescriptor fromSocket(Socket socket) {
FileDescriptor fd = getFileDescriptorFromSocket(socket);
- return new ParcelFileDescriptor(fd);
+ return fd != null ? new ParcelFileDescriptor(fd) : null;
}
// Extracts the file descriptor from the specified socket and returns it untouched
@@ -163,7 +163,10 @@ public class ParcelFileDescriptor implements Parcelable {
* If an error occurs attempting to close this ParcelFileDescriptor.
*/
public void close() throws IOException {
- mClosed = true;
+ synchronized (this) {
+ if (mClosed) return;
+ mClosed = true;
+ }
if (mParcelDescriptor != null) {
// If this is a proxy to another file descriptor, just call through to its
// close method.
@@ -235,6 +238,9 @@ public class ParcelFileDescriptor implements Parcelable {
/*package */ParcelFileDescriptor(FileDescriptor descriptor) {
super();
+ if (descriptor == null) {
+ throw new NullPointerException("descriptor must not be null");
+ }
mFileDescriptor = descriptor;
mParcelDescriptor = null;
}
diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java
index bc76180..5a79215 100644
--- a/core/java/android/os/Power.java
+++ b/core/java/android/os/Power.java
@@ -18,7 +18,6 @@ package android.os;
import java.io.IOException;
import android.os.ServiceManager;
-import android.os.IMountService;
/**
* Class that provides access to some of the power management functions.
@@ -101,15 +100,6 @@ public class Power
*/
public static void reboot(String reason) throws IOException
{
- IMountService mSvc = IMountService.Stub.asInterface(
- ServiceManager.getService("mount"));
-
- if (mSvc != null) {
- try {
- mSvc.shutdown();
- } catch (Exception e) {
- }
- }
rebootNative(reason);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 4b3b6f6..f4ca8bc 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -326,12 +326,11 @@ public class PowerManager
{
synchronized (mToken) {
if (mHeld) {
+ Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
try {
mService.releaseWakeLock(mToken, 0);
} catch (RemoteException e) {
}
- RuntimeInit.crash(TAG, new Exception(
- "WakeLock finalized while still held: "+mTag));
}
}
}
@@ -362,6 +361,9 @@ public class PowerManager
*/
public WakeLock newWakeLock(int flags, String tag)
{
+ if (tag == null) {
+ throw new NullPointerException("tag is null in PowerManager.newWakeLock");
+ }
return new WakeLock(flags, tag);
}
@@ -465,6 +467,22 @@ public class PowerManager
}
}
+ /**
+ * Reboot the device. Will not return if the reboot is
+ * successful. Requires the {@link android.Manifest.permission#REBOOT}
+ * permission.
+ *
+ * @param reason code to pass to the kernel (e.g., "recovery") to
+ * request special boot modes, or null.
+ */
+ public void reboot(String reason)
+ {
+ try {
+ mService.reboot(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
private PowerManager()
{
}
@@ -488,4 +506,3 @@ public class PowerManager
IPowerManager mService;
Handler mHandler;
}
-
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 699ddb2..5640a06 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -504,6 +504,9 @@ public class Process {
argsForZygote.add("--runtime-init");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
+ if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
+ argsForZygote.add("--enable-safemode");
+ }
if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
argsForZygote.add("--enable-debugger");
}
@@ -744,6 +747,24 @@ public class Process {
*/
public static final native void sendSignal(int pid, int signal);
+ /**
+ * @hide
+ * Private impl for avoiding a log message... DO NOT USE without doing
+ * your own log, or the Android Illuminati will find you some night and
+ * beat you up.
+ */
+ public static final void killProcessQuiet(int pid) {
+ sendSignalQuiet(pid, SIGNAL_KILL);
+ }
+
+ /**
+ * @hide
+ * Private impl for avoiding a log message... DO NOT USE without doing
+ * your own log, or the Android Illuminati will find you some night and
+ * beat you up.
+ */
+ public static final native void sendSignalQuiet(int pid, int signal);
+
/** @hide */
public static final native long getFreeMemory();
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
new file mode 100644
index 0000000..b6dc1b5
--- /dev/null
+++ b/core/java/android/os/RecoverySystem.java
@@ -0,0 +1,407 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.apache.harmony.security.asn1.BerInputStream;
+import org.apache.harmony.security.pkcs7.ContentInfo;
+import org.apache.harmony.security.pkcs7.SignedData;
+import org.apache.harmony.security.pkcs7.SignerInfo;
+import org.apache.harmony.security.provider.cert.X509CertImpl;
+
+/**
+ * RecoverySystem contains methods for interacting with the Android
+ * recovery system (the separate partition that can be used to install
+ * system updates, wipe user data, etc.)
+ */
+public class RecoverySystem {
+ private static final String TAG = "RecoverySystem";
+
+ /**
+ * Default location of zip file containing public keys (X509
+ * certs) authorized to sign OTA updates.
+ */
+ private static final File DEFAULT_KEYSTORE =
+ new File("/system/etc/security/otacerts.zip");
+
+ /** Send progress to listeners no more often than this (in ms). */
+ private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
+
+ /** Used to communicate with recovery. See bootable/recovery/recovery.c. */
+ 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");
+
+ // Length limits for reading files.
+ private static int LOG_FILE_MAX_LENGTH = 64 * 1024;
+
+ /**
+ * Interface definition for a callback to be invoked regularly as
+ * verification proceeds.
+ */
+ public interface ProgressListener {
+ /**
+ * Called periodically as the verification progresses.
+ *
+ * @param progress the approximate percentage of the
+ * verification that has been completed, ranging from 0
+ * to 100 (inclusive).
+ */
+ public void onProgress(int progress);
+ }
+
+ /** @return the set of certs that can be used to sign an OTA package. */
+ private static HashSet<Certificate> getTrustedCerts(File keystore)
+ throws IOException, GeneralSecurityException {
+ HashSet<Certificate> trusted = new HashSet<Certificate>();
+ if (keystore == null) {
+ keystore = DEFAULT_KEYSTORE;
+ }
+ ZipFile zip = new ZipFile(keystore);
+ try {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ Enumeration<? extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ trusted.add(cf.generateCertificate(zip.getInputStream(entry)));
+ }
+ } finally {
+ zip.close();
+ }
+ return trusted;
+ }
+
+ /**
+ * Verify the cryptographic signature of a system update package
+ * before installing it. Note that the package is also verified
+ * separately by the installer once the device is rebooted into
+ * the recovery system. This function will return only if the
+ * package was successfully verified; otherwise it will throw an
+ * exception.
+ *
+ * Verification of a package can take significant time, so this
+ * function should not be called from a UI thread. Interrupting
+ * the thread while this function is in progress will result in a
+ * SecurityException being thrown (and the thread's interrupt flag
+ * will be cleared).
+ *
+ * @param packageFile the package to be verified
+ * @param listener an object to receive periodic progress
+ * updates as verification proceeds. May be null.
+ * @param deviceCertsZipFile the zip file of certificates whose
+ * public keys we will accept. Verification succeeds if the
+ * package is signed by the private key corresponding to any
+ * public key in this file. May be null to use the system default
+ * file (currently "/system/etc/security/otacerts.zip").
+ *
+ * @throws IOException if there were any errors reading the
+ * package or certs files.
+ * @throws GeneralSecurityException if verification failed
+ */
+ public static void verifyPackage(File packageFile,
+ ProgressListener listener,
+ File deviceCertsZipFile)
+ throws IOException, GeneralSecurityException {
+ long fileLen = packageFile.length();
+
+ RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
+ try {
+ int lastPercent = 0;
+ long lastPublishTime = System.currentTimeMillis();
+ if (listener != null) {
+ listener.onProgress(lastPercent);
+ }
+
+ raf.seek(fileLen - 6);
+ byte[] footer = new byte[6];
+ raf.readFully(footer);
+
+ if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
+ throw new SignatureException("no signature in file (no footer)");
+ }
+
+ int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
+ int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
+ Log.v(TAG, String.format("comment size %d; signature start %d",
+ commentSize, signatureStart));
+
+ byte[] eocd = new byte[commentSize + 22];
+ raf.seek(fileLen - (commentSize + 22));
+ raf.readFully(eocd);
+
+ // Check that we have found the start of the
+ // end-of-central-directory record.
+ if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
+ eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
+ throw new SignatureException("no signature in file (bad footer)");
+ }
+
+ for (int i = 4; i < eocd.length-3; ++i) {
+ if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
+ eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
+ throw new SignatureException("EOCD marker found after start of EOCD");
+ }
+ }
+
+ // The following code is largely copied from
+ // JarUtils.verifySignature(). We could just *call* that
+ // method here if that function didn't read the entire
+ // input (ie, the whole OTA package) into memory just to
+ // compute its message digest.
+
+ BerInputStream bis = new BerInputStream(
+ new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
+ ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
+ SignedData signedData = info.getSignedData();
+ if (signedData == null) {
+ throw new IOException("signedData is null");
+ }
+ Collection encCerts = signedData.getCertificates();
+ if (encCerts.isEmpty()) {
+ throw new IOException("encCerts is empty");
+ }
+ // Take the first certificate from the signature (packages
+ // should contain only one).
+ Iterator it = encCerts.iterator();
+ X509Certificate cert = null;
+ if (it.hasNext()) {
+ cert = new X509CertImpl((org.apache.harmony.security.x509.Certificate)it.next());
+ } else {
+ throw new SignatureException("signature contains no certificates");
+ }
+
+ List sigInfos = signedData.getSignerInfos();
+ SignerInfo sigInfo;
+ if (!sigInfos.isEmpty()) {
+ sigInfo = (SignerInfo)sigInfos.get(0);
+ } else {
+ throw new IOException("no signer infos!");
+ }
+
+ // Check that the public key of the certificate contained
+ // in the package equals one of our trusted public keys.
+
+ HashSet<Certificate> trusted = getTrustedCerts(
+ deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
+
+ PublicKey signatureKey = cert.getPublicKey();
+ boolean verified = false;
+ for (Certificate c : trusted) {
+ if (c.getPublicKey().equals(signatureKey)) {
+ verified = true;
+ break;
+ }
+ }
+ if (!verified) {
+ throw new SignatureException("signature doesn't match any trusted key");
+ }
+
+ // The signature cert matches a trusted key. Now verify that
+ // the digest in the cert matches the actual file data.
+
+ // The verifier in recovery *only* handles SHA1withRSA
+ // signatures. SignApk.java always uses SHA1withRSA, no
+ // matter what the cert says to use. Ignore
+ // cert.getSigAlgName(), and instead use whatever
+ // algorithm is used by the signature (which should be
+ // SHA1withRSA).
+
+ String da = sigInfo.getdigestAlgorithm();
+ String dea = sigInfo.getDigestEncryptionAlgorithm();
+ String alg = null;
+ if (da == null || dea == null) {
+ // fall back to the cert algorithm if the sig one
+ // doesn't look right.
+ alg = cert.getSigAlgName();
+ } else {
+ alg = da + "with" + dea;
+ }
+ Signature sig = Signature.getInstance(alg);
+ sig.initVerify(cert);
+
+ // The signature covers all of the OTA package except the
+ // archive comment and its 2-byte length.
+ long toRead = fileLen - commentSize - 2;
+ long soFar = 0;
+ raf.seek(0);
+ byte[] buffer = new byte[4096];
+ boolean interrupted = false;
+ while (soFar < toRead) {
+ interrupted = Thread.interrupted();
+ if (interrupted) break;
+ int size = buffer.length;
+ if (soFar + size > toRead) {
+ size = (int)(toRead - soFar);
+ }
+ int read = raf.read(buffer, 0, size);
+ sig.update(buffer, 0, read);
+ soFar += read;
+
+ if (listener != null) {
+ long now = System.currentTimeMillis();
+ int p = (int)(soFar * 100 / toRead);
+ if (p > lastPercent &&
+ now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
+ lastPercent = p;
+ lastPublishTime = now;
+ listener.onProgress(lastPercent);
+ }
+ }
+ }
+ if (listener != null) {
+ listener.onProgress(100);
+ }
+
+ if (interrupted) {
+ throw new SignatureException("verification was interrupted");
+ }
+
+ if (!sig.verify(sigInfo.getEncryptedDigest())) {
+ throw new SignatureException("signature digest verification failed");
+ }
+ } finally {
+ raf.close();
+ }
+ }
+
+ /**
+ * Reboots the device in order to install the given update
+ * package.
+ * Requires the {@link android.Manifest.permission#REBOOT} permission.
+ *
+ * @param context the Context to use
+ * @param packageFile the update package to install. Currently
+ * must be on the /cache or /data partitions.
+ *
+ * @throws IOException if writing the recovery command file
+ * fails, or if the reboot itself fails.
+ */
+ public static void installPackage(Context context, File packageFile)
+ throws IOException {
+ String filename = packageFile.getCanonicalPath();
+
+ if (filename.startsWith("/cache/")) {
+ filename = "CACHE:" + filename.substring(7);
+ } else if (filename.startsWith("/data/")) {
+ filename = "DATA:" + filename.substring(6);
+ } else {
+ throw new IllegalArgumentException(
+ "Must start with /cache or /data: " + filename);
+ }
+ Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
+ String arg = "--update_package=" + filename;
+ bootCommand(context, arg);
+ }
+
+ /**
+ * Reboots the device and wipes the user data partition. This is
+ * sometimes called a "factory reset", which is something of a
+ * misnomer because the system partition is not restored to its
+ * factory state.
+ * Requires the {@link android.Manifest.permission#REBOOT} permission.
+ *
+ * @param context the Context to use
+ *
+ * @throws IOException if writing the recovery command file
+ * fails, or if the reboot itself fails.
+ */
+ public static void rebootWipeUserData(Context context)
+ throws IOException {
+ bootCommand(context, "--wipe_data");
+ }
+
+ /**
+ * Reboot into the recovery system with the supplied argument.
+ * @param arg to pass to the recovery utility.
+ * @throws IOException if something goes wrong.
+ */
+ private static void bootCommand(Context context, String arg) throws IOException {
+ RECOVERY_DIR.mkdirs(); // In case we need it
+ COMMAND_FILE.delete(); // In case it's not writable
+ LOG_FILE.delete();
+
+ FileWriter command = new FileWriter(COMMAND_FILE);
+ try {
+ command.write(arg);
+ command.write("\n");
+ } finally {
+ command.close();
+ }
+
+ // Having written the command file, go ahead and reboot
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ pm.reboot("recovery");
+
+ throw new IOException("Reboot failed (no permissions?)");
+ }
+
+ /**
+ * Called after booting to process and remove recovery-related files.
+ * @return the log file from recovery, or null if none was found.
+ *
+ * @hide
+ */
+ public static String handleAftermath() {
+ // Record the tail of the LOG_FILE
+ String log = null;
+ try {
+ log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "No recovery log file");
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading recovery log", e);
+ }
+
+ // Delete everything in RECOVERY_DIR
+ String[] names = RECOVERY_DIR.list();
+ for (int i = 0; names != null && i < names.length; i++) {
+ File f = new File(RECOVERY_DIR, names[i]);
+ if (!f.delete()) {
+ Log.e(TAG, "Can't delete: " + f);
+ } else {
+ Log.i(TAG, "Deleted: " + f);
+ }
+ }
+
+ return log;
+ }
+
+ private void RecoverySystem() { } // Do not instantiate
+}
diff --git a/core/java/android/os/RemoteCallback.aidl b/core/java/android/os/RemoteCallback.aidl
new file mode 100644
index 0000000..7ae56f5
--- /dev/null
+++ b/core/java/android/os/RemoteCallback.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 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.os;
+
+parcelable RemoteCallback;
diff --git a/core/java/android/os/RemoteCallback.java b/core/java/android/os/RemoteCallback.java
new file mode 100644
index 0000000..ca95bdf
--- /dev/null
+++ b/core/java/android/os/RemoteCallback.java
@@ -0,0 +1,107 @@
+/*
+ * 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.os;
+
+/**
+ * TODO: Make this a public API? Let's see how it goes with a few use
+ * cases first.
+ * @hide
+ */
+public abstract class RemoteCallback implements Parcelable {
+ final Handler mHandler;
+ final IRemoteCallback mTarget;
+
+ class DeliverResult implements Runnable {
+ final Bundle mResult;
+
+ DeliverResult(Bundle result) {
+ mResult = result;
+ }
+
+ public void run() {
+ onResult(mResult);
+ }
+ }
+
+ class LocalCallback extends IRemoteCallback.Stub {
+ public void sendResult(Bundle bundle) {
+ mHandler.post(new DeliverResult(bundle));
+ }
+ }
+
+ static class RemoteCallbackProxy extends RemoteCallback {
+ RemoteCallbackProxy(IRemoteCallback target) {
+ super(target);
+ }
+
+ protected void onResult(Bundle bundle) {
+ }
+ }
+
+ public RemoteCallback(Handler handler) {
+ mHandler = handler;
+ mTarget = new LocalCallback();
+ }
+
+ RemoteCallback(IRemoteCallback target) {
+ mHandler = null;
+ mTarget = target;
+ }
+
+ public void sendResult(Bundle bundle) throws RemoteException {
+ mTarget.sendResult(bundle);
+ }
+
+ protected abstract void onResult(Bundle bundle);
+
+ public boolean equals(Object otherObj) {
+ if (otherObj == null) {
+ return false;
+ }
+ try {
+ return mTarget.asBinder().equals(((RemoteCallback)otherObj)
+ .mTarget.asBinder());
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return mTarget.asBinder().hashCode();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mTarget.asBinder());
+ }
+
+ public static final Parcelable.Creator<RemoteCallback> CREATOR
+ = new Parcelable.Creator<RemoteCallback>() {
+ public RemoteCallback createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new RemoteCallbackProxy(
+ IRemoteCallback.Stub.asInterface(target)) : null;
+ }
+
+ public RemoteCallback[] newArray(int size) {
+ return new RemoteCallback[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 51dcff1..1895cf8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -23,14 +23,14 @@ package android.os;
*/
public class Vibrator
{
- IHardwareService mService;
+ IVibratorService mService;
private final Binder mToken = new Binder();
/** @hide */
public Vibrator()
{
- mService = IHardwareService.Stub.asInterface(
- ServiceManager.getService("hardware"));
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
}
/**
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
new file mode 100644
index 0000000..4862f80
--- /dev/null
+++ b/core/java/android/os/storage/IMountService.aidl
@@ -0,0 +1,155 @@
+/* //device/java/android/android/os/IUsb.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os.storage;
+
+import android.os.storage.IMountServiceListener;
+import android.os.storage.IMountShutdownObserver;
+
+/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
+ * In particular, the ordering of the methods below must match the
+ * _TRANSACTION enum in IMountService.cpp
+ * @hide - Applications should use android.os.storage.StorageManager to access
+ * storage functions.
+ */
+interface IMountService
+{
+ /**
+ * Registers an IMountServiceListener for receiving async
+ * notifications.
+ */
+ void registerListener(IMountServiceListener listener);
+
+ /**
+ * Unregisters an IMountServiceListener
+ */
+ void unregisterListener(IMountServiceListener listener);
+
+ /**
+ * Returns true if a USB mass storage host is connected
+ */
+ boolean isUsbMassStorageConnected();
+
+ /**
+ * Enables / disables USB mass storage.
+ * The caller should check actual status of enabling/disabling
+ * USB mass storage via StorageEventListener.
+ */
+ void setUsbMassStorageEnabled(boolean enable);
+
+ /**
+ * Returns true if a USB mass storage host is enabled (media is shared)
+ */
+ boolean isUsbMassStorageEnabled();
+
+ /**
+ * Mount external storage at given mount point.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int mountVolume(String mountPoint);
+
+ /**
+ * Safely unmount external storage at given mount point.
+ * The unmount is an asynchronous operation. Applications
+ * should register StorageEventListener for storage related
+ * status changes.
+ *
+ */
+ void unmountVolume(String mountPoint, boolean force);
+
+ /**
+ * Format external storage given a mount point.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int formatVolume(String mountPoint);
+
+ /**
+ * Returns an array of pids with open files on
+ * the specified path.
+ */
+ int[] getStorageUsers(String path);
+
+ /**
+ * Gets the state of a volume via its mountpoint.
+ */
+ String getVolumeState(String mountPoint);
+
+ /*
+ * Creates a secure container with the specified parameters.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid);
+
+ /*
+ * Finalize a container which has just been created and populated.
+ * After finalization, the container is immutable.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int finalizeSecureContainer(String id);
+
+ /*
+ * Destroy a secure container, and free up all resources associated with it.
+ * NOTE: Ensure all references are released prior to deleting.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int destroySecureContainer(String id, boolean force);
+
+ /*
+ * Mount a secure container with the specified key and owner UID.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int mountSecureContainer(String id, String key, int ownerUid);
+
+ /*
+ * Unount a secure container.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int unmountSecureContainer(String id, boolean force);
+
+ /*
+ * Returns true if the specified container is mounted
+ */
+ boolean isSecureContainerMounted(String id);
+
+ /*
+ * Rename an unmounted secure container.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ int renameSecureContainer(String oldId, String newId);
+
+ /*
+ * Returns the filesystem path of a mounted secure container.
+ */
+ String getSecureContainerPath(String id);
+
+ /**
+ * Gets an Array of currently known secure container IDs
+ */
+ String[] getSecureContainerList();
+
+ /**
+ * Shuts down the MountService and gracefully unmounts all external media.
+ * Invokes call back once the shutdown is complete.
+ */
+ void shutdown(IMountShutdownObserver observer);
+
+ /**
+ * Call into MountService by PackageManager to notify that its done
+ * processing the media status update request.
+ */
+ void finishMediaUpdate();
+}
diff --git a/core/java/android/os/storage/IMountServiceListener.aidl b/core/java/android/os/storage/IMountServiceListener.aidl
new file mode 100644
index 0000000..883413a
--- /dev/null
+++ b/core/java/android/os/storage/IMountServiceListener.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Callback class for receiving events from MountService.
+ *
+ * @hide - Applications should use android.os.storage.IStorageEventListener
+ * for storage event callbacks.
+ */
+interface IMountServiceListener {
+ /**
+ * Detection state of USB Mass Storage has changed
+ *
+ * @param available true if a UMS host is connected.
+ */
+ void onUsbMassStorageConnectionChanged(boolean connected);
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume.
+ *
+ * Note: State is one of the values returned by Environment.getExternalStorageState()
+ */
+ void onStorageStateChanged(String path, String oldState, String newState);
+}
diff --git a/core/java/android/os/storage/IMountShutdownObserver.aidl b/core/java/android/os/storage/IMountShutdownObserver.aidl
new file mode 100644
index 0000000..0aa8a45
--- /dev/null
+++ b/core/java/android/os/storage/IMountShutdownObserver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Callback class for receiving events related
+ * to shutdown.
+ *
+ * @hide - For internal consumption only.
+ */
+interface IMountShutdownObserver {
+ /**
+ * This method is called when the shutdown
+ * of MountService completed.
+ * @param statusCode indicates success or failure
+ * of the shutdown.
+ */
+ void onShutDownComplete(int statusCode);
+}
diff --git a/core/java/android/os/storage/MountServiceListener.java b/core/java/android/os/storage/MountServiceListener.java
new file mode 100644
index 0000000..bebb3f6
--- /dev/null
+++ b/core/java/android/os/storage/MountServiceListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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.os.storage;
+
+/**
+ * Callback class for receiving progress reports during a restore operation. These
+ * methods will all be called on your application's main thread.
+ * @hide
+ */
+public abstract class MountServiceListener {
+ /**
+ * USB Mass storage connection state has changed.
+ *
+ * @param connected True if UMS is connected.
+ */
+ void onUsbMassStorageConnectionChanged(boolean connected) {
+ }
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume.
+ *
+ * @Note: State is one of the values returned by Environment.getExternalStorageState()
+ */
+ void onStorageStateChange(String path, String oldState, String newState) {
+ }
+}
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
new file mode 100644
index 0000000..7b883a7
--- /dev/null
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Used for receiving notifications from the StorageManager
+ * @hide
+ */
+public abstract class StorageEventListener {
+ /**
+ * Called when the detection state of a USB Mass Storage host has changed.
+ * @param connected true if the USB mass storage is connected.
+ */
+ public void onUsbMassStorageConnectionChanged(boolean connected) {
+ }
+
+ /**
+ * Called when storage has changed state
+ * @param path the filesystem path for the storage
+ * @param oldState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+ * @param newState the old state as returned by {@link android.os.Environment#getExternalStorageState()}.
+ */
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ }
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
new file mode 100644
index 0000000..a12603c
--- /dev/null
+++ b/core/java/android/os/storage/StorageManager.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * StorageManager is the interface to the systems storage service.
+ * Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
+ * of {@link android.content.Context#STORAGE_SERVICE}.
+ *
+ * @hide
+ *
+ */
+
+public class StorageManager
+{
+ private static final String TAG = "StorageManager";
+
+ /*
+ * Our internal MountService binder reference
+ */
+ private IMountService mMountService;
+
+ /*
+ * The looper target for callbacks
+ */
+ Looper mTgtLooper;
+
+ /*
+ * Target listener for binder callbacks
+ */
+ private MountServiceBinderListener mBinderListener;
+
+ /*
+ * List of our listeners
+ */
+ private ArrayList<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
+
+ private class MountServiceBinderListener extends IMountServiceListener.Stub {
+ public void onUsbMassStorageConnectionChanged(boolean available) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).sendShareAvailabilityChanged(available);
+ }
+ }
+
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; i++) {
+ mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
+ }
+ }
+ }
+
+ /**
+ * Private base class for messages sent between the callback thread
+ * and the target looper handler.
+ */
+ private class StorageEvent {
+ public static final int EVENT_UMS_CONNECTION_CHANGED = 1;
+ public static final int EVENT_STORAGE_STATE_CHANGED = 2;
+
+ private Message mMessage;
+
+ public StorageEvent(int what) {
+ mMessage = Message.obtain();
+ mMessage.what = what;
+ mMessage.obj = this;
+ }
+
+ public Message getMessage() {
+ return mMessage;
+ }
+ }
+
+ /**
+ * Message sent on a USB mass storage connection change.
+ */
+ private class UmsConnectionChangedStorageEvent extends StorageEvent {
+ public boolean available;
+
+ public UmsConnectionChangedStorageEvent(boolean a) {
+ super(EVENT_UMS_CONNECTION_CHANGED);
+ available = a;
+ }
+ }
+
+ /**
+ * Message sent on volume state change.
+ */
+ private class StorageStateChangedStorageEvent extends StorageEvent {
+ public String path;
+ public String oldState;
+ public String newState;
+
+ public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
+ super(EVENT_STORAGE_STATE_CHANGED);
+ path = p;
+ oldState = oldS;
+ newState = newS;
+ }
+ }
+
+ /**
+ * Private class containing sender and receiver code for StorageEvents.
+ */
+ private class ListenerDelegate {
+ final StorageEventListener mStorageEventListener;
+ private final Handler mHandler;
+
+ ListenerDelegate(StorageEventListener listener) {
+ mStorageEventListener = listener;
+ mHandler = new Handler(mTgtLooper) {
+ @Override
+ public void handleMessage(Message msg) {
+ StorageEvent e = (StorageEvent) msg.obj;
+
+ if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
+ UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
+ mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
+ } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
+ StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
+ mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
+ } else {
+ Log.e(TAG, "Unsupported event " + msg.what);
+ }
+ }
+ };
+ }
+
+ StorageEventListener getListener() {
+ return mStorageEventListener;
+ }
+
+ void sendShareAvailabilityChanged(boolean available) {
+ UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
+ mHandler.sendMessage(e.getMessage());
+ }
+
+ void sendStorageStateChanged(String path, String oldState, String newState) {
+ StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
+ mHandler.sendMessage(e.getMessage());
+ }
+ }
+
+ /**
+ * Constructs a StorageManager object through which an application can
+ * can communicate with the systems mount service.
+ *
+ * @param tgtLooper The {@android.os.Looper} which events will be received on.
+ *
+ * <p>Applications can get instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
+ * of {@link android.content.Context#STORAGE_SERVICE}.
+ *
+ * @hide
+ */
+ public StorageManager(Looper tgtLooper) throws RemoteException {
+ mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+ if (mMountService == null) {
+ Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
+ return;
+ }
+ mTgtLooper = tgtLooper;
+ mBinderListener = new MountServiceBinderListener();
+ mMountService.registerListener(mBinderListener);
+ }
+
+
+ /**
+ * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
+ *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+ *
+ */
+ public void registerListener(StorageEventListener listener) {
+ if (listener == null) {
+ return;
+ }
+
+ synchronized (mListeners) {
+ mListeners.add(new ListenerDelegate(listener));
+ }
+ }
+
+ /**
+ * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
+ *
+ * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
+ *
+ */
+ public void unregisterListener(StorageEventListener listener) {
+ if (listener == null) {
+ return;
+ }
+
+ synchronized (mListeners) {
+ final int size = mListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = mListeners.get(i);
+ if (l.getListener() == listener) {
+ mListeners.remove(i);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Enables USB Mass Storage (UMS) on the device.
+ */
+ public void enableUsbMassStorage() {
+ try {
+ mMountService.setUsbMassStorageEnabled(true);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to enable UMS", ex);
+ }
+ }
+
+ /**
+ * Disables USB Mass Storage (UMS) on the device.
+ */
+ public void disableUsbMassStorage() {
+ try {
+ mMountService.setUsbMassStorageEnabled(false);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to disable UMS", ex);
+ }
+ }
+
+ /**
+ * Query if a USB Mass Storage (UMS) host is connected.
+ * @return true if UMS host is connected.
+ */
+ public boolean isUsbMassStorageConnected() {
+ try {
+ return mMountService.isUsbMassStorageConnected();
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to get UMS connection state", ex);
+ }
+ return false;
+ }
+
+ /**
+ * Query if a USB Mass Storage (UMS) is enabled on the device.
+ * @return true if UMS host is enabled.
+ */
+ public boolean isUsbMassStorageEnabled() {
+ try {
+ return mMountService.isUsbMassStorageEnabled();
+ } catch (RemoteException rex) {
+ Log.e(TAG, "Failed to get UMS enable state", rex);
+ }
+ return false;
+ }
+}
diff --git a/core/java/android/os/storage/StorageResultCode.java b/core/java/android/os/storage/StorageResultCode.java
new file mode 100644
index 0000000..075f47f
--- /dev/null
+++ b/core/java/android/os/storage/StorageResultCode.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+/**
+ * Class that provides access to constants returned from StorageManager
+ * and lower level MountService APIs.
+ *
+ * @hide
+ */
+public class StorageResultCode
+{
+ /**
+ * Operation succeeded.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationSucceeded = 0;
+
+ /**
+ * Operation failed: Internal error.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedInternalError = -1;
+
+ /**
+ * Operation failed: Missing media.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedNoMedia = -2;
+
+ /**
+ * Operation failed: Media is blank.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedMediaBlank = -3;
+
+ /**
+ * Operation failed: Media is corrupt.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedMediaCorrupt = -4;
+
+ /**
+ * Operation failed: Storage not mounted.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedStorageNotMounted = -5;
+
+ /**
+ * Operation failed: Storage is mounted.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedStorageMounted = -6;
+
+ /**
+ * Operation failed: Storage is busy.
+ * @see android.os.storage.StorageManager
+ */
+ public static final int OperationFailedStorageBusy = -7;
+
+}
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
index 3ea9b4a..56c4f7a 100644
--- a/core/java/android/pim/EventRecurrence.java
+++ b/core/java/android/pim/EventRecurrence.java
@@ -16,7 +16,6 @@
package android.pim;
-import android.content.res.Resources;
import android.text.TextUtils;
import android.text.format.Time;
@@ -325,55 +324,6 @@ public class EventRecurrence
return s.toString();
}
- public String getRepeatString() {
- Resources r = Resources.getSystem();
-
- // TODO Implement "Until" portion of string, as well as custom settings
- switch (this.freq) {
- case DAILY:
- return r.getString(com.android.internal.R.string.daily);
- case WEEKLY: {
- if (repeatsOnEveryWeekDay()) {
- return r.getString(com.android.internal.R.string.every_weekday);
- } else {
- String format = r.getString(com.android.internal.R.string.weekly);
- StringBuilder days = new StringBuilder();
-
- // Do one less iteration in the loop so the last element is added out of the
- // loop. This is done so the comma is not placed after the last item.
- int count = this.bydayCount - 1;
- if (count >= 0) {
- for (int i = 0 ; i < count ; i++) {
- days.append(dayToString(r, this.byday[i]));
- days.append(",");
- }
- days.append(dayToString(r, this.byday[count]));
-
- return String.format(format, days.toString());
- }
-
- // There is no "BYDAY" specifier, so use the day of the
- // first event. For this to work, the setStartDate()
- // method must have been used by the caller to set the
- // date of the first event in the recurrence.
- if (startDate == null) {
- return null;
- }
-
- int day = timeDay2Day(startDate.weekDay);
- return String.format(format, dayToString(r, day));
- }
- }
- case MONTHLY: {
- return r.getString(com.android.internal.R.string.monthly);
- }
- case YEARLY:
- return r.getString(com.android.internal.R.string.yearly);
- }
-
- return null;
- }
-
public boolean repeatsOnEveryWeekDay() {
if (this.freq != WEEKLY) {
return false;
@@ -405,17 +355,4 @@ public class EventRecurrence
return true;
}
-
- private String dayToString(Resources r, int day) {
- switch (day) {
- case SU: return r.getString(com.android.internal.R.string.day_of_week_long_sunday);
- case MO: return r.getString(com.android.internal.R.string.day_of_week_long_monday);
- case TU: return r.getString(com.android.internal.R.string.day_of_week_long_tuesday);
- case WE: return r.getString(com.android.internal.R.string.day_of_week_long_wednesday);
- case TH: return r.getString(com.android.internal.R.string.day_of_week_long_thursday);
- case FR: return r.getString(com.android.internal.R.string.day_of_week_long_friday);
- case SA: return r.getString(com.android.internal.R.string.day_of_week_long_saturday);
- default: throw new IllegalArgumentException("bad day argument: " + day);
- }
- }
}
diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java
index bd7924a..635323e 100644
--- a/core/java/android/pim/RecurrenceSet.java
+++ b/core/java/android/pim/RecurrenceSet.java
@@ -18,7 +18,6 @@ package android.pim;
import android.content.ContentValues;
import android.database.Cursor;
-import android.os.Bundle;
import android.provider.Calendar;
import android.text.TextUtils;
import android.text.format.Time;
@@ -26,6 +25,7 @@ import android.util.Config;
import android.util.Log;
import java.util.List;
+import java.util.regex.Pattern;
/**
* Basic information about a recurrence, following RFC 2445 Section 4.8.5.
@@ -36,6 +36,7 @@ public class RecurrenceSet {
private final static String TAG = "CalendarProvider";
private final static String RULE_SEPARATOR = "\n";
+ private final static String FOLDING_SEPARATOR = "\n ";
// TODO: make these final?
public EventRecurrence[] rrules = null;
@@ -48,7 +49,8 @@ public class RecurrenceSet {
* events table in the CalendarProvider.
* @param values The values retrieved from the Events table.
*/
- public RecurrenceSet(ContentValues values) {
+ public RecurrenceSet(ContentValues values)
+ throws EventRecurrence.InvalidFormatException {
String rruleStr = values.getAsString(Calendar.Events.RRULE);
String rdateStr = values.getAsString(Calendar.Events.RDATE);
String exruleStr = values.getAsString(Calendar.Events.EXRULE);
@@ -65,7 +67,8 @@ public class RecurrenceSet {
* @param cursor The cursor containing the RRULE, RDATE, EXRULE, and EXDATE
* columns.
*/
- public RecurrenceSet(Cursor cursor) {
+ public RecurrenceSet(Cursor cursor)
+ throws EventRecurrence.InvalidFormatException {
int rruleColumn = cursor.getColumnIndex(Calendar.Events.RRULE);
int rdateColumn = cursor.getColumnIndex(Calendar.Events.RDATE);
int exruleColumn = cursor.getColumnIndex(Calendar.Events.EXRULE);
@@ -78,12 +81,14 @@ public class RecurrenceSet {
}
public RecurrenceSet(String rruleStr, String rdateStr,
- String exruleStr, String exdateStr) {
+ String exruleStr, String exdateStr)
+ throws EventRecurrence.InvalidFormatException {
init(rruleStr, rdateStr, exruleStr, exdateStr);
}
private void init(String rruleStr, String rdateStr,
- String exruleStr, String exdateStr) {
+ String exruleStr, String exdateStr)
+ throws EventRecurrence.InvalidFormatException {
if (!TextUtils.isEmpty(rruleStr) || !TextUtils.isEmpty(rdateStr)) {
if (!TextUtils.isEmpty(rruleStr)) {
@@ -305,7 +310,8 @@ public static boolean populateComponent(ContentValues values,
String rdateStr = values.getAsString(Calendar.Events.RDATE);
String exruleStr = values.getAsString(Calendar.Events.EXRULE);
String exdateStr = values.getAsString(Calendar.Events.EXDATE);
- boolean allDay = values.getAsInteger(Calendar.Events.ALL_DAY) == 1;
+ Integer allDayInteger = values.getAsInteger(Calendar.Events.ALL_DAY);
+ boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false;
if ((dtstart == -1) ||
(TextUtils.isEmpty(duration))||
@@ -357,7 +363,7 @@ public static boolean populateComponent(ContentValues values,
if (TextUtils.isEmpty(ruleStr)) {
return;
}
- String[] rrules = ruleStr.split(RULE_SEPARATOR);
+ String[] rrules = getRuleStrings(ruleStr);
for (String rrule : rrules) {
ICalendar.Property prop = new ICalendar.Property(propertyName);
prop.setValue(rrule);
@@ -365,6 +371,52 @@ public static boolean populateComponent(ContentValues values,
}
}
+ private static String[] getRuleStrings(String ruleStr) {
+ if (null == ruleStr) {
+ return new String[0];
+ }
+ String unfoldedRuleStr = unfold(ruleStr);
+ String[] split = unfoldedRuleStr.split(RULE_SEPARATOR);
+ int count = split.length;
+ for (int n = 0; n < count; n++) {
+ split[n] = fold(split[n]);
+ }
+ return split;
+ }
+
+
+ private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE =
+ Pattern.compile("(?:\\r\\n?|\\n)[ \t]");
+
+ private static final Pattern FOLD_RE = Pattern.compile(".{75}");
+
+ /**
+ * fold and unfolds ical content lines as per RFC 2445 section 4.1.
+ *
+ * <h3>4.1 Content Lines</h3>
+ *
+ * <p>The iCalendar object is organized into individual lines of text, called
+ * content lines. Content lines are delimited by a line break, which is a CRLF
+ * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10).
+ *
+ * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line
+ * break. Long content lines SHOULD be split into a multiple line
+ * representations using a line "folding" technique. That is, a long line can
+ * be split between any two characters by inserting a CRLF immediately
+ * followed by a single linear white space character (i.e., SPACE, US-ASCII
+ * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed
+ * immediately by a single linear white space character is ignored (i.e.,
+ * removed) when processing the content type.
+ */
+ public static String fold(String unfoldedIcalContent) {
+ return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n ");
+ }
+
+ public static String unfold(String foldedIcalContent) {
+ return IGNORABLE_ICAL_WHITESPACE_RE.matcher(
+ foldedIcalContent).replaceAll("");
+ }
+
private static void addPropertyForDateStr(ICalendar.Component component,
String propertyName,
String dateStr) {
diff --git a/core/java/android/pim/vcard/Constants.java b/core/java/android/pim/vcard/Constants.java
deleted file mode 100644
index ca41ce5..0000000
--- a/core/java/android/pim/vcard/Constants.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.pim.vcard;
-
-/**
- * Constants used in both composer and parser.
- */
-/* package */ class Constants {
-
- public static final String ATTR_TYPE = "TYPE";
-
- public static final String VERSION_V21 = "2.1";
- public static final String VERSION_V30 = "3.0";
-
- // Properties both the current (as of 2009-08-17) ContactsStruct and de-fact vCard extensions
- // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
- public static final String PROPERTY_X_AIM = "X-AIM";
- public static final String PROPERTY_X_MSN = "X-MSN";
- public static final String PROPERTY_X_YAHOO = "X-YAHOO";
- public static final String PROPERTY_X_ICQ = "X-ICQ";
- public static final String PROPERTY_X_JABBER = "X-JABBER";
- public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
- public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
- // Phone number for Skype, available as usual phone.
- public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
- // Some device emits this "X-" attribute, which is specifically invalid but should be
- // always properly accepted, and emitted in some special case (for that device/application).
- public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
-
- // How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0
- //
- // e.g.
- // 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."
- // 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."
- // 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."
- //
- // 2) has been the default of VCard exporter/importer in Android, but we can see the other
- // formats in vCard data emitted by the other softwares/devices.
- //
- // So we are currently not sure which type is the best; probably we will have to change which
- // type should be emitted depending on the device.
- public static final String ATTR_TYPE_HOME = "HOME";
- public static final String ATTR_TYPE_WORK = "WORK";
- public static final String ATTR_TYPE_FAX = "FAX";
- public static final String ATTR_TYPE_CELL = "CELL";
- public static final String ATTR_TYPE_VOICE = "VOICE";
- public static final String ATTR_TYPE_INTERNET = "INTERNET";
-
- public static final String ATTR_TYPE_PREF = "PREF";
-
- // Phone types valid in vCard and known to ContactsContract, but not so common.
- public static final String ATTR_TYPE_CAR = "CAR";
- public static final String ATTR_TYPE_ISDN = "ISDN";
- public static final String ATTR_TYPE_PAGER = "PAGER";
-
- // Phone types existing in vCard 2.1 but not known to ContactsContract.
- // TODO: should make parser make these TYPE_CUSTOM.
- public static final String ATTR_TYPE_MODEM = "MODEM";
- public static final String ATTR_TYPE_MSG = "MSG";
- public static final String ATTR_TYPE_BBS = "BBS";
- public static final String ATTR_TYPE_VIDEO = "VIDEO";
-
- // Phone types existing in the current Contacts structure but not valid in vCard (at least 2.1)
- // These types are encoded to "X-" attributes when composing vCard for now.
- // Parser passes these even if "X-" is added to the attribute.
- public static final String ATTR_TYPE_PHONE_EXTRA_OTHER = "OTHER";
- public static final String ATTR_TYPE_PHONE_EXTRA_CALLBACK = "CALLBACK";
- // TODO: may be "TYPE=COMPANY,PREF", not "COMPANY-MAIN".
- public static final String ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN = "COMPANY-MAIN";
- public static final String ATTR_TYPE_PHONE_EXTRA_RADIO = "RADIO";
- public static final String ATTR_TYPE_PHONE_EXTRA_TELEX = "TELEX";
- public static final String ATTR_TYPE_PHONE_EXTRA_TTY_TDD = "TTY-TDD";
- public static final String ATTR_TYPE_PHONE_EXTRA_ASSISTANT = "ASSISTANT";
-
- // DoCoMo specific attribute. Used with "SOUND" property, which is alternate of SORT-STRING in
- // vCard 3.0.
- public static final String ATTR_TYPE_X_IRMC_N = "X-IRMC-N";
-
- private Constants() {
- }
-} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/EntryCommitter.java b/core/java/android/pim/vcard/EntryCommitter.java
deleted file mode 100644
index 3f1655d..0000000
--- a/core/java/android/pim/vcard/EntryCommitter.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.pim.vcard;
-
-import android.content.ContentResolver;
-import android.util.Log;
-
-/**
- * EntryHandler implementation which commits the entry to Contacts Provider
- */
-public class EntryCommitter implements EntryHandler {
- public static String LOG_TAG = "vcard.EntryComitter";
-
- private ContentResolver mContentResolver;
- private long mTimeToCommit;
-
- public EntryCommitter(ContentResolver resolver) {
- mContentResolver = resolver;
- }
-
- public void onParsingStart() {
- }
-
- public void onParsingEnd() {
- if (VCardConfig.showPerformanceLog()) {
- Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
- }
- }
-
- public void onEntryCreated(final ContactStruct contactStruct) {
- long start = System.currentTimeMillis();
- contactStruct.pushIntoContentResolver(mContentResolver);
- mTimeToCommit += System.currentTimeMillis() - start;
- }
-} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
new file mode 100644
index 0000000..875c29e
--- /dev/null
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -0,0 +1,380 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TextUtils especially for Japanese.
+ */
+/* package */ class JapaneseUtils {
+ static private final Map<Character, String> sHalfWidthMap =
+ new HashMap<Character, String>();
+
+ static {
+ // There's no logical mapping rule in Unicode. Sigh.
+ sHalfWidthMap.put('\u3001', "\uFF64");
+ sHalfWidthMap.put('\u3002', "\uFF61");
+ sHalfWidthMap.put('\u300C', "\uFF62");
+ sHalfWidthMap.put('\u300D', "\uFF63");
+ sHalfWidthMap.put('\u301C', "~");
+ sHalfWidthMap.put('\u3041', "\uFF67");
+ sHalfWidthMap.put('\u3042', "\uFF71");
+ sHalfWidthMap.put('\u3043', "\uFF68");
+ sHalfWidthMap.put('\u3044', "\uFF72");
+ sHalfWidthMap.put('\u3045', "\uFF69");
+ sHalfWidthMap.put('\u3046', "\uFF73");
+ sHalfWidthMap.put('\u3047', "\uFF6A");
+ sHalfWidthMap.put('\u3048', "\uFF74");
+ sHalfWidthMap.put('\u3049', "\uFF6B");
+ sHalfWidthMap.put('\u304A', "\uFF75");
+ sHalfWidthMap.put('\u304B', "\uFF76");
+ sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u304D', "\uFF77");
+ sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u304F', "\uFF78");
+ sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u3051', "\uFF79");
+ sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u3053', "\uFF7A");
+ sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u3055', "\uFF7B");
+ sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u3057', "\uFF7C");
+ sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u3059', "\uFF7D");
+ sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u305B', "\uFF7E");
+ sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u305D', "\uFF7F");
+ sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u305F', "\uFF80");
+ sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u3061', "\uFF81");
+ sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u3063', "\uFF6F");
+ sHalfWidthMap.put('\u3064', "\uFF82");
+ sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u3066', "\uFF83");
+ sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u3068', "\uFF84");
+ sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u306A', "\uFF85");
+ sHalfWidthMap.put('\u306B', "\uFF86");
+ sHalfWidthMap.put('\u306C', "\uFF87");
+ sHalfWidthMap.put('\u306D', "\uFF88");
+ sHalfWidthMap.put('\u306E', "\uFF89");
+ sHalfWidthMap.put('\u306F', "\uFF8A");
+ sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u3072', "\uFF8B");
+ sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u3075', "\uFF8C");
+ sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u3078', "\uFF8D");
+ sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u307B', "\uFF8E");
+ sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u307E', "\uFF8F");
+ sHalfWidthMap.put('\u307F', "\uFF90");
+ sHalfWidthMap.put('\u3080', "\uFF91");
+ sHalfWidthMap.put('\u3081', "\uFF92");
+ sHalfWidthMap.put('\u3082', "\uFF93");
+ sHalfWidthMap.put('\u3083', "\uFF6C");
+ sHalfWidthMap.put('\u3084', "\uFF94");
+ sHalfWidthMap.put('\u3085', "\uFF6D");
+ sHalfWidthMap.put('\u3086', "\uFF95");
+ sHalfWidthMap.put('\u3087', "\uFF6E");
+ sHalfWidthMap.put('\u3088', "\uFF96");
+ sHalfWidthMap.put('\u3089', "\uFF97");
+ sHalfWidthMap.put('\u308A', "\uFF98");
+ sHalfWidthMap.put('\u308B', "\uFF99");
+ sHalfWidthMap.put('\u308C', "\uFF9A");
+ sHalfWidthMap.put('\u308D', "\uFF9B");
+ sHalfWidthMap.put('\u308E', "\uFF9C");
+ sHalfWidthMap.put('\u308F', "\uFF9C");
+ sHalfWidthMap.put('\u3090', "\uFF72");
+ sHalfWidthMap.put('\u3091', "\uFF74");
+ sHalfWidthMap.put('\u3092', "\uFF66");
+ sHalfWidthMap.put('\u3093', "\uFF9D");
+ sHalfWidthMap.put('\u309B', "\uFF9E");
+ sHalfWidthMap.put('\u309C', "\uFF9F");
+ sHalfWidthMap.put('\u30A1', "\uFF67");
+ sHalfWidthMap.put('\u30A2', "\uFF71");
+ sHalfWidthMap.put('\u30A3', "\uFF68");
+ sHalfWidthMap.put('\u30A4', "\uFF72");
+ sHalfWidthMap.put('\u30A5', "\uFF69");
+ sHalfWidthMap.put('\u30A6', "\uFF73");
+ sHalfWidthMap.put('\u30A7', "\uFF6A");
+ sHalfWidthMap.put('\u30A8', "\uFF74");
+ sHalfWidthMap.put('\u30A9', "\uFF6B");
+ sHalfWidthMap.put('\u30AA', "\uFF75");
+ sHalfWidthMap.put('\u30AB', "\uFF76");
+ sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
+ sHalfWidthMap.put('\u30AD', "\uFF77");
+ sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
+ sHalfWidthMap.put('\u30AF', "\uFF78");
+ sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
+ sHalfWidthMap.put('\u30B1', "\uFF79");
+ sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
+ sHalfWidthMap.put('\u30B3', "\uFF7A");
+ sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
+ sHalfWidthMap.put('\u30B5', "\uFF7B");
+ sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
+ sHalfWidthMap.put('\u30B7', "\uFF7C");
+ sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
+ sHalfWidthMap.put('\u30B9', "\uFF7D");
+ sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
+ sHalfWidthMap.put('\u30BB', "\uFF7E");
+ sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
+ sHalfWidthMap.put('\u30BD', "\uFF7F");
+ sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
+ sHalfWidthMap.put('\u30BF', "\uFF80");
+ sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
+ sHalfWidthMap.put('\u30C1', "\uFF81");
+ sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
+ sHalfWidthMap.put('\u30C3', "\uFF6F");
+ sHalfWidthMap.put('\u30C4', "\uFF82");
+ sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
+ sHalfWidthMap.put('\u30C6', "\uFF83");
+ sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
+ sHalfWidthMap.put('\u30C8', "\uFF84");
+ sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
+ sHalfWidthMap.put('\u30CA', "\uFF85");
+ sHalfWidthMap.put('\u30CB', "\uFF86");
+ sHalfWidthMap.put('\u30CC', "\uFF87");
+ sHalfWidthMap.put('\u30CD', "\uFF88");
+ sHalfWidthMap.put('\u30CE', "\uFF89");
+ sHalfWidthMap.put('\u30CF', "\uFF8A");
+ sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
+ sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
+ sHalfWidthMap.put('\u30D2', "\uFF8B");
+ sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
+ sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
+ sHalfWidthMap.put('\u30D5', "\uFF8C");
+ sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
+ sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
+ sHalfWidthMap.put('\u30D8', "\uFF8D");
+ sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
+ sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
+ sHalfWidthMap.put('\u30DB', "\uFF8E");
+ sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
+ sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
+ sHalfWidthMap.put('\u30DE', "\uFF8F");
+ sHalfWidthMap.put('\u30DF', "\uFF90");
+ sHalfWidthMap.put('\u30E0', "\uFF91");
+ sHalfWidthMap.put('\u30E1', "\uFF92");
+ sHalfWidthMap.put('\u30E2', "\uFF93");
+ sHalfWidthMap.put('\u30E3', "\uFF6C");
+ sHalfWidthMap.put('\u30E4', "\uFF94");
+ sHalfWidthMap.put('\u30E5', "\uFF6D");
+ sHalfWidthMap.put('\u30E6', "\uFF95");
+ sHalfWidthMap.put('\u30E7', "\uFF6E");
+ sHalfWidthMap.put('\u30E8', "\uFF96");
+ sHalfWidthMap.put('\u30E9', "\uFF97");
+ sHalfWidthMap.put('\u30EA', "\uFF98");
+ sHalfWidthMap.put('\u30EB', "\uFF99");
+ sHalfWidthMap.put('\u30EC', "\uFF9A");
+ sHalfWidthMap.put('\u30ED', "\uFF9B");
+ sHalfWidthMap.put('\u30EE', "\uFF9C");
+ sHalfWidthMap.put('\u30EF', "\uFF9C");
+ sHalfWidthMap.put('\u30F0', "\uFF72");
+ sHalfWidthMap.put('\u30F1', "\uFF74");
+ sHalfWidthMap.put('\u30F2', "\uFF66");
+ sHalfWidthMap.put('\u30F3', "\uFF9D");
+ sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
+ sHalfWidthMap.put('\u30F5', "\uFF76");
+ sHalfWidthMap.put('\u30F6', "\uFF79");
+ sHalfWidthMap.put('\u30FB', "\uFF65");
+ sHalfWidthMap.put('\u30FC', "\uFF70");
+ sHalfWidthMap.put('\uFF01', "!");
+ sHalfWidthMap.put('\uFF02', "\"");
+ sHalfWidthMap.put('\uFF03', "#");
+ sHalfWidthMap.put('\uFF04', "$");
+ sHalfWidthMap.put('\uFF05', "%");
+ sHalfWidthMap.put('\uFF06', "&");
+ sHalfWidthMap.put('\uFF07', "'");
+ sHalfWidthMap.put('\uFF08', "(");
+ sHalfWidthMap.put('\uFF09', ")");
+ sHalfWidthMap.put('\uFF0A', "*");
+ sHalfWidthMap.put('\uFF0B', "+");
+ sHalfWidthMap.put('\uFF0C', ",");
+ sHalfWidthMap.put('\uFF0D', "-");
+ sHalfWidthMap.put('\uFF0E', ".");
+ sHalfWidthMap.put('\uFF0F', "/");
+ sHalfWidthMap.put('\uFF10', "0");
+ sHalfWidthMap.put('\uFF11', "1");
+ sHalfWidthMap.put('\uFF12', "2");
+ sHalfWidthMap.put('\uFF13', "3");
+ sHalfWidthMap.put('\uFF14', "4");
+ sHalfWidthMap.put('\uFF15', "5");
+ sHalfWidthMap.put('\uFF16', "6");
+ sHalfWidthMap.put('\uFF17', "7");
+ sHalfWidthMap.put('\uFF18', "8");
+ sHalfWidthMap.put('\uFF19', "9");
+ sHalfWidthMap.put('\uFF1A', ":");
+ sHalfWidthMap.put('\uFF1B', ";");
+ sHalfWidthMap.put('\uFF1C', "<");
+ sHalfWidthMap.put('\uFF1D', "=");
+ sHalfWidthMap.put('\uFF1E', ">");
+ sHalfWidthMap.put('\uFF1F', "?");
+ sHalfWidthMap.put('\uFF20', "@");
+ sHalfWidthMap.put('\uFF21', "A");
+ sHalfWidthMap.put('\uFF22', "B");
+ sHalfWidthMap.put('\uFF23', "C");
+ sHalfWidthMap.put('\uFF24', "D");
+ sHalfWidthMap.put('\uFF25', "E");
+ sHalfWidthMap.put('\uFF26', "F");
+ sHalfWidthMap.put('\uFF27', "G");
+ sHalfWidthMap.put('\uFF28', "H");
+ sHalfWidthMap.put('\uFF29', "I");
+ sHalfWidthMap.put('\uFF2A', "J");
+ sHalfWidthMap.put('\uFF2B', "K");
+ sHalfWidthMap.put('\uFF2C', "L");
+ sHalfWidthMap.put('\uFF2D', "M");
+ sHalfWidthMap.put('\uFF2E', "N");
+ sHalfWidthMap.put('\uFF2F', "O");
+ sHalfWidthMap.put('\uFF30', "P");
+ sHalfWidthMap.put('\uFF31', "Q");
+ sHalfWidthMap.put('\uFF32', "R");
+ sHalfWidthMap.put('\uFF33', "S");
+ sHalfWidthMap.put('\uFF34', "T");
+ sHalfWidthMap.put('\uFF35', "U");
+ sHalfWidthMap.put('\uFF36', "V");
+ sHalfWidthMap.put('\uFF37', "W");
+ sHalfWidthMap.put('\uFF38', "X");
+ sHalfWidthMap.put('\uFF39', "Y");
+ sHalfWidthMap.put('\uFF3A', "Z");
+ sHalfWidthMap.put('\uFF3B', "[");
+ sHalfWidthMap.put('\uFF3C', "\\");
+ sHalfWidthMap.put('\uFF3D', "]");
+ sHalfWidthMap.put('\uFF3E', "^");
+ sHalfWidthMap.put('\uFF3F', "_");
+ sHalfWidthMap.put('\uFF41', "a");
+ sHalfWidthMap.put('\uFF42', "b");
+ sHalfWidthMap.put('\uFF43', "c");
+ sHalfWidthMap.put('\uFF44', "d");
+ sHalfWidthMap.put('\uFF45', "e");
+ sHalfWidthMap.put('\uFF46', "f");
+ sHalfWidthMap.put('\uFF47', "g");
+ sHalfWidthMap.put('\uFF48', "h");
+ sHalfWidthMap.put('\uFF49', "i");
+ sHalfWidthMap.put('\uFF4A', "j");
+ sHalfWidthMap.put('\uFF4B', "k");
+ sHalfWidthMap.put('\uFF4C', "l");
+ sHalfWidthMap.put('\uFF4D', "m");
+ sHalfWidthMap.put('\uFF4E', "n");
+ sHalfWidthMap.put('\uFF4F', "o");
+ sHalfWidthMap.put('\uFF50', "p");
+ sHalfWidthMap.put('\uFF51', "q");
+ sHalfWidthMap.put('\uFF52', "r");
+ sHalfWidthMap.put('\uFF53', "s");
+ sHalfWidthMap.put('\uFF54', "t");
+ sHalfWidthMap.put('\uFF55', "u");
+ sHalfWidthMap.put('\uFF56', "v");
+ sHalfWidthMap.put('\uFF57', "w");
+ sHalfWidthMap.put('\uFF58', "x");
+ sHalfWidthMap.put('\uFF59', "y");
+ sHalfWidthMap.put('\uFF5A', "z");
+ sHalfWidthMap.put('\uFF5B', "{");
+ sHalfWidthMap.put('\uFF5C', "|");
+ sHalfWidthMap.put('\uFF5D', "}");
+ sHalfWidthMap.put('\uFF5E', "~");
+ sHalfWidthMap.put('\uFF61', "\uFF61");
+ sHalfWidthMap.put('\uFF62', "\uFF62");
+ sHalfWidthMap.put('\uFF63', "\uFF63");
+ sHalfWidthMap.put('\uFF64', "\uFF64");
+ sHalfWidthMap.put('\uFF65', "\uFF65");
+ sHalfWidthMap.put('\uFF66', "\uFF66");
+ sHalfWidthMap.put('\uFF67', "\uFF67");
+ sHalfWidthMap.put('\uFF68', "\uFF68");
+ sHalfWidthMap.put('\uFF69', "\uFF69");
+ sHalfWidthMap.put('\uFF6A', "\uFF6A");
+ sHalfWidthMap.put('\uFF6B', "\uFF6B");
+ sHalfWidthMap.put('\uFF6C', "\uFF6C");
+ sHalfWidthMap.put('\uFF6D', "\uFF6D");
+ sHalfWidthMap.put('\uFF6E', "\uFF6E");
+ sHalfWidthMap.put('\uFF6F', "\uFF6F");
+ sHalfWidthMap.put('\uFF70', "\uFF70");
+ sHalfWidthMap.put('\uFF71', "\uFF71");
+ sHalfWidthMap.put('\uFF72', "\uFF72");
+ sHalfWidthMap.put('\uFF73', "\uFF73");
+ sHalfWidthMap.put('\uFF74', "\uFF74");
+ sHalfWidthMap.put('\uFF75', "\uFF75");
+ sHalfWidthMap.put('\uFF76', "\uFF76");
+ sHalfWidthMap.put('\uFF77', "\uFF77");
+ sHalfWidthMap.put('\uFF78', "\uFF78");
+ sHalfWidthMap.put('\uFF79', "\uFF79");
+ sHalfWidthMap.put('\uFF7A', "\uFF7A");
+ sHalfWidthMap.put('\uFF7B', "\uFF7B");
+ sHalfWidthMap.put('\uFF7C', "\uFF7C");
+ sHalfWidthMap.put('\uFF7D', "\uFF7D");
+ sHalfWidthMap.put('\uFF7E', "\uFF7E");
+ sHalfWidthMap.put('\uFF7F', "\uFF7F");
+ sHalfWidthMap.put('\uFF80', "\uFF80");
+ sHalfWidthMap.put('\uFF81', "\uFF81");
+ sHalfWidthMap.put('\uFF82', "\uFF82");
+ sHalfWidthMap.put('\uFF83', "\uFF83");
+ sHalfWidthMap.put('\uFF84', "\uFF84");
+ sHalfWidthMap.put('\uFF85', "\uFF85");
+ sHalfWidthMap.put('\uFF86', "\uFF86");
+ sHalfWidthMap.put('\uFF87', "\uFF87");
+ sHalfWidthMap.put('\uFF88', "\uFF88");
+ sHalfWidthMap.put('\uFF89', "\uFF89");
+ sHalfWidthMap.put('\uFF8A', "\uFF8A");
+ sHalfWidthMap.put('\uFF8B', "\uFF8B");
+ sHalfWidthMap.put('\uFF8C', "\uFF8C");
+ sHalfWidthMap.put('\uFF8D', "\uFF8D");
+ sHalfWidthMap.put('\uFF8E', "\uFF8E");
+ sHalfWidthMap.put('\uFF8F', "\uFF8F");
+ sHalfWidthMap.put('\uFF90', "\uFF90");
+ sHalfWidthMap.put('\uFF91', "\uFF91");
+ sHalfWidthMap.put('\uFF92', "\uFF92");
+ sHalfWidthMap.put('\uFF93', "\uFF93");
+ sHalfWidthMap.put('\uFF94', "\uFF94");
+ sHalfWidthMap.put('\uFF95', "\uFF95");
+ sHalfWidthMap.put('\uFF96', "\uFF96");
+ sHalfWidthMap.put('\uFF97', "\uFF97");
+ sHalfWidthMap.put('\uFF98', "\uFF98");
+ sHalfWidthMap.put('\uFF99', "\uFF99");
+ sHalfWidthMap.put('\uFF9A', "\uFF9A");
+ sHalfWidthMap.put('\uFF9B', "\uFF9B");
+ sHalfWidthMap.put('\uFF9C', "\uFF9C");
+ sHalfWidthMap.put('\uFF9D', "\uFF9D");
+ sHalfWidthMap.put('\uFF9E', "\uFF9E");
+ sHalfWidthMap.put('\uFF9F', "\uFF9F");
+ sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
+ }
+
+ /**
+ * Return half-width version of that character if possible. Return null if not possible
+ * @param ch input character
+ * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+ */
+ public static String tryGetHalfWidthText(char ch) {
+ if (sHalfWidthMap.containsKey(ch)) {
+ return sHalfWidthMap.get(ch);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index e1c4b33..0a6415d 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -1,64 +1,1913 @@
/*
* 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
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
*/
package android.pim.vcard;
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import org.apache.commons.codec.binary.Base64;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The class which lets users create their own vCard String.
+ */
+public class VCardBuilder {
+ private static final String LOG_TAG = "VCardBuilder";
+
+ // If you add the other element, please check all the columns are able to be
+ // converted to String.
+ //
+ // e.g. BLOB is not what we can handle here now.
+ private static final Set<String> sAllowedAndroidPropertySet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ Nickname.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE)));
+
+ public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+ public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+ public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
+
+ private static final String VCARD_DATA_VCARD = "VCARD";
+ private static final String VCARD_DATA_PUBLIC = "PUBLIC";
+
+ private static final String VCARD_PARAM_SEPARATOR = ";";
+ private static final String VCARD_END_OF_LINE = "\r\n";
+ private static final String VCARD_DATA_SEPARATOR = ":";
+ private static final String VCARD_ITEM_SEPARATOR = ";";
+ private static final String VCARD_WS = " ";
+ private static final String VCARD_PARAM_EQUAL = "=";
+
+ private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
+
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
+ private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ private final int mVCardType;
+
+ private final boolean mIsV30;
+ private final boolean mIsJapaneseMobilePhone;
+ private final boolean mOnlyOneNoteFieldIsAvailable;
+ private final boolean mIsDoCoMo;
+ private final boolean mShouldUseQuotedPrintable;
+ private final boolean mUsesAndroidProperty;
+ private final boolean mUsesDefactProperty;
+ private final boolean mUsesUtf8;
+ private final boolean mUsesShiftJis;
+ private final boolean mAppendTypeParamName;
+ private final boolean mRefrainsQPToNameProperties;
+ private final boolean mNeedsToConvertPhoneticString;
+
+ private final boolean mShouldAppendCharsetParam;
+
+ private final String mCharsetString;
+ private final String mVCardCharsetParameter;
+
+ private StringBuilder mBuilder;
+ private boolean mEndAppended;
+
+ public VCardBuilder(final int vcardType) {
+ mVCardType = vcardType;
+
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
+ mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
+ mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
+ mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
+ mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
+ mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
+ mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
+ mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
+ mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
+
+ mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
+
+ if (mIsDoCoMo) {
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
+ // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
+ // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
+ // Android, not shown to the public).
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+ } else if (mUsesShiftJis) {
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
+ mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
+ } else {
+ mCharsetString = UTF_8;
+ mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ }
+ clear();
+ }
+
+ public void clear() {
+ mBuilder = new StringBuilder();
+ mEndAppended = false;
+ appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
+ if (mIsV30) {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
+ } else {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
+ }
+ }
+
+ private boolean containsNonEmptyName(final ContentValues contentValues) {
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String phoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String phoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String phoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+ TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+ TextUtils.isEmpty(suffix) && TextUtils.isEmpty(phoneticFamilyName) &&
+ TextUtils.isEmpty(phoneticMiddleName) && TextUtils.isEmpty(phoneticGivenName) &&
+ TextUtils.isEmpty(displayName));
+ }
+
+ private ContentValues getPrimaryContentValue(final List<ContentValues> contentValuesList) {
+ ContentValues primaryContentValues = null;
+ ContentValues subprimaryContentValues = null;
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null){
+ continue;
+ }
+ Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
+ if (isSuperPrimary != null && isSuperPrimary > 0) {
+ // We choose "super primary" ContentValues.
+ primaryContentValues = contentValues;
+ break;
+ } else if (primaryContentValues == null) {
+ // We choose the first "primary" ContentValues
+ // if "super primary" ContentValues does not exist.
+ final Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+ if (isPrimary != null && isPrimary > 0 &&
+ containsNonEmptyName(contentValues)) {
+ primaryContentValues = contentValues;
+ // Do not break, since there may be ContentValues with "super primary"
+ // afterword.
+ } else if (subprimaryContentValues == null &&
+ containsNonEmptyName(contentValues)) {
+ subprimaryContentValues = contentValues;
+ }
+ }
+ }
+
+ if (primaryContentValues == null) {
+ if (subprimaryContentValues != null) {
+ // We choose the first ContentValues if any "primary" ContentValues does not exist.
+ primaryContentValues = subprimaryContentValues;
+ } else {
+ Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+ primaryContentValues = new ContentValues();
+ }
+ }
+
+ return primaryContentValues;
+ }
+
+ /**
+ * For safety, we'll emit just one value around StructuredName, as external importers
+ * may get confused with multiple "N", "FN", etc. properties, though it is valid in
+ * vCard spec.
+ */
+ public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ } else if (mIsV30) {
+ // vCard 3.0 requires "N" and "FN" properties.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ }
+ return this;
+ }
+
+ final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
+ final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+
+ if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
+ final boolean reallyAppendCharsetParameterToName =
+ shouldAppendCharsetParam(familyName, givenName, middleName, prefix, suffix);
+ final boolean reallyUseQuotedPrintableToName =
+ (!mRefrainsQPToNameProperties &&
+ !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
+ VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
+
+ final String formattedName;
+ if (!TextUtils.isEmpty(displayName)) {
+ formattedName = displayName;
+ } else {
+ formattedName = VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ familyName, middleName, givenName, prefix, suffix);
+ }
+ final boolean reallyAppendCharsetParameterToFN =
+ shouldAppendCharsetParam(formattedName);
+ final boolean reallyUseQuotedPrintableToFN =
+ !mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(formattedName);
+
+ final String encodedFamily;
+ final String encodedGiven;
+ final String encodedMiddle;
+ final String encodedPrefix;
+ final String encodedSuffix;
+ if (reallyUseQuotedPrintableToName) {
+ encodedFamily = encodeQuotedPrintable(familyName);
+ encodedGiven = encodeQuotedPrintable(givenName);
+ encodedMiddle = encodeQuotedPrintable(middleName);
+ encodedPrefix = encodeQuotedPrintable(prefix);
+ encodedSuffix = encodeQuotedPrintable(suffix);
+ } else {
+ encodedFamily = escapeCharacters(familyName);
+ encodedGiven = escapeCharacters(givenName);
+ encodedMiddle = escapeCharacters(middleName);
+ encodedPrefix = escapeCharacters(prefix);
+ encodedSuffix = escapeCharacters(suffix);
+ }
+
+ final String encodedFormattedname =
+ (reallyUseQuotedPrintableToFN ?
+ encodeQuotedPrintable(formattedName) : escapeCharacters(formattedName));
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (mIsDoCoMo) {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ // DoCoMo phones require that all the elements in the "family name" field.
+ mBuilder.append(formattedName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ } else {
+ if (reallyAppendCharsetParameterToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFamily);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedGiven);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedMiddle);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedPrefix);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedSuffix);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN property
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+ if (reallyAppendCharsetParameterToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToFN) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedFormattedname);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (!TextUtils.isEmpty(displayName)) {
+ final boolean reallyUseQuotedPrintableToDisplayName =
+ (!mRefrainsQPToNameProperties &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
+ final String encodedDisplayName =
+ reallyUseQuotedPrintableToDisplayName ?
+ encodeQuotedPrintable(displayName) :
+ escapeCharacters(displayName);
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintableToDisplayName) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+
+ // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
+ // when it would be useful for external importers, assuming no external
+ // importer allows this vioration.
+ if (shouldAppendCharsetParam(displayName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedDisplayName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsV30) {
+ // vCard 3.0 specification requires these fields.
+ appendLine(VCardConstants.PROPERTY_N, "");
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
+ }
+
+ appendPhoneticNameFields(contentValues);
+ return this;
+ }
+
+ private void appendPhoneticNameFields(final ContentValues contentValues) {
+ final String phoneticFamilyName;
+ final String phoneticMiddleName;
+ final String phoneticGivenName;
+ {
+ final String tmpPhoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String tmpPhoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String tmpPhoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ if (mNeedsToConvertPhoneticString) {
+ phoneticFamilyName = VCardUtils.toHalfWidthString(tmpPhoneticFamilyName);
+ phoneticMiddleName = VCardUtils.toHalfWidthString(tmpPhoneticMiddleName);
+ phoneticGivenName = VCardUtils.toHalfWidthString(tmpPhoneticGivenName);
+ } else {
+ phoneticFamilyName = tmpPhoneticFamilyName;
+ phoneticMiddleName = tmpPhoneticMiddleName;
+ phoneticGivenName = tmpPhoneticGivenName;
+ }
+ }
+
+ if (TextUtils.isEmpty(phoneticFamilyName)
+ && TextUtils.isEmpty(phoneticMiddleName)
+ && TextUtils.isEmpty(phoneticGivenName)) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ return;
+ }
+
+ // Try to emit the field(s) related to phonetic name.
+ if (mIsV30) {
+ final String sortString = VCardUtils
+ .constructNameFromElements(mVCardType,
+ phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
+ mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
+ if (shouldAppendCharsetParam(sortString)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapeCharacters(sortString));
+ mBuilder.append(VCARD_END_OF_LINE);
+ } else if (mIsJapaneseMobilePhone) {
+ // Note: There is no appropriate property for expressing
+ // phonetic name in vCard 2.1, while there is in
+ // vCard 3.0 (SORT-STRING).
+ // We chose to use DoCoMo's way when the device is Japanese one
+ // since it is supported by
+ // a lot of Japanese mobile phones. This is "X-" property, so
+ // any parser hopefully would not get confused with this.
+ //
+ // Also, DoCoMo's specification requires vCard composer to use just the first
+ // column.
+ // i.e.
+ // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ mBuilder.append(VCardConstants.PROPERTY_SOUND);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
+
+ boolean reallyUseQuotedPrintable =
+ (!mRefrainsQPToNameProperties
+ && !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticFamilyName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticMiddleName)
+ && VCardUtils.containsOnlyNonCrLfPrintableAscii(
+ phoneticGivenName)));
+
+ final String encodedPhoneticFamilyName;
+ final String encodedPhoneticMiddleName;
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+
+ if (shouldAppendCharsetParam(encodedPhoneticFamilyName,
+ encodedPhoneticMiddleName, encodedPhoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ {
+ boolean first = true;
+ if (!TextUtils.isEmpty(encodedPhoneticFamilyName)) {
+ mBuilder.append(encodedPhoneticFamilyName);
+ first = false;
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticMiddleName)) {
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticMiddleName);
+ }
+ if (!TextUtils.isEmpty(encodedPhoneticGivenName)) {
+ if (!first) {
+ mBuilder.append(' ');
+ }
+ mBuilder.append(encodedPhoneticGivenName);
+ }
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ if (mUsesDefactProperty) {
+ if (!TextUtils.isEmpty(phoneticGivenName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
+ final String encodedPhoneticGivenName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
+ } else {
+ encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME);
+ if (shouldAppendCharsetParam(phoneticGivenName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticGivenName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticMiddleName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
+ final String encodedPhoneticMiddleName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
+ } else {
+ encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME);
+ if (shouldAppendCharsetParam(phoneticMiddleName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticMiddleName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ if (!TextUtils.isEmpty(phoneticFamilyName)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
+ final String encodedPhoneticFamilyName;
+ if (reallyUseQuotedPrintable) {
+ encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
+ } else {
+ encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
+ }
+ mBuilder.append(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME);
+ if (shouldAppendCharsetParam(phoneticFamilyName)) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedPhoneticFamilyName);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ }
+ }
+
+ public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
+ final boolean useAndroidProperty;
+ if (mIsV30) {
+ useAndroidProperty = false;
+ } else if (mUsesAndroidProperty) {
+ useAndroidProperty = true;
+ } else {
+ // There's no way to add this field.
+ return this;
+ }
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final String nickname = contentValues.getAsString(Nickname.NAME);
+ if (TextUtils.isEmpty(nickname)) {
+ continue;
+ }
+ if (useAndroidProperty) {
+ appendAndroidSpecificProperty(Nickname.CONTENT_ITEM_TYPE, contentValues);
+ } else {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_NICKNAME, nickname);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhones(final List<ContentValues> contentValuesList) {
+ boolean phoneLineExists = false;
+ if (contentValuesList != null) {
+ Set<String> phoneSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
+ final String label = contentValues.getAsString(Phone.LABEL);
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Phone.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ String phoneNumber = contentValues.getAsString(Phone.NUMBER);
+ if (phoneNumber != null) {
+ phoneNumber = phoneNumber.trim();
+ }
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+ int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER) {
+ phoneLineExists = true;
+ if (!phoneSet.contains(phoneNumber)) {
+ phoneSet.add(phoneNumber);
+ appendTelLine(type, label, phoneNumber, isPrimary);
+ }
+ } else {
+ // The entry "may" have several phone numbers when the contact entry is
+ // corrupted because of its original source.
+ //
+ // e.g. I encountered the entry like the following.
+ // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
+ // This kind of entry is not able to be inserted via Android devices, but
+ // possible if the source of the data is already corrupted.
+ List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ if (phoneNumberList.isEmpty()) {
+ continue;
+ }
+ phoneLineExists = true;
+ for (String actualPhoneNumber : phoneNumberList) {
+ if (!phoneSet.contains(actualPhoneNumber)) {
+ final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+ final String formattedPhoneNumber =
+ PhoneNumberUtils.formatNumber(actualPhoneNumber, format);
+ phoneSet.add(actualPhoneNumber);
+ appendTelLine(type, label, formattedPhoneNumber, isPrimary);
+ }
+ }
+ }
+ }
+ }
+
+ if (!phoneLineExists && mIsDoCoMo) {
+ appendTelLine(Phone.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+ List<String> phoneList = new ArrayList<String>();
+
+ StringBuilder builder = new StringBuilder();
+ final int length = phoneNumber.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = phoneNumber.charAt(i);
+ // TODO: add a test case for string with '+', and care the other possible issues
+ // which may happen by ignoring non-digits other than '+'.
+ if (Character.isDigit(ch) || ch == '+') {
+ builder.append(ch);
+ } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+ phoneList.add(builder.toString());
+ builder = new StringBuilder();
+ }
+ }
+ if (builder.length() > 0) {
+ phoneList.add(builder.toString());
+ }
+
+ return phoneList;
+ }
+
+ public VCardBuilder appendEmails(final List<ContentValues> contentValuesList) {
+ boolean emailAddressExists = false;
+ if (contentValuesList != null) {
+ final Set<String> addressSet = new HashSet<String>();
+ for (ContentValues contentValues : contentValuesList) {
+ String emailAddress = contentValues.getAsString(Email.DATA);
+ if (emailAddress != null) {
+ emailAddress = emailAddress.trim();
+ }
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
+ final int type = (typeAsObject != null ?
+ typeAsObject : DEFAULT_EMAIL_TYPE);
+ final String label = contentValues.getAsString(Email.LABEL);
+ Integer isPrimaryAsInteger = contentValues.getAsInteger(Email.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ emailAddressExists = true;
+ if (!addressSet.contains(emailAddress)) {
+ addressSet.add(emailAddress);
+ appendEmailLine(type, label, emailAddress, isPrimary);
+ }
+ }
+ }
+
+ if (!emailAddressExists && mIsDoCoMo) {
+ appendEmailLine(Email.TYPE_HOME, "", "", false);
+ }
+
+ return this;
+ }
+
+ public VCardBuilder appendPostals(final List<ContentValues> contentValuesList) {
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ if (mIsDoCoMo) {
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCardConstants.PARAM_TYPE_HOME);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+ } else {
+ if (mIsDoCoMo) {
+ appendPostalsForDoCoMo(contentValuesList);
+ } else {
+ appendPostalsForGeneric(contentValuesList);
+ }
+ }
+
+ return this;
+ }
+
+ private static final Map<Integer, Integer> sPostalTypePriorityMap;
+
+ static {
+ sPostalTypePriorityMap = new HashMap<Integer, Integer>();
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_HOME, 0);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_WORK, 1);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_OTHER, 2);
+ sPostalTypePriorityMap.put(StructuredPostal.TYPE_CUSTOM, 3);
+ }
+
+ /**
+ * Tries to append just one line. If there's no appropriate address
+ * information, append an empty line.
+ */
+ private void appendPostalsForDoCoMo(final List<ContentValues> contentValuesList) {
+ int currentPriority = Integer.MAX_VALUE;
+ int currentType = Integer.MAX_VALUE;
+ ContentValues currentContentValues = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final Integer priorityAsInteger = sPostalTypePriorityMap.get(typeAsInteger);
+ final int priority =
+ (priorityAsInteger != null ? priorityAsInteger : Integer.MAX_VALUE);
+ if (priority < currentPriority) {
+ currentPriority = priority;
+ currentType = typeAsInteger;
+ currentContentValues = contentValues;
+ if (priority == 0) {
+ break;
+ }
+ }
+ }
+
+ if (currentContentValues == null) {
+ Log.w(LOG_TAG, "Should not come here. Must have at least one postal data.");
+ return;
+ }
-public interface VCardBuilder {
- void start();
+ final String label = currentContentValues.getAsString(StructuredPostal.LABEL);
+ appendPostalLine(currentType, label, currentContentValues, false, true);
+ }
- void end();
+ private void appendPostalsForGeneric(final List<ContentValues> contentValuesList) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer typeAsInteger = contentValues.getAsInteger(StructuredPostal.TYPE);
+ final int type = (typeAsInteger != null ?
+ typeAsInteger : DEFAULT_POSTAL_TYPE);
+ final String label = contentValues.getAsString(StructuredPostal.LABEL);
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(StructuredPostal.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ appendPostalLine(type, label, contentValues, isPrimary, false);
+ }
+ }
- /**
- * BEGIN:VCARD
+ private static class PostalStruct {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressData;
+ public PostalStruct(final boolean reallyUseQuotedPrintable,
+ final boolean appendCharset, final String addressData) {
+ this.reallyUseQuotedPrintable = reallyUseQuotedPrintable;
+ this.appendCharset = appendCharset;
+ this.addressData = addressData;
+ }
+ }
+
+ /**
+ * @return null when there's no information available to construct the data.
*/
- void startRecord(String type);
+ private PostalStruct tryConstructPostalStruct(ContentValues contentValues) {
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ final String rawPoBox = contentValues.getAsString(StructuredPostal.POBOX);
+ final String rawNeighborhood = contentValues.getAsString(StructuredPostal.NEIGHBORHOOD);
+ final String rawStreet = contentValues.getAsString(StructuredPostal.STREET);
+ final String rawLocality = contentValues.getAsString(StructuredPostal.CITY);
+ final String rawRegion = contentValues.getAsString(StructuredPostal.REGION);
+ final String rawPostalCode = contentValues.getAsString(StructuredPostal.POSTCODE);
+ final String rawCountry = contentValues.getAsString(StructuredPostal.COUNTRY);
+ final String[] rawAddressArray = new String[]{
+ rawPoBox, rawNeighborhood, rawStreet, rawLocality,
+ rawRegion, rawPostalCode, rawCountry};
+ if (!VCardUtils.areAllEmpty(rawAddressArray)) {
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawAddressArray));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawAddressArray);
+ final String encodedPoBox;
+ final String encodedStreet;
+ final String encodedLocality;
+ final String encodedRegion;
+ final String encodedPostalCode;
+ final String encodedCountry;
+ final String encodedNeighborhood;
+
+ final String rawLocality2;
+ // This looks inefficient since we encode rawLocality and rawNeighborhood twice,
+ // but this is intentional.
+ //
+ // QP encoding may add line feeds when needed and the result of
+ // - encodeQuotedPrintable(rawLocality + " " + rawNeighborhood)
+ // may be different from
+ // - encodedLocality + " " + encodedNeighborhood.
+ //
+ // We use safer way.
+ if (TextUtils.isEmpty(rawLocality)) {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = "";
+ } else {
+ rawLocality2 = rawNeighborhood;
+ }
+ } else {
+ if (TextUtils.isEmpty(rawNeighborhood)) {
+ rawLocality2 = rawLocality;
+ } else {
+ rawLocality2 = rawLocality + " " + rawNeighborhood;
+ }
+ }
+ if (reallyUseQuotedPrintable) {
+ encodedPoBox = encodeQuotedPrintable(rawPoBox);
+ encodedStreet = encodeQuotedPrintable(rawStreet);
+ encodedLocality = encodeQuotedPrintable(rawLocality2);
+ encodedRegion = encodeQuotedPrintable(rawRegion);
+ encodedPostalCode = encodeQuotedPrintable(rawPostalCode);
+ encodedCountry = encodeQuotedPrintable(rawCountry);
+ } else {
+ encodedPoBox = escapeCharacters(rawPoBox);
+ encodedStreet = escapeCharacters(rawStreet);
+ encodedLocality = escapeCharacters(rawLocality2);
+ encodedRegion = escapeCharacters(rawRegion);
+ encodedPostalCode = escapeCharacters(rawPostalCode);
+ encodedCountry = escapeCharacters(rawCountry);
+ encodedNeighborhood = escapeCharacters(rawNeighborhood);
+ }
+ final StringBuffer addressBuffer = new StringBuffer();
+ addressBuffer.append(encodedPoBox);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedStreet);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedLocality);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedRegion);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedPostalCode);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedCountry);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ } else { // VCardUtils.areAllEmpty(rawAddressArray) == true
+ // Try to use FORMATTED_ADDRESS instead.
+ final String rawFormattedAddress =
+ contentValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+ if (TextUtils.isEmpty(rawFormattedAddress)) {
+ return null;
+ }
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawFormattedAddress));
+ final boolean appendCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawFormattedAddress);
+ final String encodedFormattedAddress;
+ if (reallyUseQuotedPrintable) {
+ encodedFormattedAddress = encodeQuotedPrintable(rawFormattedAddress);
+ } else {
+ encodedFormattedAddress = escapeCharacters(rawFormattedAddress);
+ }
+
+ // We use the second value ("Extended Address") just because Japanese mobile phones
+ // do so. If the other importer expects the value be in the other field, some flag may
+ // be needed.
+ final StringBuffer addressBuffer = new StringBuffer();
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(encodedFormattedAddress);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ return new PostalStruct(
+ reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ }
+ }
+
+ public VCardBuilder appendIms(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ final Integer protocolAsObject = contentValues.getAsInteger(Im.PROTOCOL);
+ if (protocolAsObject == null) {
+ continue;
+ }
+ final String propertyName = VCardUtils.getPropertyNameForIm(protocolAsObject);
+ if (propertyName == null) {
+ continue;
+ }
+ String data = contentValues.getAsString(Im.DATA);
+ if (data != null) {
+ data = data.trim();
+ }
+ if (TextUtils.isEmpty(data)) {
+ continue;
+ }
+ final String typeAsString;
+ {
+ final Integer typeAsInteger = contentValues.getAsInteger(Im.TYPE);
+ switch (typeAsInteger != null ? typeAsInteger : Im.TYPE_OTHER) {
+ case Im.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Im.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Im.TYPE_CUSTOM: {
+ final String label = contentValues.getAsString(Im.LABEL);
+ typeAsString = (label != null ? "X-" + label : null);
+ break;
+ }
+ case Im.TYPE_OTHER: // Ignore
+ default: {
+ typeAsString = null;
+ break;
+ }
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+ final Integer isPrimaryAsInteger = contentValues.getAsInteger(Im.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ appendLineWithCharsetAndQPDetection(propertyName, parameterList, data);
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendWebsites(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String website = contentValues.getAsString(Website.URL);
+ if (website != null) {
+ website = website.trim();
+ }
+
+ // Note: vCard 3.0 does not allow any parameter addition toward "URL"
+ // property, while there's no document in vCard 2.1.
+ if (!TextUtils.isEmpty(website)) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_URL, website);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendOrganizations(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ String company = contentValues.getAsString(Organization.COMPANY);
+ if (company != null) {
+ company = company.trim();
+ }
+ String department = contentValues.getAsString(Organization.DEPARTMENT);
+ if (department != null) {
+ department = department.trim();
+ }
+ String title = contentValues.getAsString(Organization.TITLE);
+ if (title != null) {
+ title = title.trim();
+ }
+
+ StringBuilder orgBuilder = new StringBuilder();
+ if (!TextUtils.isEmpty(company)) {
+ orgBuilder.append(company);
+ }
+ if (!TextUtils.isEmpty(department)) {
+ if (orgBuilder.length() > 0) {
+ orgBuilder.append(';');
+ }
+ orgBuilder.append(department);
+ }
+ final String orgline = orgBuilder.toString();
+ appendLine(VCardConstants.PROPERTY_ORG, orgline,
+ !VCardUtils.containsOnlyPrintableAscii(orgline),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(orgline)));
+
+ if (!TextUtils.isEmpty(title)) {
+ appendLine(VCardConstants.PROPERTY_TITLE, title,
+ !VCardUtils.containsOnlyPrintableAscii(title),
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendPhotos(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ for (ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
+ if (data == null) {
+ continue;
+ }
+ final String photoType = VCardUtils.guessImageType(data);
+ if (photoType == null) {
+ Log.d(LOG_TAG, "Unknown photo type. Ignored.");
+ continue;
+ }
+ final String photoString = new String(Base64.encodeBase64(data));
+ if (!TextUtils.isEmpty(photoString)) {
+ appendPhotoLine(photoString, photoType);
+ }
+ }
+ }
+ return this;
+ }
+
+ public VCardBuilder appendNotes(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ if (mOnlyOneNoteFieldIsAvailable) {
+ final StringBuilder noteBuilder = new StringBuilder();
+ boolean first = true;
+ for (final ContentValues contentValues : contentValuesList) {
+ String note = contentValues.getAsString(Note.NOTE);
+ if (note == null) {
+ note = "";
+ }
+ if (note.length() > 0) {
+ if (first) {
+ first = false;
+ } else {
+ noteBuilder.append('\n');
+ }
+ noteBuilder.append(note);
+ }
+ }
+ final String noteStr = noteBuilder.toString();
+ // This means we scan noteStr completely twice, which is redundant.
+ // But for now, we assume this is not so time-consuming..
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ } else {
+ for (ContentValues contentValues : contentValuesList) {
+ final String noteStr = contentValues.getAsString(Note.NOTE);
+ if (!TextUtils.isEmpty(noteStr)) {
+ final boolean shouldAppendCharsetInfo =
+ !VCardUtils.containsOnlyPrintableAscii(noteStr);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
+ appendLine(VCardConstants.PROPERTY_NOTE, noteStr,
+ shouldAppendCharsetInfo, reallyUseQuotedPrintable);
+ }
+ }
+ }
+ }
+ return this;
+ }
- /** END:VXX */
- void endRecord();
+ public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ if (contentValuesList != null) {
+ String primaryBirthday = null;
+ String secondaryBirthday = null;
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ final Integer eventTypeAsInteger = contentValues.getAsInteger(Event.TYPE);
+ final int eventType;
+ if (eventTypeAsInteger != null) {
+ eventType = eventTypeAsInteger;
+ } else {
+ eventType = Event.TYPE_OTHER;
+ }
+ if (eventType == Event.TYPE_BIRTHDAY) {
+ final String birthdayCandidate = contentValues.getAsString(Event.START_DATE);
+ if (birthdayCandidate == null) {
+ continue;
+ }
+ final Integer isSuperPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_SUPER_PRIMARY);
+ final boolean isSuperPrimary = (isSuperPrimaryAsInteger != null ?
+ (isSuperPrimaryAsInteger > 0) : false);
+ if (isSuperPrimary) {
+ // "super primary" birthday should the prefered one.
+ primaryBirthday = birthdayCandidate;
+ break;
+ }
+ final Integer isPrimaryAsInteger =
+ contentValues.getAsInteger(Event.IS_PRIMARY);
+ final boolean isPrimary = (isPrimaryAsInteger != null ?
+ (isPrimaryAsInteger > 0) : false);
+ if (isPrimary) {
+ // We don't break here since "super primary" birthday may exist later.
+ primaryBirthday = birthdayCandidate;
+ } else if (secondaryBirthday == null) {
+ // First entry is set to the "secondary" candidate.
+ secondaryBirthday = birthdayCandidate;
+ }
+ } else if (mUsesAndroidProperty) {
+ // Event types other than Birthday is not supported by vCard.
+ appendAndroidSpecificProperty(Event.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ if (primaryBirthday != null) {
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ primaryBirthday.trim());
+ } else if (secondaryBirthday != null){
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_BDAY,
+ secondaryBirthday.trim());
+ }
+ }
+ return this;
+ }
- void startProperty();
+ public VCardBuilder appendRelation(final List<ContentValues> contentValuesList) {
+ if (mUsesAndroidProperty && contentValuesList != null) {
+ for (final ContentValues contentValues : contentValuesList) {
+ if (contentValues == null) {
+ continue;
+ }
+ appendAndroidSpecificProperty(Relation.CONTENT_ITEM_TYPE, contentValues);
+ }
+ }
+ return this;
+ }
- void endProperty();
+ public void appendPostalLine(final int type, final String label,
+ final ContentValues contentValues,
+ final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean reallyUseQuotedPrintable;
+ final boolean appendCharset;
+ final String addressValue;
+ {
+ PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
+ if (postalStruct == null) {
+ if (emitLineEveryTime) {
+ reallyUseQuotedPrintable = false;
+ appendCharset = false;
+ addressValue = "";
+ } else {
+ return;
+ }
+ } else {
+ reallyUseQuotedPrintable = postalStruct.reallyUseQuotedPrintable;
+ appendCharset = postalStruct.appendCharset;
+ addressValue = postalStruct.addressData;
+ }
+ }
+
+ List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ switch (type) {
+ case StructuredPostal.TYPE_HOME: {
+ parameterList.add(VCardConstants.PARAM_TYPE_HOME);
+ break;
+ }
+ case StructuredPostal.TYPE_WORK: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ break;
+ }
+ case StructuredPostal.TYPE_CUSTOM: {
+ if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // We're not sure whether the label is valid in the spec
+ // ("IANA-token" in the vCard 3.0 is unclear...)
+ // Just for safety, we add "X-" at the beggining of each label.
+ // Also checks the label obeys with vCard 3.0 spec.
+ parameterList.add("X-" + label);
+ }
+ break;
+ }
+ case StructuredPostal.TYPE_OTHER: {
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown StructuredPostal type: " + type);
+ break;
+ }
+ }
+
+ mBuilder.append(VCardConstants.PROPERTY_ADR);
+ if (!parameterList.isEmpty()) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (appendCharset) {
+ // Strictly, vCard 3.0 does not allow exporters to emit charset information,
+ // but we will add it since the information should be useful for importers,
+ //
+ // Assume no parser does not emit error with this parameter in vCard 3.0.
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(addressValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendEmailLine(final int type, final String label,
+ final String rawValue, final boolean isPrimary) {
+ final String typeAsString;
+ switch (type) {
+ case Email.TYPE_CUSTOM: {
+ if (VCardUtils.isMobilePhoneLabel(label)) {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ } else if (!TextUtils.isEmpty(label)
+ && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ typeAsString = "X-" + label;
+ } else {
+ typeAsString = null;
+ }
+ break;
+ }
+ case Email.TYPE_HOME: {
+ typeAsString = VCardConstants.PARAM_TYPE_HOME;
+ break;
+ }
+ case Email.TYPE_WORK: {
+ typeAsString = VCardConstants.PARAM_TYPE_WORK;
+ break;
+ }
+ case Email.TYPE_OTHER: {
+ typeAsString = null;
+ break;
+ }
+ case Email.TYPE_MOBILE: {
+ typeAsString = VCardConstants.PARAM_TYPE_CELL;
+ break;
+ }
+ default: {
+ Log.e(LOG_TAG, "Unknown Email type: " + type);
+ typeAsString = null;
+ break;
+ }
+ }
+
+ final List<String> parameterList = new ArrayList<String>();
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+ if (!TextUtils.isEmpty(typeAsString)) {
+ parameterList.add(typeAsString);
+ }
+
+ appendLineWithCharsetAndQPDetection(VCardConstants.PROPERTY_EMAIL, parameterList,
+ rawValue);
+ }
+
+ public void appendTelLine(final Integer typeAsInteger, final String label,
+ final String encodedValue, boolean isPrimary) {
+ mBuilder.append(VCardConstants.PROPERTY_TEL);
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+
+ final int type;
+ if (typeAsInteger == null) {
+ type = Phone.TYPE_OTHER;
+ } else {
+ type = typeAsInteger;
+ }
+
+ ArrayList<String> parameterList = new ArrayList<String>();
+ switch (type) {
+ case Phone.TYPE_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME));
+ break;
+ }
+ case Phone.TYPE_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK));
+ break;
+ }
+ case Phone.TYPE_FAX_HOME: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_HOME, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_FAX_WORK: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_FAX));
+ break;
+ }
+ case Phone.TYPE_MOBILE: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ break;
+ }
+ case Phone.TYPE_PAGER: {
+ if (mIsDoCoMo) {
+ // Not sure about the reason, but previous implementation had
+ // used "VOICE" instead of "PAGER"
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_OTHER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ break;
+ }
+ case Phone.TYPE_CAR: {
+ parameterList.add(VCardConstants.PARAM_TYPE_CAR);
+ break;
+ }
+ case Phone.TYPE_COMPANY_MAIN: {
+ // There's no relevant field in vCard (at least 2.1).
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_ISDN: {
+ parameterList.add(VCardConstants.PARAM_TYPE_ISDN);
+ break;
+ }
+ case Phone.TYPE_MAIN: {
+ isPrimary = true;
+ break;
+ }
+ case Phone.TYPE_OTHER_FAX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_FAX);
+ break;
+ }
+ case Phone.TYPE_TELEX: {
+ parameterList.add(VCardConstants.PARAM_TYPE_TLX);
+ break;
+ }
+ case Phone.TYPE_WORK_MOBILE: {
+ parameterList.addAll(
+ Arrays.asList(VCardConstants.PARAM_TYPE_WORK, VCardConstants.PARAM_TYPE_CELL));
+ break;
+ }
+ case Phone.TYPE_WORK_PAGER: {
+ parameterList.add(VCardConstants.PARAM_TYPE_WORK);
+ // See above.
+ if (mIsDoCoMo) {
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ parameterList.add(VCardConstants.PARAM_TYPE_PAGER);
+ }
+ break;
+ }
+ case Phone.TYPE_MMS: {
+ parameterList.add(VCardConstants.PARAM_TYPE_MSG);
+ break;
+ }
+ case Phone.TYPE_CUSTOM: {
+ if (TextUtils.isEmpty(label)) {
+ // Just ignore the custom type.
+ parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
+ } else if (VCardUtils.isMobilePhoneLabel(label)) {
+ parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ } else {
+ final String upperLabel = label.toUpperCase();
+ if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
+ parameterList.add(upperLabel);
+ } else if (VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
+ // Note: Strictly, vCard 2.1 does not allow "X-" parameter without
+ // "TYPE=" string.
+ parameterList.add("X-" + label);
+ }
+ }
+ break;
+ }
+ case Phone.TYPE_RADIO:
+ case Phone.TYPE_TTY_TDD:
+ default: {
+ break;
+ }
+ }
+
+ if (isPrimary) {
+ parameterList.add(VCardConstants.PARAM_TYPE_PREF);
+ }
+
+ if (parameterList.isEmpty()) {
+ appendUncommonPhoneType(mBuilder, type);
+ } else {
+ appendTypeParameters(parameterList);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
/**
- * @param group
+ * Appends phone type string which may not be available in some devices.
*/
- void propertyGroup(String group);
-
+ private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
+ if (mIsDoCoMo) {
+ // The previous implementation for DoCoMo had been conservative
+ // about miscellaneous types.
+ builder.append(VCardConstants.PARAM_TYPE_VOICE);
+ } else {
+ String phoneType = VCardUtils.getPhoneTypeString(type);
+ if (phoneType != null) {
+ appendTypeParameter(phoneType);
+ } else {
+ Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
+ }
+ }
+ }
+
/**
- * @param name
- * N <br>
- * N
+ * @param encodedValue Must be encoded by BASE64
+ * @param photoType
*/
- void propertyName(String name);
+ public void appendPhotoLine(final String encodedValue, final String photoType) {
+ StringBuilder tmpBuilder = new StringBuilder();
+ tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ if (mIsV30) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ } else {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
+ }
+ tmpBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameter(tmpBuilder, photoType);
+ tmpBuilder.append(VCARD_DATA_SEPARATOR);
+ tmpBuilder.append(encodedValue);
+
+ final String tmpStr = tmpBuilder.toString();
+ tmpBuilder = new StringBuilder();
+ int lineCount = 0;
+ final int length = tmpStr.length();
+ final int maxNumForFirstLine = VCardConstants.MAX_CHARACTER_NUMS_BASE64_V30
+ - VCARD_END_OF_LINE.length();
+ final int maxNumInGeneral = maxNumForFirstLine - VCARD_WS.length();
+ int maxNum = maxNumForFirstLine;
+ for (int i = 0; i < length; i++) {
+ tmpBuilder.append(tmpStr.charAt(i));
+ lineCount++;
+ if (lineCount > maxNum) {
+ tmpBuilder.append(VCARD_END_OF_LINE);
+ tmpBuilder.append(VCARD_WS);
+ maxNum = maxNumInGeneral;
+ lineCount = 0;
+ }
+ }
+ mBuilder.append(tmpBuilder.toString());
+ mBuilder.append(VCARD_END_OF_LINE);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ if (!sAllowedAndroidPropertySet.contains(mimeType)) {
+ return;
+ }
+ final List<String> rawValueList = new ArrayList<String>();
+ for (int i = 1; i <= VCardConstants.MAX_DATA_COLUMN; i++) {
+ String value = contentValues.getAsString("data" + i);
+ if (value == null) {
+ value = "";
+ }
+ rawValueList.add(value);
+ }
+
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ mBuilder.append(VCardConstants.PROPERTY_X_ANDROID_CUSTOM);
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(mimeType); // Should not be encoded.
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final String rawValue) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValue);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(
+ final String propertyName, final List<String> rawValueList) {
+ appendLineWithCharsetAndQPDetection(propertyName, null, rawValueList);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final String rawValue) {
+ final boolean needCharset =
+ !VCardUtils.containsOnlyPrintableAscii(rawValue);
+ final boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValue));
+ appendLine(propertyName, parameterList,
+ rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLineWithCharsetAndQPDetection(final String propertyName,
+ final List<String> parameterList, final List<String> rawValueList) {
+ boolean needCharset =
+ (mShouldAppendCharsetParam &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ boolean reallyUseQuotedPrintable =
+ (mShouldUseQuotedPrintable &&
+ !VCardUtils.containsOnlyNonCrLfPrintableAscii(rawValueList));
+ appendLine(propertyName, parameterList, rawValueList,
+ needCharset, reallyUseQuotedPrintable);
+ }
+
+ /**
+ * Appends one line with a given property name and value.
+ */
+ public void appendLine(final String propertyName, final String rawValue) {
+ appendLine(propertyName, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList) {
+ appendLine(propertyName, rawValueList, false, false);
+ }
+
+ public void appendLine(final String propertyName,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ appendLine(propertyName, null, rawValue, needCharset, reallyUseQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue) {
+ appendLine(propertyName, parameterList, rawValue, false, false);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final String rawValue, final boolean needCharset,
+ boolean reallyUseQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+
+ final String encodedValue;
+ if (reallyUseQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard spec, though
+ // several (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(encodedValue);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ public void appendLine(final String propertyName, final List<String> rawValueList,
+ final boolean needCharset, boolean needQuotedPrintable) {
+ appendLine(propertyName, null, rawValueList, needCharset, needQuotedPrintable);
+ }
+
+ public void appendLine(final String propertyName, final List<String> parameterList,
+ final List<String> rawValueList, final boolean needCharset,
+ final boolean needQuotedPrintable) {
+ mBuilder.append(propertyName);
+ if (parameterList != null && parameterList.size() > 0) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ appendTypeParameters(parameterList);
+ }
+ if (needCharset) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(mVCardCharsetParameter);
+ }
+ if (needQuotedPrintable) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ mBuilder.append(VCARD_PARAM_ENCODING_QP);
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ boolean first = true;
+ for (String rawValue : rawValueList) {
+ final String encodedValue;
+ if (needQuotedPrintable) {
+ encodedValue = encodeQuotedPrintable(rawValue);
+ } else {
+ // TODO: one line may be too huge, which may be invalid in vCard 3.0
+ // (which says "When generating a content line, lines longer than
+ // 75 characters SHOULD be folded"), though several
+ // (even well-known) applications do not care this.
+ encodedValue = escapeCharacters(rawValue);
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ }
+ mBuilder.append(encodedValue);
+ }
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ /**
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
+ */
+ private void appendTypeParameters(final List<String> types) {
+ // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
+ // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
+ boolean first = true;
+ for (final String typeValue : types) {
+ // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
+ // we don't emit that kind of vCard 3.0 specific type since there should be
+ // high probabilyty in which external importers cannot understand them.
+ //
+ // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
+ // are quoted.)
+ if (!VCardUtils.isV21Word(typeValue)) {
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(typeValue);
+ }
+ }
/**
- * @param type
- * LANGUAGE \ ENCODING <br>
- * ;LANGUage= \ ;ENCODING=
+ * VCARD_PARAM_SEPARATOR must be appended before this method being called.
*/
- void propertyParamType(String type);
+ private void appendTypeParameter(final String type) {
+ appendTypeParameter(mBuilder, type);
+ }
+
+ private void appendTypeParameter(final StringBuilder builder, final String type) {
+ // Refrain from using appendType() so that "TYPE=" is not be appended when the
+ // device is DoCoMo's (just for safety).
+ //
+ // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
+ if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
+ }
+ builder.append(type);
+ }
/**
- * @param value
- * FR-EN \ GBK <br>
- * FR-EN \ GBK
+ * Returns true when the property line should contain charset parameter
+ * information. This method may return true even when vCard version is 3.0.
+ *
+ * Strictly, adding charset information is invalid in VCard 3.0.
+ * However we'll add the info only when charset we use is not UTF-8
+ * in vCard 3.0 format, since parser side may be able to use the charset
+ * via this field, though we may encounter another problem by adding it.
+ *
+ * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
+ * recommends UTF-8. By adding this field, parsers may be able
+ * to know this text is NOT UTF-8 but Shift_Jis.
*/
- void propertyParamValue(String value);
+ private boolean shouldAppendCharsetParam(String...propertyValueList) {
+ if (!mShouldAppendCharsetParam) {
+ return false;
+ }
+ for (String propertyValue : propertyValueList) {
+ if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String encodeQuotedPrintable(final String str) {
+ if (TextUtils.isEmpty(str)) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ int index = 0;
+ int lineCount = 0;
+ byte[] strArray = null;
+
+ try {
+ strArray = str.getBytes(mCharsetString);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ + "Try default charset");
+ strArray = str.getBytes();
+ }
+ while (index < strArray.length) {
+ builder.append(String.format("=%02X", strArray[index]));
+ index += 1;
+ lineCount += 3;
+
+ if (lineCount >= 67) {
+ // Specification requires CRLF must be inserted before the
+ // length of the line
+ // becomes more than 76.
+ // Assuming that the next character is a multi-byte character,
+ // it will become
+ // 6 bytes.
+ // 76 - 6 - 3 = 67
+ builder.append("=\r\n");
+ lineCount = 0;
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Append '\' to the characters which should be escaped. The character set is different
+ * not only between vCard 2.1 and vCard 3.0 but also among each device.
+ *
+ * Note that Quoted-Printable string must not be input here.
+ */
+ @SuppressWarnings("fallthrough")
+ private String escapeCharacters(final String unescaped) {
+ if (TextUtils.isEmpty(unescaped)) {
+ return "";
+ }
+
+ final StringBuilder tmpBuilder = new StringBuilder();
+ final int length = unescaped.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = unescaped.charAt(i);
+ switch (ch) {
+ case ';': {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(';');
+ break;
+ }
+ case '\r': {
+ if (i + 1 < length) {
+ char nextChar = unescaped.charAt(i);
+ if (nextChar == '\n') {
+ break;
+ } else {
+ // fall through
+ }
+ } else {
+ // fall through
+ }
+ }
+ case '\n': {
+ // In vCard 2.1, there's no specification about this, while
+ // vCard 3.0 explicitly requires this should be encoded to "\n".
+ tmpBuilder.append("\\n");
+ break;
+ }
+ case '\\': {
+ if (mIsV30) {
+ tmpBuilder.append("\\\\");
+ break;
+ } else {
+ // fall through
+ }
+ }
+ case '<':
+ case '>': {
+ if (mIsDoCoMo) {
+ tmpBuilder.append('\\');
+ tmpBuilder.append(ch);
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ case ',': {
+ if (mIsV30) {
+ tmpBuilder.append("\\,");
+ } else {
+ tmpBuilder.append(ch);
+ }
+ break;
+ }
+ default: {
+ tmpBuilder.append(ch);
+ break;
+ }
+ }
+ }
+ return tmpBuilder.toString();
+ }
- void propertyValues(List<String> values);
+ @Override
+ public String toString() {
+ if (!mEndAppended) {
+ if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
+ appendLine(VCardConstants.PROPERTY_X_REDUCTION, "");
+ appendLine(VCardConstants.PROPERTY_X_NO, "");
+ appendLine(VCardConstants.PROPERTY_X_DCM_HMN_MODE, "");
+ }
+ appendLine(VCardConstants.PROPERTY_END, VCARD_DATA_VCARD);
+ mEndAppended = true;
+ }
+ return mBuilder.toString();
+ }
}
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 7807595..0e8b665 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -24,12 +24,11 @@ import android.content.Entity.NamedContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
+import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -38,13 +37,10 @@ import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.telephony.PhoneNumberUtils;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.format.Time;
import android.util.CharsetUtils;
import android.util.Log;
@@ -55,13 +51,13 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
* <p>
@@ -74,19 +70,37 @@ import java.util.Set;
* Usually, this class should be used like this.
* </p>
*
- * <pre class="prettyprint"> VCardComposer composer = null; try { composer = new
- * VCardComposer(context); composer.addHandler(composer.new
- * HandlerForOutputStream(outputStream)); if (!composer.init()) { // Do
- * something handling the situation. return; } while (!composer.isAfterLast()) {
- * if (mCanceled) { // Assume a user may cancel this operation during the
- * export. return; } if (!composer.createOneEntry()) { // Do something handling
- * the error situation. return; } } } finally { if (composer != null) {
- * composer.terminate(); } } </pre>
+ * <pre class="prettyprint">VCardComposer composer = null;
+ * try {
+ * composer = new VCardComposer(context);
+ * composer.addHandler(
+ * composer.new HandlerForOutputStream(outputStream));
+ * if (!composer.init()) {
+ * // Do something handling the situation.
+ * return;
+ * }
+ * while (!composer.isAfterLast()) {
+ * if (mCanceled) {
+ * // Assume a user may cancel this operation during the export.
+ * return;
+ * }
+ * if (!composer.createOneEntry()) {
+ * // Do something handling the error situation.
+ * return;
+ * }
+ * }
+ * } finally {
+ * if (composer != null) {
+ * composer.terminate();
+ * }
+ * } </pre>
*/
public class VCardComposer {
- private static final String LOG_TAG = "vcard.VCardComposer";
+ private static final String LOG_TAG = "VCardComposer";
- private static final String DEFAULT_EMAIL_TYPE = Constants.ATTR_TYPE_INTERNET;
+ public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
+ public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
+ public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -97,31 +111,51 @@ public class VCardComposer {
public static final String FAILURE_REASON_NOT_INITIALIZED =
"The vCard composer object is not correctly initialized";
+ /** Should be visible only from developers... (no need to translate, hopefully) */
+ public static final String FAILURE_REASON_UNSUPPORTED_URI =
+ "The Uri vCard composer received is not supported by the composer.";
+
public static final String NO_ERROR = "No error";
- private static final Uri sDataRequestUri;
+ public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+
+ private static final String SHIFT_JIS = "SHIFT_JIS";
+ private static final String UTF_8 = "UTF-8";
+
+ /**
+ * Special URI for testing.
+ */
+ public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard";
+ public static final Uri VCARD_TEST_AUTHORITY_URI =
+ Uri.parse("content://" + VCARD_TEST_AUTHORITY);
+ public static final Uri CONTACTS_TEST_CONTENT_URI =
+ Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts");
+
+ private static final Map<Integer, String> sImMap;
static {
- Uri.Builder builder = RawContacts.CONTENT_URI.buildUpon();
- builder.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1");
- sDataRequestUri = builder.build();
+ sImMap = new HashMap<Integer, String>();
+ sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ // Google talk is a special case.
}
public static interface OneEntryHandler {
public boolean onInit(Context context);
-
public boolean onEntryCreated(String vcard);
-
public void onTerminate();
}
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one
- * by one.
+ * An useful example handler, which emits VCard String to outputstream one by one.
* </p>
* <p>
- * The input OutputStream object is closed() on {{@link #onTerminate()}.
+ * The input OutputStream object is closed() on {@link #onTerminate()}.
* Must not close the stream outside.
* </p>
*/
@@ -155,7 +189,11 @@ public class VCardComposer {
if (mIsDoCoMo) {
try {
// Create one empty entry.
- mWriter.write(createOneEntryInternal("-1"));
+ mWriter.write(createOneEntryInternal("-1", null));
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, "VCardException has been thrown during on Init(): " +
+ e.getMessage());
+ return false;
} catch (IOException e) {
Log.e(LOG_TAG,
"IOException occurred during exportOneContactData: "
@@ -213,233 +251,105 @@ public class VCardComposer {
}
}
- public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
- private static final String VCARD_PROPERTY_ADR = "ADR";
- private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
- private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
- private static final String VCARD_PROPERTY_END = "END";
- private static final String VCARD_PROPERTY_NAME = "N";
- private static final String VCARD_PROPERTY_FULL_NAME = "FN";
- private static final String VCARD_PROPERTY_NOTE = "NOTE";
- private static final String VCARD_PROPERTY_ORG = "ORG";
- private static final String VCARD_PROPERTY_SOUND = "SOUND";
- private static final String VCARD_PROPERTY_SORT_STRING = "SORT-STRING";
- private static final String VCARD_PROPERTY_NICKNAME = "NICKNAME";
- private static final String VCARD_PROPERTY_TEL = "TEL";
- private static final String VCARD_PROPERTY_TITLE = "TITLE";
- private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
- private static final String VCARD_PROPERTY_VERSION = "VERSION";
- private static final String VCARD_PROPERTY_URL = "URL";
- private static final String VCARD_PROPERTY_BIRTHDAY = "BDAY";
-
- private static final String VCARD_PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
- private static final String VCARD_PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
-
- // Android specific properties
- // TODO: ues extra MIME-TYPE instead of adding this kind of inflexible fields
- private static final String VCARD_PROPERTY_X_NICKNAME = "X-NICKNAME";
-
- // Property for call log entry
- private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME";
- private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING";
- private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING";
- private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED";
-
- // Properties for DoCoMo vCard.
- private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
- private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
- private static final String VCARD_PROPERTY_X_NO = "X-NO";
- private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
- private static final String VCARD_DATA_VCARD = "VCARD";
- private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
- private static final String VCARD_ATTR_SEPARATOR = ";";
- private static final String VCARD_COL_SEPARATOR = "\r\n";
- private static final String VCARD_DATA_SEPARATOR = ":";
- private static final String VCARD_ITEM_SEPARATOR = ";";
- private static final String VCARD_WS = " ";
- private static final String VCARD_ATTR_EQUAL = "=";
-
- // Type strings are now in VCardConstants.java.
-
- private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
- private static final String SHIFT_JIS = "SHIFT_JIS";
-
private final Context mContext;
private final int mVCardType;
private final boolean mCareHandlerErrors;
private final ContentResolver mContentResolver;
- // Convenient member variables about the restriction of the vCard format.
- // Used for not calling the same methods returning same results.
- private final boolean mIsV30;
- private final boolean mIsJapaneseMobilePhone;
- private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo;
- private final boolean mUsesQuotedPrintable;
- private final boolean mUsesAndroidProperty;
- private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
- private final boolean mUsesQPToPrimaryProperties;
-
private Cursor mCursor;
private int mIdColumn;
private final String mCharsetString;
- private final String mVCardAttributeCharset;
private boolean mTerminateIsCalled;
- final private List<OneEntryHandler> mHandlerList;
+ private final List<OneEntryHandler> mHandlerList;
private String mErrorReason = NO_ERROR;
- private static final Map<Integer, String> sImMap;
-
- static {
- sImMap = new HashMap<Integer, String>();
- sImMap.put(Im.PROTOCOL_AIM, Constants.PROPERTY_X_AIM);
- sImMap.put(Im.PROTOCOL_MSN, Constants.PROPERTY_X_MSN);
- sImMap.put(Im.PROTOCOL_YAHOO, Constants.PROPERTY_X_YAHOO);
- sImMap.put(Im.PROTOCOL_ICQ, Constants.PROPERTY_X_ICQ);
- sImMap.put(Im.PROTOCOL_JABBER, Constants.PROPERTY_X_JABBER);
- sImMap.put(Im.PROTOCOL_SKYPE, Constants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
- }
-
- private boolean mIsCallLogComposer = false;
-
- private boolean mNeedPhotoForVCard = true;
-
private static final String[] sContactsProjection = new String[] {
Contacts._ID,
};
- /** The projection to use when querying the call log table */
- private static final String[] sCallLogProjection = new String[] {
- Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
- Calls.CACHED_NUMBER_LABEL
- };
- private static final int NUMBER_COLUMN_INDEX = 0;
- private static final int DATE_COLUMN_INDEX = 1;
- private static final int CALL_TYPE_COLUMN_INDEX = 2;
- private static final int CALLER_NAME_COLUMN_INDEX = 3;
- private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4;
- private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5;
-
- private static final String FLAG_TIMEZONE_UTC = "Z";
-
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true, false, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
}
- public VCardComposer(Context context, String vcardTypeStr,
- boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr),
- careHandlerErrors, false, true);
+ public VCardComposer(Context context, int vcardType) {
+ this(context, vcardType, true);
}
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors) {
- this(context, vcardType, careHandlerErrors, false, true);
+ public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
+ this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
}
/**
* Construct for supporting call log entry vCard composing.
- *
- * @param isCallLogComposer true if this composer is for creating Call Log vCard.
*/
- public VCardComposer(Context context, int vcardType, boolean careHandlerErrors,
- boolean isCallLogComposer, boolean needPhotoInVCard) {
+ public VCardComposer(final Context context, final int vcardType,
+ final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
- mIsCallLogComposer = isCallLogComposer;
- mNeedPhotoForVCard = needPhotoInVCard;
mContentResolver = context.getContentResolver();
- mIsV30 = VCardConfig.isV30(vcardType);
- mUsesQuotedPrintable = VCardConfig.usesQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mIsJapaneseMobilePhone = VCardConfig
- .needsToConvertPhoneticString(vcardType);
- mOnlyOneNoteFieldIsAvailable = VCardConfig
- .onlyOneNoteFieldIsAvailable(vcardType);
- mUsesAndroidProperty = VCardConfig
- .usesAndroidSpecificProperty(vcardType);
- mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
- mUsesQPToPrimaryProperties = VCardConfig.usesQPToPrimaryProperties(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
- // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
- // Android, not shown to the public).
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
} else if (mUsesShiftJis) {
- mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
+ String charset;
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ mCharsetString = charset;
} else {
- mCharsetString = "UTF-8";
- mVCardAttributeCharset = "CHARSET=UTF-8";
+ mCharsetString = UTF_8;
}
}
/**
- * This static function is to compose vCard for phone own number
+ * Must be called before {@link #init()}.
*/
- public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName,
- String phoneNumber, boolean vcardVer21) {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (!vcardVer21) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- boolean needCharset = false;
- if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) {
- needCharset = true;
- }
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, phoneName, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, phoneName, needCharset, false);
-
- if (!TextUtils.isEmpty(phoneNumber)) {
- String label = Integer.toString(phonetype);
- appendVCardTelephoneLine(builder, phonetype, label, phoneNumber);
+ public void addHandler(OneEntryHandler handler) {
+ if (handler != null) {
+ mHandlerList.add(handler);
}
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
- return builder.toString();
}
/**
- * Must call before {{@link #init()}.
+ * @return Returns true when initialization is successful and all the other
+ * methods are available. Returns false otherwise.
*/
- public void addHandler(OneEntryHandler handler) {
- mHandlerList.add(handler);
- }
-
public boolean init() {
return init(null, null);
}
+ public boolean init(final String selection, final String[] selectionArgs) {
+ return init(Contacts.CONTENT_URI, selection, selectionArgs, null);
+ }
+
/**
- * @return Returns true when initialization is successful and all the other
- * methods are available. Returns false otherwise.
+ * Note that this is unstable interface, may be deleted in the future.
*/
- public boolean init(final String selection, final String[] selectionArgs) {
+ public boolean init(final Uri contentUri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (contentUri == null) {
+ return false;
+ }
+
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -458,13 +368,16 @@ public class VCardComposer {
}
}
- if (mIsCallLogComposer) {
- mCursor = mContentResolver.query(CallLog.Calls.CONTENT_URI, sCallLogProjection,
- selection, selectionArgs, null);
+ final String[] projection;
+ if (Contacts.CONTENT_URI.equals(contentUri) ||
+ CONTACTS_TEST_CONTENT_URI.equals(contentUri)) {
+ projection = sContactsProjection;
} else {
- mCursor = mContentResolver.query(Contacts.CONTENT_URI, sContactsProjection,
- selection, selectionArgs, null);
+ mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
+ return false;
}
+ mCursor = mContentResolver.query(
+ contentUri, projection, selection, selectionArgs, sortOrder);
if (mCursor == null) {
mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
@@ -483,38 +396,40 @@ public class VCardComposer {
return false;
}
- if (mIsCallLogComposer) {
- mIdColumn = -1;
- } else {
- mIdColumn = mCursor.getColumnIndex(Contacts._ID);
- }
+ mIdColumn = mCursor.getColumnIndex(Contacts._ID);
return true;
}
public boolean createOneEntry() {
+ return createOneEntry(null);
+ }
+
+ /**
+ * @param getEntityIteratorMethod For Dependency Injection.
+ * @hide just for testing.
+ */
+ public boolean createOneEntry(Method getEntityIteratorMethod) {
if (mCursor == null || mCursor.isAfterLast()) {
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String name = null;
String vcard;
try {
- if (mIsCallLogComposer) {
- vcard = createOneCallLogEntryInternal();
+ if (mIdColumn >= 0) {
+ vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
+ getEntityIteratorMethod);
} else {
- if (mIdColumn >= 0) {
- vcard = createOneEntryInternal(mCursor.getString(mIdColumn));
- } else {
- Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
- return true;
- }
+ Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn);
+ return true;
}
+ } catch (VCardException e) {
+ Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage());
+ return false;
} catch (OutOfMemoryError error) {
// Maybe some data (e.g. photo) is too big to have in memory. But it
// should be rare.
- Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: "
- + name);
+ Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry.");
System.gc();
// TODO: should tell users what happened?
return true;
@@ -541,107 +456,58 @@ public class VCardComposer {
return true;
}
- /**
- * Format according to RFC 2445 DATETIME type.
- * The format is: ("%Y%m%dT%H%M%SZ").
- */
- private final String toRfc2455Format(final long millSecs) {
- Time startDate = new Time();
- startDate.set(millSecs);
- String date = startDate.format2445();
- return date + FLAG_TIMEZONE_UTC;
- }
-
- /**
- * Try to append the property line for a call history time stamp field if possible.
- * Do nothing if the call log type gotton from the database is invalid.
- */
- private void tryAppendCallHistoryTimeStampField(final StringBuilder builder) {
- // Extension for call history as defined in
- // in the Specification for Ic Mobile Communcation - ver 1.1,
- // Oct 2000. This is used to send the details of the call
- // history - missed, incoming, outgoing along with date and time
- // to the requesting device (For example, transferring phone book
- // when connected over bluetooth)
- //
- // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z"
- final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX);
- final String callLogTypeStr;
- switch (callLogType) {
- case Calls.INCOMING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING;
- break;
- }
- case Calls.OUTGOING_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING;
- break;
- }
- case Calls.MISSED_TYPE: {
- callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED;
- break;
- }
- default: {
- Log.w(LOG_TAG, "Call log type not correct.");
- return;
- }
- }
-
- final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX);
- builder.append(VCARD_PROPERTY_X_TIMESTAMP);
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, callLogTypeStr);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(toRfc2455Format(dateAsLong));
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private String createOneCallLogEntryInternal() {
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
- String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX);
- if (TextUtils.isEmpty(name)) {
- name = mCursor.getString(NUMBER_COLUMN_INDEX);
- }
- final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name));
- // TODO: QP should be used? Using mUsesQPToPrimaryProperties should help.
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, name, needCharset, false);
- appendVCardLine(builder, VCARD_PROPERTY_NAME, name, needCharset, false);
-
- String number = mCursor.getString(NUMBER_COLUMN_INDEX);
- int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
- String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
- if (TextUtils.isEmpty(label)) {
- label = Integer.toString(type);
- }
- appendVCardTelephoneLine(builder, type, label, number);
- tryAppendCallHistoryTimeStampField(builder);
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
- return builder.toString();
- }
-
- private String createOneEntryInternal(final String contactId) {
+ private String createOneEntryInternal(final String contactId,
+ Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
- final String selection = Data.CONTACT_ID + "=?";
- final String[] selectionArgs = new String[] {contactId};
- // The resolver may return the entity iterator with no data. It is possiible.
+ // The resolver may return the entity iterator with no data. It is possible.
// e.g. If all the data in the contact of the given contact id are not exportable ones,
// they are hidden from the view of this method, though contact id itself exists.
- boolean dataExists = false;
EntityIterator entityIterator = null;
try {
- entityIterator = mContentResolver.queryEntities(
- sDataRequestUri, selection, selectionArgs, null);
- dataExists = entityIterator.hasNext();
+ final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
+ .build();
+ final String selection = Data.CONTACT_ID + "=?";
+ final String[] selectionArgs = new String[] {contactId};
+ if (getEntityIteratorMethod != null) {
+ // Please note that this branch is executed by some tests only
+ try {
+ entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
+ mContentResolver, uri, selection, selectionArgs, null);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " +
+ e.getMessage());
+ } catch (IllegalAccessException e) {
+ Log.e(LOG_TAG, "IllegalAccessException has been thrown: " +
+ e.getMessage());
+ } catch (InvocationTargetException e) {
+ Log.e(LOG_TAG, "InvocationTargetException has been thrown: ");
+ StackTraceElement[] stackTraceElements = e.getCause().getStackTrace();
+ for (StackTraceElement element : stackTraceElements) {
+ Log.e(LOG_TAG, " at " + element.toString());
+ }
+ throw new VCardException("InvocationTargetException has been thrown: " +
+ e.getCause().getMessage());
+ }
+ } else {
+ entityIterator = RawContacts.newEntityIterator(mContentResolver.query(
+ uri, null, selection, selectionArgs, null));
+ }
+
+ if (entityIterator == null) {
+ Log.e(LOG_TAG, "EntityIterator is null");
+ return "";
+ }
+
+ if (!entityIterator.hasNext()) {
+ Log.w(LOG_TAG, "Data does not exist. contactId: " + contactId);
+ return "";
+ }
+
while (entityIterator.hasNext()) {
Entity entity = entityIterator.next();
- for (NamedContentValues namedContentValues : entity
- .getSubValues()) {
+ for (NamedContentValues namedContentValues : entity.getSubValues()) {
ContentValues contentValues = namedContentValues.values;
String key = contentValues.getAsString(Data.MIMETYPE);
if (key != null) {
@@ -655,52 +521,27 @@ public class VCardComposer {
}
}
}
- } catch (RemoteException e) {
- Log.e(LOG_TAG, String.format("RemoteException at id %s (%s)",
- contactId, e.getMessage()));
- return "";
} finally {
if (entityIterator != null) {
entityIterator.close();
}
}
- if (!dataExists) {
- return "";
+ final VCardBuilder builder = new VCardBuilder(mVCardType);
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
+ if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
+ builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
}
-
- final StringBuilder builder = new StringBuilder();
- appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V30);
- } else {
- appendVCardLine(builder, VCARD_PROPERTY_VERSION, Constants.VERSION_V21);
- }
-
- appendStructuredNames(builder, contentValuesListMap);
- appendNickNames(builder, contentValuesListMap);
- appendPhones(builder, contentValuesListMap);
- appendEmails(builder, contentValuesListMap);
- appendPostals(builder, contentValuesListMap);
- appendIms(builder, contentValuesListMap);
- appendWebsites(builder, contentValuesListMap);
- appendBirthday(builder, contentValuesListMap);
- appendOrganizations(builder, contentValuesListMap);
- if (mNeedPhotoForVCard) {
- appendPhotos(builder, contentValuesListMap);
- }
- appendNotes(builder, contentValuesListMap);
- // TODO: GroupMembership
-
- if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
- appendVCardLine(builder, VCARD_PROPERTY_X_REDUCTION, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_NO, "");
- appendVCardLine(builder, VCARD_PROPERTY_X_DCM_HMN_MODE, "");
- }
-
- appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
+ builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
}
@@ -713,8 +554,7 @@ public class VCardComposer {
try {
mCursor.close();
} catch (SQLiteException e) {
- Log.e(LOG_TAG, "SQLiteException on Cursor#close(): "
- + e.getMessage());
+ Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
}
mCursor = null;
}
@@ -749,1303 +589,4 @@ public class VCardComposer {
public String getErrorReason() {
return mErrorReason;
}
-
- private void appendStructuredNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredName.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- appendStructuredNamesInternal(builder, contentValuesList);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- } else if (mIsV30) {
- // vCard 3.0 requires "N" and "FN" properties.
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
- }
- }
-
- private boolean containsNonEmptyName(ContentValues contentValues) {
- final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
- final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
- final String prefix = contentValues.getAsString(StructuredName.PREFIX);
- final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
- final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
- return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
- TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
- TextUtils.isEmpty(suffix) && TextUtils.isEmpty(displayName));
- }
-
- private void appendStructuredNamesInternal(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- // For safety, we'll emit just one value around StructuredName, as external importers
- // may get confused with multiple "N", "FN", etc. properties, though it is valid in
- // vCard spec.
- ContentValues primaryContentValues = null;
- ContentValues subprimaryContentValues = null;
- for (ContentValues contentValues : contentValuesList) {
- if (contentValues == null){
- continue;
- }
- Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
- if (isSuperPrimary != null && isSuperPrimary > 0) {
- // We choose "super primary" ContentValues.
- primaryContentValues = contentValues;
- break;
- } else if (primaryContentValues == null) {
- // We choose the first "primary" ContentValues
- // if "super primary" ContentValues does not exist.
- Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
- if (isPrimary != null && isPrimary > 0 &&
- containsNonEmptyName(contentValues)) {
- primaryContentValues = contentValues;
- // Do not break, since there may be ContentValues with "super primary"
- // afterword.
- } else if (subprimaryContentValues == null &&
- containsNonEmptyName(contentValues)) {
- subprimaryContentValues = contentValues;
- }
- }
- }
-
- if (primaryContentValues == null) {
- if (subprimaryContentValues != null) {
- // We choose the first ContentValues if any "primary" ContentValues does not exist.
- primaryContentValues = subprimaryContentValues;
- } else {
- Log.e(LOG_TAG, "All ContentValues given from database is empty.");
- primaryContentValues = new ContentValues();
- }
- }
-
- final String familyName = primaryContentValues
- .getAsString(StructuredName.FAMILY_NAME);
- final String middleName = primaryContentValues
- .getAsString(StructuredName.MIDDLE_NAME);
- final String givenName = primaryContentValues
- .getAsString(StructuredName.GIVEN_NAME);
- final String prefix = primaryContentValues
- .getAsString(StructuredName.PREFIX);
- final String suffix = primaryContentValues
- .getAsString(StructuredName.SUFFIX);
- final String displayName = primaryContentValues
- .getAsString(StructuredName.DISPLAY_NAME);
-
- if (!TextUtils.isEmpty(familyName) || !TextUtils.isEmpty(givenName)) {
- final String encodedFamily;
- final String encodedGiven;
- final String encodedMiddle;
- final String encodedPrefix;
- final String encodedSuffix;
-
- final boolean reallyUseQuotedPrintableToName =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(familyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(givenName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(middleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(prefix) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(suffix)));
-
- if (reallyUseQuotedPrintableToName) {
- encodedFamily = encodeQuotedPrintable(familyName);
- encodedGiven = encodeQuotedPrintable(givenName);
- encodedMiddle = encodeQuotedPrintable(middleName);
- encodedPrefix = encodeQuotedPrintable(prefix);
- encodedSuffix = encodeQuotedPrintable(suffix);
- } else {
- encodedFamily = escapeCharacters(familyName);
- encodedGiven = escapeCharacters(givenName);
- encodedMiddle = escapeCharacters(middleName);
- encodedPrefix = escapeCharacters(prefix);
- encodedSuffix = escapeCharacters(suffix);
- }
-
- // N property. This order is specified by vCard spec and does not depend on countries.
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(Arrays.asList(
- familyName, givenName, middleName, prefix, suffix))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFamily);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedGiven);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedMiddle);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPrefix);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedSuffix);
- builder.append(VCARD_COL_SEPARATOR);
-
- final String fullname = VCardUtils.constructNameFromElements(
- VCardConfig.getNameOrderType(mVCardType),
- encodedFamily, encodedMiddle, encodedGiven, encodedPrefix, encodedSuffix);
- final boolean reallyUseQuotedPrintableToFullname =
- mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(fullname);
-
- final String encodedFullname =
- reallyUseQuotedPrintableToFullname ?
- encodeQuotedPrintable(fullname) :
- escapeCharacters(fullname);
-
- // FN property
- builder.append(VCARD_PROPERTY_FULL_NAME);
- if (shouldAppendCharsetAttribute(encodedFullname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToFullname) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedFullname);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (!TextUtils.isEmpty(displayName)) {
- final boolean reallyUseQuotedPrintableToDisplayName =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(displayName));
- final String encodedDisplayName =
- reallyUseQuotedPrintableToDisplayName ?
- encodeQuotedPrintable(displayName) :
- escapeCharacters(displayName);
-
- builder.append(VCARD_PROPERTY_NAME);
- if (shouldAppendCharsetAttribute(encodedDisplayName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintableToDisplayName) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedDisplayName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- } else if (mIsDoCoMo) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- } else if (mIsV30) {
- appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
- appendVCardLine(builder, VCARD_PROPERTY_FULL_NAME, "");
- }
-
- String phoneticFamilyName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_FAMILY_NAME);
- String phoneticMiddleName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
- String phoneticGivenName = primaryContentValues
- .getAsString(StructuredName.PHONETIC_GIVEN_NAME);
- if (!(TextUtils.isEmpty(phoneticFamilyName)
- && TextUtils.isEmpty(phoneticMiddleName) &&
- TextUtils.isEmpty(phoneticGivenName))) { // if not empty
- if (mIsJapaneseMobilePhone) {
- phoneticFamilyName = VCardUtils
- .toHalfWidthString(phoneticFamilyName);
- phoneticMiddleName = VCardUtils
- .toHalfWidthString(phoneticMiddleName);
- phoneticGivenName = VCardUtils
- .toHalfWidthString(phoneticGivenName);
- }
-
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
- phoneticFamilyName,
- phoneticMiddleName,
- phoneticGivenName);
- builder.append(VCARD_PROPERTY_SORT_STRING);
-
- // Do not need to care about QP, since vCard 3.0 does not allow it.
- final String encodedSortString = escapeCharacters(sortString);
- if (shouldAppendCharsetAttribute(encodedSortString)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedSortString);
- builder.append(VCARD_COL_SEPARATOR);
- } else {
- // Note: There is no appropriate property for expressing
- // phonetic name in vCard 2.1, while there is in
- // vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
-
- boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !(VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticFamilyName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticMiddleName) &&
- VCardUtils.containsOnlyNonCrLfPrintableAscii(
- phoneticGivenName)));
-
- final String encodedPhoneticFamilyName;
- final String encodedPhoneticMiddleName;
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
-
- if (shouldAppendCharsetAttribute(Arrays.asList(
- encodedPhoneticFamilyName, encodedPhoneticMiddleName,
- encodedPhoneticGivenName))) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
- } else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_SOUND);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_X_IRMC_N);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- if (mUsesDefactProperty) {
- if (!TextUtils.isEmpty(phoneticGivenName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticGivenName));
- final String encodedPhoneticGivenName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticGivenName = encodeQuotedPrintable(phoneticGivenName);
- } else {
- encodedPhoneticGivenName = escapeCharacters(phoneticGivenName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_FIRST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticGivenName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticGivenName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticMiddleName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticMiddleName));
- final String encodedPhoneticMiddleName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticMiddleName = encodeQuotedPrintable(phoneticMiddleName);
- } else {
- encodedPhoneticMiddleName = escapeCharacters(phoneticMiddleName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_MIDDLE_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticMiddleName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticMiddleName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- if (!TextUtils.isEmpty(phoneticFamilyName)) {
- final boolean reallyUseQuotedPrintable =
- (mUsesQPToPrimaryProperties &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(phoneticFamilyName));
- final String encodedPhoneticFamilyName;
- if (reallyUseQuotedPrintable) {
- encodedPhoneticFamilyName = encodeQuotedPrintable(phoneticFamilyName);
- } else {
- encodedPhoneticFamilyName = escapeCharacters(phoneticFamilyName);
- }
- builder.append(VCARD_PROPERTY_X_PHONETIC_LAST_NAME);
- if (shouldAppendCharsetAttribute(encodedPhoneticFamilyName)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedPhoneticFamilyName);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
- }
-
- private void appendNickNames(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Nickname.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- final String propertyNickname;
- if (mIsV30) {
- propertyNickname = VCARD_PROPERTY_NICKNAME;
- } else if (mUsesAndroidProperty) {
- propertyNickname = VCARD_PROPERTY_X_NICKNAME;
- } else {
- // There's no way to add this field.
- return;
- }
-
- for (ContentValues contentValues : contentValuesList) {
- final String nickname = contentValues.getAsString(Nickname.NAME);
- if (TextUtils.isEmpty(nickname)) {
- continue;
- }
-
- final String encodedNickname;
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(nickname));
- if (reallyUseQuotedPrintable) {
- encodedNickname = encodeQuotedPrintable(nickname);
- } else {
- encodedNickname = escapeCharacters(nickname);
- }
-
- builder.append(propertyNickname);
- if (shouldAppendCharsetAttribute(propertyNickname)) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
- if (reallyUseQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- }
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedNickname);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
- }
-
- private void appendPhones(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Phone.CONTENT_ITEM_TYPE);
- boolean phoneLineExists = false;
- if (contentValuesList != null) {
- Set<String> phoneSet = new HashSet<String>();
- for (ContentValues contentValues : contentValuesList) {
- final Integer typeAsObject = contentValues.getAsInteger(Phone.TYPE);
- final String label = contentValues.getAsString(Phone.LABEL);
- String phoneNumber = contentValues.getAsString(Phone.NUMBER);
- if (phoneNumber != null) {
- phoneNumber = phoneNumber.trim();
- }
- if (TextUtils.isEmpty(phoneNumber)) {
- continue;
- }
- int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
-
- phoneLineExists = true;
- if (type == Phone.TYPE_PAGER) {
- phoneLineExists = true;
- if (!phoneSet.contains(phoneNumber)) {
- phoneSet.add(phoneNumber);
- appendVCardTelephoneLine(builder, type, label, phoneNumber);
- }
- } else {
- // The entry "may" have several phone numbers when the contact entry is
- // corrupted because of its original source.
- //
- // e.g. I encountered the entry like the following.
- // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
- // This kind of entry is not able to be inserted via Android devices, but
- // possible if the source of the data is already corrupted.
- List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
- if (phoneNumberList.isEmpty()) {
- continue;
- }
- phoneLineExists = true;
- for (String actualPhoneNumber : phoneNumberList) {
- if (!phoneSet.contains(actualPhoneNumber)) {
- final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
- SpannableStringBuilder tmpBuilder =
- new SpannableStringBuilder(actualPhoneNumber);
- PhoneNumberUtils.formatNumber(tmpBuilder, format);
- final String formattedPhoneNumber = tmpBuilder.toString();
- phoneSet.add(actualPhoneNumber);
- appendVCardTelephoneLine(builder, type, label, formattedPhoneNumber);
- }
- }
- }
- }
- }
-
- if (!phoneLineExists && mIsDoCoMo) {
- appendVCardTelephoneLine(builder, Phone.TYPE_HOME, "", "");
- }
- }
-
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- List<String> phoneList = new ArrayList<String>();
-
- StringBuilder builder = new StringBuilder();
- final int length = phoneNumber.length();
- for (int i = 0; i < length; i++) {
- final char ch = phoneNumber.charAt(i);
- if (Character.isDigit(ch)) {
- builder.append(ch);
- } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
- phoneList.add(builder.toString());
- builder = new StringBuilder();
- }
- }
- if (builder.length() > 0) {
- phoneList.add(builder.toString());
- }
-
- return phoneList;
- }
-
- private void appendEmails(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Email.CONTENT_ITEM_TYPE);
- boolean emailAddressExists = false;
- if (contentValuesList != null) {
- Set<String> addressSet = new HashSet<String>();
- for (ContentValues contentValues : contentValuesList) {
- Integer typeAsObject = contentValues.getAsInteger(Email.TYPE);
- final int type = (typeAsObject != null ?
- typeAsObject : Email.TYPE_OTHER);
- final String label = contentValues.getAsString(Email.LABEL);
- String emailAddress = contentValues.getAsString(Email.DATA);
- if (emailAddress != null) {
- emailAddress = emailAddress.trim();
- }
- if (TextUtils.isEmpty(emailAddress)) {
- continue;
- }
- emailAddressExists = true;
- if (!addressSet.contains(emailAddress)) {
- addressSet.add(emailAddress);
- appendVCardEmailLine(builder, type, label, emailAddress);
- }
- }
- }
-
- if (!emailAddressExists && mIsDoCoMo) {
- appendVCardEmailLine(builder, Email.TYPE_HOME, "", "");
- }
- }
-
- private void appendPostals(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(StructuredPostal.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mIsDoCoMo) {
- appendPostalsForDoCoMo(builder, contentValuesList);
- } else {
- appendPostalsForGeneric(builder, contentValuesList);
- }
- } else if (mIsDoCoMo) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(Constants.ATTR_TYPE_HOME);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
- }
-
- /**
- * Tries to append just one line. If there's no appropriate address
- * information, append an empty line.
- */
- private void appendPostalsForDoCoMo(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- // TODO: from old, inefficient code. fix this.
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_HOME)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_WORK)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_OTHER)) {
- return;
- }
- if (appendPostalsForDoCoMoInternal(builder, contentValuesList,
- StructuredPostal.TYPE_CUSTOM)) {
- return;
- }
-
- Log.w(LOG_TAG,
- "Should not come here. Must have at least one postal data.");
- }
-
- private boolean appendPostalsForDoCoMoInternal(final StringBuilder builder,
- final List<ContentValues> contentValuesList, Integer preferedType) {
- for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type == preferedType) {
- appendVCardPostalLine(builder, type, label, contentValues);
- return true;
- }
- }
- return false;
- }
-
- private void appendPostalsForGeneric(final StringBuilder builder,
- final List<ContentValues> contentValuesList) {
- for (ContentValues contentValues : contentValuesList) {
- final Integer type = contentValues.getAsInteger(StructuredPostal.TYPE);
- final String label = contentValues.getAsString(StructuredPostal.LABEL);
- if (type != null) {
- appendVCardPostalLine(builder, type, label, contentValues);
- }
- }
- }
-
- private void appendIms(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Im.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- Integer protocol = contentValues.getAsInteger(Im.PROTOCOL);
- String data = contentValues.getAsString(Im.DATA);
- if (data != null) {
- data = data.trim();
- }
- if (TextUtils.isEmpty(data)) {
- continue;
- }
-
- if (protocol != null && protocol == Im.PROTOCOL_GOOGLE_TALK) {
- if (VCardConfig.usesAndroidSpecificProperty(mVCardType)) {
- appendVCardLine(builder, Constants.PROPERTY_X_GOOGLE_TALK, data);
- }
- // TODO: add "X-GOOGLE TALK" case...
- }
- }
- }
- }
-
- private void appendWebsites(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Website.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String website = contentValues.getAsString(Website.URL);
- if (website != null) {
- website = website.trim();
- }
- if (!TextUtils.isEmpty(website)) {
- appendVCardLine(builder, VCARD_PROPERTY_URL, website);
- }
- }
- }
- }
-
- private void appendBirthday(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Event.CONTENT_ITEM_TYPE);
- if (contentValuesList != null && contentValuesList.size() > 0) {
- Integer eventType = contentValuesList.get(0).getAsInteger(Event.TYPE);
- if (eventType == null || !eventType.equals(Event.TYPE_BIRTHDAY)) {
- return;
- }
- // Theoretically, there must be only one birthday for each vCard data and
- // we are afraid of some parse error occuring in some devices, so
- // we emit only one birthday entry for now.
- String birthday = contentValuesList.get(0).getAsString(Event.START_DATE);
- if (birthday != null) {
- birthday = birthday.trim();
- }
- if (!TextUtils.isEmpty(birthday)) {
- appendVCardLine(builder, VCARD_PROPERTY_BIRTHDAY, birthday);
- }
- }
- }
-
- private void appendOrganizations(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Organization.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- String company = contentValues
- .getAsString(Organization.COMPANY);
- if (company != null) {
- company = company.trim();
- }
- String title = contentValues
- .getAsString(Organization.TITLE);
- if (title != null) {
- title = title.trim();
- }
-
- if (!TextUtils.isEmpty(company)) {
- appendVCardLine(builder, VCARD_PROPERTY_ORG, company,
- !VCardUtils.containsOnlyPrintableAscii(company),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(company)));
- }
- if (!TextUtils.isEmpty(title)) {
- appendVCardLine(builder, VCARD_PROPERTY_TITLE, title,
- !VCardUtils.containsOnlyPrintableAscii(title),
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(title)));
- }
- }
- }
- }
-
- private void appendPhotos(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList = contentValuesListMap
- .get(Photo.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- for (ContentValues contentValues : contentValuesList) {
- byte[] data = contentValues.getAsByteArray(Photo.PHOTO);
- if (data == null) {
- continue;
- }
- final String photoType;
- // Use some heuristics for guessing the format of the image.
- // TODO: there should be some general API for detecting the file format.
- if (data.length >= 3 && data[0] == 'G' && data[1] == 'I'
- && data[2] == 'F') {
- photoType = "GIF";
- } else if (data.length >= 4 && data[0] == (byte) 0x89
- && data[1] == 'P' && data[2] == 'N' && data[3] == 'G') {
- // Note: vCard 2.1 officially does not support PNG, but we
- // may have it
- // and using X- word like "X-PNG" may not let importers know
- // it is
- // PNG. So we use the String "PNG" as is...
- photoType = "PNG";
- } else if (data.length >= 2 && data[0] == (byte) 0xff
- && data[1] == (byte) 0xd8) {
- photoType = "JPEG";
- } else {
- Log.d(LOG_TAG, "Unknown photo type. Ignore.");
- continue;
- }
- final String photoString = VCardUtils.encodeBase64(data);
- if (photoString.length() > 0) {
- appendVCardPhotoLine(builder, photoString, photoType);
- }
- }
- }
- }
-
- private void appendNotes(final StringBuilder builder,
- final Map<String, List<ContentValues>> contentValuesListMap) {
- final List<ContentValues> contentValuesList =
- contentValuesListMap.get(Note.CONTENT_ITEM_TYPE);
- if (contentValuesList != null) {
- if (mOnlyOneNoteFieldIsAvailable) {
- StringBuilder noteBuilder = new StringBuilder();
- boolean first = true;
- for (ContentValues contentValues : contentValuesList) {
- String note = contentValues.getAsString(Note.NOTE);
- if (note == null) {
- note = "";
- }
- if (note.length() > 0) {
- if (first) {
- first = false;
- } else {
- noteBuilder.append('\n');
- }
- noteBuilder.append(note);
- }
- }
- final String noteStr = noteBuilder.toString();
- // This means we scan noteStr completely twice, which is redundant.
- // But for now, we assume this is not so time-consuming..
- final boolean shouldAppendCharsetInfo =
- !VCardUtils.containsOnlyPrintableAscii(noteStr);
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- } else {
- for (ContentValues contentValues : contentValuesList) {
- final String noteStr = contentValues.getAsString(Note.NOTE);
- if (!TextUtils.isEmpty(noteStr)) {
- final boolean shouldAppendCharsetInfo =
- !VCardUtils.containsOnlyPrintableAscii(noteStr);
- final boolean reallyUseQuotedPrintable =
- (mUsesQuotedPrintable &&
- !VCardUtils.containsOnlyNonCrLfPrintableAscii(noteStr));
- appendVCardLine(builder, VCARD_PROPERTY_NOTE, noteStr,
- shouldAppendCharsetInfo, reallyUseQuotedPrintable);
- }
- }
- }
- }
- }
-
- /**
- * Append '\' to the characters which should be escaped. The character set is different
- * not only between vCard 2.1 and vCard 3.0 but also among each device.
- *
- * Note that Quoted-Printable string must not be input here.
- */
- @SuppressWarnings("fallthrough")
- private String escapeCharacters(final String unescaped) {
- if (TextUtils.isEmpty(unescaped)) {
- return "";
- }
-
- final StringBuilder tmpBuilder = new StringBuilder();
- final int length = unescaped.length();
- for (int i = 0; i < length; i++) {
- char ch = unescaped.charAt(i);
- switch (ch) {
- case ';': {
- tmpBuilder.append('\\');
- tmpBuilder.append(';');
- break;
- }
- case '\r': {
- if (i + 1 < length) {
- char nextChar = unescaped.charAt(i);
- if (nextChar == '\n') {
- continue;
- } else {
- // fall through
- }
- } else {
- // fall through
- }
- }
- case '\n': {
- // In vCard 2.1, there's no specification about this, while
- // vCard 3.0 explicitly requires this should be encoded to "\n".
- tmpBuilder.append("\\n");
- break;
- }
- case '\\': {
- if (mIsV30) {
- tmpBuilder.append("\\\\");
- break;
- } else {
- // fall through
- }
- }
- case '<':
- case '>': {
- if (mIsDoCoMo) {
- tmpBuilder.append('\\');
- tmpBuilder.append(ch);
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- case ',': {
- if (mIsV30) {
- tmpBuilder.append("\\,");
- } else {
- tmpBuilder.append(ch);
- }
- break;
- }
- default: {
- tmpBuilder.append(ch);
- break;
- }
- }
- }
- return tmpBuilder.toString();
- }
-
- private void appendVCardPhotoLine(final StringBuilder builder,
- final String encodedData, final String photoType) {
- StringBuilder tmpBuilder = new StringBuilder();
- tmpBuilder.append(VCARD_PROPERTY_PHOTO);
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- if (mIsV30) {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V30);
- } else {
- tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
- }
- tmpBuilder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(tmpBuilder, photoType);
- tmpBuilder.append(VCARD_DATA_SEPARATOR);
- tmpBuilder.append(encodedData);
-
- final String tmpStr = tmpBuilder.toString();
- tmpBuilder = new StringBuilder();
- int lineCount = 0;
- int length = tmpStr.length();
- for (int i = 0; i < length; i++) {
- tmpBuilder.append(tmpStr.charAt(i));
- lineCount++;
- if (lineCount > 72) {
- tmpBuilder.append(VCARD_COL_SEPARATOR);
- tmpBuilder.append(VCARD_WS);
- lineCount = 0;
- }
- }
- builder.append(tmpBuilder.toString());
- builder.append(VCARD_COL_SEPARATOR);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardPostalLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- final ContentValues contentValues) {
- builder.append(VCARD_PROPERTY_ADR);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- // Note: Not sure why we need to emit "empty" line even when actual data does not exist.
- // There may be some reason or may not be any. We keep safer side.
- // TODO: investigate this.
- boolean dataExists = false;
- String[] dataArray = VCardUtils.getVCardPostalElements(contentValues);
- boolean actuallyUseQuotedPrintable = false;
- boolean shouldAppendCharset = false;
- for (String data : dataArray) {
- if (!TextUtils.isEmpty(data)) {
- dataExists = true;
- if (!shouldAppendCharset && !VCardUtils.containsOnlyPrintableAscii(data)) {
- shouldAppendCharset = true;
- }
- if (mUsesQuotedPrintable && !VCardUtils.containsOnlyNonCrLfPrintableAscii(data)) {
- actuallyUseQuotedPrintable = true;
- break;
- }
- }
- }
-
- int length = dataArray.length;
- for (int i = 0; i < length; i++) {
- String data = dataArray[i];
- if (!TextUtils.isEmpty(data)) {
- if (actuallyUseQuotedPrintable) {
- dataArray[i] = encodeQuotedPrintable(data);
- } else {
- dataArray[i] = escapeCharacters(data);
- }
- }
- }
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = StructuredPostal.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- String typeAsString = null;
- switch (typeAsPrimitive) {
- case StructuredPostal.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
- break;
- }
- case StructuredPostal.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
- break;
- }
- case StructuredPostal.TYPE_CUSTOM: {
- if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- // We're not sure whether the label is valid in the spec
- // ("IANA-token" in the vCard 3.0 is unclear...)
- // Just for safety, we add "X-" at the beggining of each label.
- // Also checks the label obeys with vCard 3.0 spec.
- builder.append("X-");
- builder.append(label);
- builder.append(VCARD_DATA_SEPARATOR);
- }
- break;
- }
- case StructuredPostal.TYPE_OTHER: {
- break;
- }
- default: {
- Log.e(LOG_TAG, "Unknown StructuredPostal type: " + typeAsPrimitive);
- break;
- }
- }
-
- // Attribute(s).
-
- {
- boolean shouldAppendAttrSeparator = false;
- if (typeAsString != null) {
- appendTypeAttribute(builder, typeAsString);
- shouldAppendAttrSeparator = true;
- }
-
- if (dataExists) {
- if (shouldAppendCharset) {
- // Strictly, vCard 3.0 does not allow exporters to emit charset information,
- // but we will add it since the information should be useful for importers,
- //
- // Assume no parser does not emit error with this attribute in vCard 3.0.
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(mVCardAttributeCharset);
- shouldAppendAttrSeparator = true;
- }
-
- if (actuallyUseQuotedPrintable) {
- if (shouldAppendAttrSeparator) {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- builder.append(VCARD_ATTR_ENCODING_QP);
- shouldAppendAttrSeparator = true;
- }
- }
- }
-
- // Property values.
-
- builder.append(VCARD_DATA_SEPARATOR);
- if (dataExists) {
- // The elements in dataArray are already encoded to quoted printable
- // if needed.
- // See above.
- //
- // TODO: in vCard 3.0, one line may become too huge. Fix this.
- builder.append(dataArray[0]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[1]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[2]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[3]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[4]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[5]);
- builder.append(VCARD_ITEM_SEPARATOR);
- builder.append(dataArray[6]);
- }
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardEmailLine(final StringBuilder builder,
- final Integer typeAsObject, final String label, final String data) {
- builder.append(VCARD_PROPERTY_EMAIL);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Email.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- final String typeAsString;
- switch (typeAsPrimitive) {
- case Email.TYPE_CUSTOM: {
- // For backward compatibility.
- // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
- // To support mobile type at that time, this custom label had been used.
- if (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME
- .equals(label)) {
- typeAsString = Constants.ATTR_TYPE_CELL;
- } else if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- typeAsString = "X-" + label;
- } else {
- typeAsString = DEFAULT_EMAIL_TYPE;
- }
- break;
- }
- case Email.TYPE_HOME: {
- typeAsString = Constants.ATTR_TYPE_HOME;
- break;
- }
- case Email.TYPE_WORK: {
- typeAsString = Constants.ATTR_TYPE_WORK;
- break;
- }
- case Email.TYPE_OTHER: {
- typeAsString = DEFAULT_EMAIL_TYPE;
- break;
- }
- case Email.TYPE_MOBILE: {
- typeAsString = Constants.ATTR_TYPE_CELL;
- break;
- }
- default: {
- Log.e(LOG_TAG, "Unknown Email type: " + typeAsPrimitive);
- typeAsString = DEFAULT_EMAIL_TYPE;
- break;
- }
- }
-
- builder.append(VCARD_ATTR_SEPARATOR);
- appendTypeAttribute(builder, typeAsString);
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(data);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendVCardTelephoneLine(final StringBuilder builder,
- final Integer typeAsObject, final String label,
- String encodedData) {
- builder.append(VCARD_PROPERTY_TEL);
- builder.append(VCARD_ATTR_SEPARATOR);
-
- final int typeAsPrimitive;
- if (typeAsObject == null) {
- typeAsPrimitive = Phone.TYPE_OTHER;
- } else {
- typeAsPrimitive = typeAsObject;
- }
-
- switch (typeAsPrimitive) {
- case Phone.TYPE_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_VOICE));
- break;
- case Phone.TYPE_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_VOICE));
- break;
- case Phone.TYPE_FAX_HOME:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_HOME, Constants.ATTR_TYPE_FAX));
- break;
- case Phone.TYPE_FAX_WORK:
- appendTypeAttributes(builder, Arrays.asList(
- Constants.ATTR_TYPE_WORK, Constants.ATTR_TYPE_FAX));
- break;
- case Phone.TYPE_MOBILE:
- builder.append(Constants.ATTR_TYPE_CELL);
- break;
- case Phone.TYPE_PAGER:
- if (mIsDoCoMo) {
- // Not sure about the reason, but previous implementation had
- // used "VOICE" instead of "PAGER"
- // Also, refrain from using appendType() so that "TYPE=" is never be appended.
- builder.append(Constants.ATTR_TYPE_VOICE);
- } else {
- appendTypeAttribute(builder, Constants.ATTR_TYPE_PAGER);
- }
- break;
- case Phone.TYPE_OTHER:
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
- break;
- case Phone.TYPE_CUSTOM:
- if (mUsesAndroidProperty && !TextUtils.isEmpty(label)
- && VCardUtils.containsOnlyAlphaDigitHyphen(label)) {
- appendTypeAttribute(builder, "X-" + label);
- } else {
- // Just ignore the custom type.
- appendTypeAttribute(builder, Constants.ATTR_TYPE_VOICE);
- }
- break;
- default:
- appendUncommonPhoneType(builder, typeAsPrimitive);
- break;
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- /**
- * Appends phone type string which may not be available in some devices.
- */
- private void appendUncommonPhoneType(final StringBuilder builder, final Integer type) {
- if (mIsDoCoMo) {
- // The previous implementation for DoCoMo had been conservative
- // about miscellaneous types.
- builder.append(Constants.ATTR_TYPE_VOICE);
- } else {
- String phoneAttribute = VCardUtils.getPhoneAttributeString(type);
- if (phoneAttribute != null) {
- appendTypeAttribute(builder, phoneAttribute);
- } else {
- Log.e(LOG_TAG, "Unknown or unsupported (by vCard) Phone type: " + type);
- }
- }
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String propertyName, final String rawData) {
- appendVCardLine(builder, propertyName, rawData, false, false);
- }
-
- private void appendVCardLine(final StringBuilder builder,
- final String field, final String rawData, final boolean needCharset,
- boolean needQuotedPrintable) {
- builder.append(field);
- if (needCharset) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(mVCardAttributeCharset);
- }
-
- final String encodedData;
- if (needQuotedPrintable) {
- builder.append(VCARD_ATTR_SEPARATOR);
- builder.append(VCARD_ATTR_ENCODING_QP);
- encodedData = encodeQuotedPrintable(rawData);
- } else {
- // TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
- encodedData = escapeCharacters(rawData);
- }
-
- builder.append(VCARD_DATA_SEPARATOR);
- builder.append(encodedData);
- builder.append(VCARD_COL_SEPARATOR);
- }
-
- private void appendTypeAttributes(final StringBuilder builder,
- final List<String> types) {
- // We may have to make this comma separated form like "TYPE=DOM,WORK" in the future,
- // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
- boolean first = true;
- for (String type : types) {
- if (first) {
- first = false;
- } else {
- builder.append(VCARD_ATTR_SEPARATOR);
- }
- appendTypeAttribute(builder, type);
- }
- }
-
- private void appendTypeAttribute(final StringBuilder builder, final String type) {
- // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
- if (mIsV30) {
- builder.append(Constants.ATTR_TYPE).append(VCARD_ATTR_EQUAL);
- }
- builder.append(type);
- }
-
- /**
- * Returns true when the property line should contain charset attribute
- * information. This method may return true even when vCard version is 3.0.
- *
- * Strictly, adding charset information is invalid in VCard 3.0.
- * However we'll add the info only when used charset is not UTF-8
- * in vCard 3.0 format, since parser side may be able to use the charset
- * via this field, though we may encounter another problem by adding it...
- *
- * e.g. Japanese mobile phones use Shift_Jis while RFC 2426
- * recommends UTF-8. By adding this field, parsers may be able
- * to know this text is NOT UTF-8 but Shift_Jis.
- */
- private boolean shouldAppendCharsetAttribute(final String propertyValue) {
- return (!VCardUtils.containsOnlyPrintableAscii(propertyValue) &&
- (!mIsV30 || !mUsesUtf8));
- }
-
- private boolean shouldAppendCharsetAttribute(final List<String> propertyValueList) {
- boolean shouldAppendBasically = false;
- for (String propertyValue : propertyValueList) {
- if (!VCardUtils.containsOnlyPrintableAscii(propertyValue)) {
- shouldAppendBasically = true;
- break;
- }
- }
- return shouldAppendBasically && (!mIsV30 || !mUsesUtf8);
- }
-
- private String encodeQuotedPrintable(String str) {
- if (TextUtils.isEmpty(str)) {
- return "";
- }
- {
- // Replace "\n" and "\r" with "\r\n".
- StringBuilder tmpBuilder = new StringBuilder();
- int length = str.length();
- for (int i = 0; i < length; i++) {
- char ch = str.charAt(i);
- if (ch == '\r') {
- if (i + 1 < length && str.charAt(i + 1) == '\n') {
- i++;
- }
- tmpBuilder.append("\r\n");
- } else if (ch == '\n') {
- tmpBuilder.append("\r\n");
- } else {
- tmpBuilder.append(ch);
- }
- }
- str = tmpBuilder.toString();
- }
-
- final StringBuilder tmpBuilder = new StringBuilder();
- int index = 0;
- int lineCount = 0;
- byte[] strArray = null;
-
- try {
- strArray = str.getBytes(mCharsetString);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
- + "Try default charset");
- strArray = str.getBytes();
- }
- while (index < strArray.length) {
- tmpBuilder.append(String.format("=%02X", strArray[index]));
- index += 1;
- lineCount += 3;
-
- if (lineCount >= 67) {
- // Specification requires CRLF must be inserted before the
- // length of the line
- // becomes more than 76.
- // Assuming that the next character is a multi-byte character,
- // it will become
- // 6 bytes.
- // 76 - 6 - 3 = 67
- tmpBuilder.append("=\r\n");
- lineCount = 0;
- }
- }
-
- return tmpBuilder.toString();
- }
}
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 68cd0df..3409be6 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,16 +15,19 @@
*/
package android.pim.vcard;
+import android.util.Log;
+
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
/**
* The class representing VCard related configurations. Useful static methods are not in this class
* but in VCardUtils.
*/
public class VCardConfig {
- // TODO: may be better to make the instance of this available and stop using static methods and
- // one integer.
+ private static final String LOG_TAG = "VCardConfig";
/* package */ static final int LOG_LEVEL_NONE = 0;
/* package */ static final int LOG_LEVEL_PERFORMANCE_MEASUREMENT = 0x1;
@@ -34,15 +37,18 @@ public class VCardConfig {
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
+ /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
+ /* package */ static final int PARSE_TYPE_APPLE = 1;
+ /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones.
+ /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones.
+ /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+
// Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
// decode the unicode to the original charset. If not, this setting will cause some bug.
public static final String DEFAULT_CHARSET = "iso-8859-1";
- // TODO: make the other codes use this flag
- public static final boolean IGNORE_CASE_EXCEPT_VALUE = true;
-
- private static final int FLAG_V21 = 0;
- private static final int FLAG_V30 = 1;
+ public static final int FLAG_V21 = 0;
+ public static final int FLAG_V30 = 1;
// 0x2 is reserved for the future use ...
@@ -54,7 +60,8 @@ public class VCardConfig {
// 0x10 is reserved for safety
private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 0x20;
+ private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
+ private static final int FLAG_CHARSET_MASK = 0xF00;
/**
* The flag indicating the vCard composer will add some "X-" properties used only in Android
@@ -95,96 +102,204 @@ public class VCardConfig {
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * The flag indicating the vCard composer use Quoted-Printable toward even "primary" types.
- * In this context, "primary" types means "N", "FN", etc. which are usually "not" encoded
- * into Quoted-Printable format in external exporters.
- * This flag is useful when some target importer does not accept "primary" property values
- * without Quoted-Printable encoding.
- *
- * @hide Temporaly made public. We don't strictly define "primary", so we may change the
- * behavior around this flag in the future. Do not use this flag without any reason.
+ * <P>
+ * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
+ * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
+ * </P>
+ * <P>
+ * We actually cannot define what is the "primary" property. Note that this is NOT defined
+ * in vCard specification either. Also be aware that it is NOT related to "primary" notion
+ * used in {@link android.provider.ContactsContract}.
+ * This notion is just for vCard composition in Android.
+ * </P>
+ * <P>
+ * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
+ * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
+ * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
+ * other properties like "ADR", "ORG", etc.
+ * <P>
+ * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
+ * not used in such fields.
+ * </P>
+ * <P>
+ * This flag is useful when some target importer you are going to focus on does not accept
+ * such properties with Quoted-Printable encoding.
+ * </P>
+ * <P>
+ * Again, we should not use this flag at all for complying vCard 2.1 spec.
+ * </P>
+ * <P>
+ * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
+ * kind of problem (hopefully).
+ * </P>
*/
- public static final int FLAG_USE_QP_TO_PRIMARY_PROPERTIES = 0x10000000;
-
- // VCard types
+ public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/**
- * General vCard format with the version 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used.
- *
+ * <P>
+ * The flag indicating that phonetic name related fields must be converted to
+ * appropriate form. Note that "appropriate" is not defined in any vCard specification.
+ * This is Android-specific.
+ * </P>
+ * <P>
+ * One typical (and currently sole) example where we need this flag is the time when
+ * we need to emit Japanese phonetic names into vCard entries. The property values
+ * should be encoded into half-width katakana when the target importer is Japanese mobile
+ * phones', which are probably not able to parse full-width hiragana/katakana for
+ * historical reasons, while the vCard importers embedded to softwares for PC should be
+ * able to parse them as we expect.
+ * </P>
+ */
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+
+ /**
+ * <P>
+ * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
+ * every time possible. The default behavior does not emit it and is valid in the spec.
+ * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
+ * </P>
+ * <P>
+ * Detail:
+ * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
+ * </p>
+ * <P>
+ * e.g.<BR />
+ * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
+ * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
+ * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
+ * </P>
+ * <P>
+ * 2) had been the default of VCard exporter/importer in Android, but it is found that
+ * some external exporter is not able to parse the type format like 2) but only 3).
+ * </P>
+ * <P>
+ * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
+ * strings (which should be rare though), please use this flag.
+ * </P>
+ * <P>
+ * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
+ * </P>
+ */
+ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
+
+ /**
+ * <P>
+ * The flag asking exporter to refrain image export.
+ * </P>
+ * @hide will be deleted in the near future.
+ */
+ public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
+
+ //// The followings are VCard types available from importer/exporter. ////
+
+ /**
+ * <P>
+ * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
+ * When composing a vCard entry, the US convension will be used toward formatting
+ * some values.
+ * </P>
+ * <P>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
- * while in Japan, it should be "Prefix Family Middle Given Suffix".
+ * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
+ * </P>
*/
- public static final int VCARD_TYPE_V21_GENERIC =
+ public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
(FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
/**
+ * <P>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- *
- * Note that this type is not fully implemented, so probably some bugs remain both in
- * parsing and composing.
- *
- * TODO: implement this type correctly.
+ * </P>
+ * <P>
+ * Not fully ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_GENERIC =
+ public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
/**
- * General vCard format with the version 2.1 with some Europe convension. Uses Utf-8.
+ * <P>
+ * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
+ * </P>
*/
- public static final int VCARD_TYPE_V21_EUROPE =
+ public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
(FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
/**
- * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8
+ * <P>
+ * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_EUROPE =
+ public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
(FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
-
- /**
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- */
- public static final int VCARD_TYPE_V21_JAPANESE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese";
-
/**
- * vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+
+ /**
+ * <P>
+ * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
+ * parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
/**
+ * <P>
* vCard format for miscellaneous Japanese devices, using Shift_Jis for
* parsing/composing the vCard data.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
- public static final int VCARD_TYPE_V30_JAPANESE =
+ public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
/**
- * vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * <P>
+ * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
@@ -193,111 +308,139 @@ public class VCardConfig {
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
/**
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
- * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
- * No Android-specific property nor defact property is included.
+ * <P>
+ * The vCard 2.1 based format which (partially) considers the convention in Japanese
+ * mobile phones, where phonetic names are translated to half-width katakana if
+ * possible, etc.
+ * </P>
+ * <P>
+ * Not ready yet. Use with caution when you use this.
+ * </P>
+ */
+ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
+ (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS |
+ FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
+
+ /**
+ * <P>
+ * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * </p>
+ * <P>
+ * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
+ * No Android-specific property nor defact property is included. The "Primary" properties
+ * are NOT encoded to Quoted-Printable.
+ * </P>
*/
public static final int VCARD_TYPE_DOCOMO =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | FLAG_DOCOMO);
+ (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
- private static final String VCARD_TYPE_DOCOMO_STR = "docomo";
-
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
+ /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
+
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
- private static final Map<String, Integer> VCARD_TYPES_MAP;
+ private static final Map<String, Integer> sVCardTypeMap;
+ private static final Set<Integer> sJapaneseMobileTypeSet;
static {
- VCARD_TYPES_MAP = new HashMap<String, Integer>();
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
- VCARD_TYPES_MAP.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
- VCARD_TYPES_MAP.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+ sVCardTypeMap = new HashMap<String, Integer>();
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
+
+ sJapaneseMobileTypeSet = new HashSet<Integer>();
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
- public static int getVCardTypeFromString(String vcardTypeString) {
- String loweredKey = vcardTypeString.toLowerCase();
- if (VCARD_TYPES_MAP.containsKey(loweredKey)) {
- return VCARD_TYPES_MAP.get(loweredKey);
+ public static int getVCardTypeFromString(final String vcardTypeString) {
+ final String loweredKey = vcardTypeString.toLowerCase();
+ if (sVCardTypeMap.containsKey(loweredKey)) {
+ return sVCardTypeMap.get(loweredKey);
+ } else if ("default".equalsIgnoreCase(vcardTypeString)) {
+ return VCARD_TYPE_DEFAULT;
} else {
- // XXX: should return the value indicating the input is invalid?
+ Log.e(LOG_TAG, "Unknown vCard type String: \"" + vcardTypeString + "\"");
return VCARD_TYPE_DEFAULT;
}
}
- public static boolean isV30(int vcardType) {
+ public static boolean isV30(final int vcardType) {
return ((vcardType & FLAG_V30) != 0);
}
- public static boolean usesQuotedPrintable(int vcardType) {
+ public static boolean shouldUseQuotedPrintable(final int vcardType) {
return !isV30(vcardType);
}
- public static boolean isDoCoMo(int vcardType) {
- return ((vcardType & FLAG_DOCOMO) != 0);
- }
-
- /**
- * @return true if the device is Japanese and some Japanese convension is
- * applied to creating "formatted" something like FORMATTED_ADDRESS.
- */
- public static boolean isJapaneseDevice(int vcardType) {
- return ((vcardType == VCARD_TYPE_V21_JAPANESE) ||
- (vcardType == VCARD_TYPE_V21_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE) ||
- (vcardType == VCARD_TYPE_V30_JAPANESE_UTF8) ||
- (vcardType == VCARD_TYPE_DOCOMO));
- }
-
- public static boolean usesUtf8(int vcardType) {
- return ((vcardType & FLAG_CHARSET_UTF8) != 0);
+ public static boolean usesUtf8(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
}
- public static boolean usesShiftJis(int vcardType) {
- return ((vcardType & FLAG_CHARSET_SHIFT_JIS) != 0);
- }
-
- /**
- * @return true when Japanese phonetic string must be converted to a string
- * containing only half-width katakana. This method exists since Japanese mobile
- * phones usually use only half-width katakana for expressing phonetic names and
- * some devices are not ready for parsing other phonetic strings like hiragana and
- * full-width katakana.
- */
- public static boolean needsToConvertPhoneticString(int vcardType) {
- return (vcardType == VCARD_TYPE_DOCOMO);
+ public static boolean usesShiftJis(final int vcardType) {
+ return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
}
- public static int getNameOrderType(int vcardType) {
+ public static int getNameOrderType(final int vcardType) {
return vcardType & NAME_ORDER_MASK;
}
- public static boolean usesAndroidSpecificProperty(int vcardType) {
+ public static boolean usesAndroidSpecificProperty(final int vcardType) {
return ((vcardType & FLAG_USE_ANDROID_PROPERTY) != 0);
}
- public static boolean usesDefactProperty(int vcardType) {
+ public static boolean usesDefactProperty(final int vcardType) {
return ((vcardType & FLAG_USE_DEFACT_PROPERTY) != 0);
}
- public static boolean onlyOneNoteFieldIsAvailable(int vcardType) {
- return vcardType == VCARD_TYPE_DOCOMO;
- }
-
public static boolean showPerformanceLog() {
return (VCardConfig.LOG_LEVEL & VCardConfig.LOG_LEVEL_PERFORMANCE_MEASUREMENT) != 0;
}
+ public static boolean shouldRefrainQPToNameProperties(final int vcardType) {
+ return (!shouldUseQuotedPrintable(vcardType) ||
+ ((vcardType & FLAG_REFRAIN_QP_TO_NAME_PROPERTIES) != 0));
+ }
+
+ public static boolean appendTypeParamName(final int vcardType) {
+ return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ }
+
/**
- * @hide
+ * @return true if the device is Japanese and some Japanese convension is
+ * applied to creating "formatted" something like FORMATTED_ADDRESS.
*/
- public static boolean usesQPToPrimaryProperties(int vcardType) {
- return (usesQuotedPrintable(vcardType) &&
- ((vcardType & FLAG_USE_QP_TO_PRIMARY_PROPERTIES) != 0));
+ public static boolean isJapaneseDevice(final int vcardType) {
+ // TODO: Some mask will be required so that this method wrongly interpret
+ // Japanese"-like" vCard type.
+ // e.g. VCARD_TYPE_V21_JAPANESE_SJIS | FLAG_APPEND_TYPE_PARAMS
+ return sJapaneseMobileTypeSet.contains(vcardType);
+ }
+
+ public static boolean needsToConvertPhoneticString(final int vcardType) {
+ return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
+ }
+
+ public static boolean onlyOneNoteFieldIsAvailable(final int vcardType) {
+ return vcardType == VCARD_TYPE_DOCOMO;
+ }
+
+ public static boolean isDoCoMo(final int vcardType) {
+ return ((vcardType & FLAG_DOCOMO) != 0);
}
private VCardConfig() {
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
new file mode 100644
index 0000000..8c07126
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -0,0 +1,152 @@
+/*
+ * 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.pim.vcard;
+
+/**
+ * Constants used in both exporter and importer code.
+ */
+public class VCardConstants {
+ public static final String VERSION_V21 = "2.1";
+ public static final String VERSION_V30 = "3.0";
+
+ // The property names valid both in vCard 2.1 and 3.0.
+ public static final String PROPERTY_BEGIN = "BEGIN";
+ public static final String PROPERTY_VERSION = "VERSION";
+ public static final String PROPERTY_N = "N";
+ public static final String PROPERTY_FN = "FN";
+ public static final String PROPERTY_ADR = "ADR";
+ public static final String PROPERTY_EMAIL = "EMAIL";
+ public static final String PROPERTY_NOTE = "NOTE";
+ public static final String PROPERTY_ORG = "ORG";
+ public static final String PROPERTY_SOUND = "SOUND"; // Not fully supported.
+ public static final String PROPERTY_TEL = "TEL";
+ public static final String PROPERTY_TITLE = "TITLE";
+ public static final String PROPERTY_ROLE = "ROLE";
+ public static final String PROPERTY_PHOTO = "PHOTO";
+ public static final String PROPERTY_LOGO = "LOGO";
+ public static final String PROPERTY_URL = "URL";
+ public static final String PROPERTY_BDAY = "BDAY"; // Birthday
+ public static final String PROPERTY_END = "END";
+
+ // Valid property names not supported (not appropriately handled) by our vCard importer now.
+ public static final String PROPERTY_REV = "REV";
+ public static final String PROPERTY_AGENT = "AGENT";
+
+ // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
+ public static final String PROPERTY_NAME = "NAME";
+ public static final String PROPERTY_NICKNAME = "NICKNAME";
+ public static final String PROPERTY_SORT_STRING = "SORT-STRING";
+
+ // De-fact property values expressing phonetic names.
+ public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
+ public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
+ public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
+
+ // Properties both ContactsStruct in Eclair and de-fact vCard extensions
+ // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ public static final String PROPERTY_X_AIM = "X-AIM";
+ public static final String PROPERTY_X_MSN = "X-MSN";
+ public static final String PROPERTY_X_YAHOO = "X-YAHOO";
+ public static final String PROPERTY_X_ICQ = "X-ICQ";
+ public static final String PROPERTY_X_JABBER = "X-JABBER";
+ public static final String PROPERTY_X_GOOGLE_TALK = "X-GOOGLE-TALK";
+ public static final String PROPERTY_X_SKYPE_USERNAME = "X-SKYPE-USERNAME";
+ // Properties only ContactsStruct has. We alse use this.
+ public static final String PROPERTY_X_QQ = "X-QQ";
+ public static final String PROPERTY_X_NETMEETING = "X-NETMEETING";
+
+ // Phone number for Skype, available as usual phone.
+ public static final String PROPERTY_X_SKYPE_PSTNNUMBER = "X-SKYPE-PSTNNUMBER";
+
+ // Property for Android-specific fields.
+ public static final String PROPERTY_X_ANDROID_CUSTOM = "X-ANDROID-CUSTOM";
+
+ // Properties for DoCoMo vCard.
+ public static final String PROPERTY_X_CLASS = "X-CLASS";
+ public static final String PROPERTY_X_REDUCTION = "X-REDUCTION";
+ public static final String PROPERTY_X_NO = "X-NO";
+ public static final String PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
+
+ public static final String PARAM_TYPE = "TYPE";
+
+ public static final String PARAM_TYPE_HOME = "HOME";
+ public static final String PARAM_TYPE_WORK = "WORK";
+ public static final String PARAM_TYPE_FAX = "FAX";
+ public static final String PARAM_TYPE_CELL = "CELL";
+ public static final String PARAM_TYPE_VOICE = "VOICE";
+ public static final String PARAM_TYPE_INTERNET = "INTERNET";
+
+ // Abbreviation of "prefered" according to vCard 2.1 specification.
+ // We interpret this value as "primary" property during import/export.
+ //
+ // Note: Both vCard specs does not mention anything about the requirement for this parameter,
+ // but there may be some vCard importer which will get confused with more than
+ // one "PREF"s in one property name, while Android accepts them.
+ public static final String PARAM_TYPE_PREF = "PREF";
+
+ // Phone type parameters valid in vCard and known to ContactsContract, but not so common.
+ public static final String PARAM_TYPE_CAR = "CAR";
+ public static final String PARAM_TYPE_ISDN = "ISDN";
+ public static final String PARAM_TYPE_PAGER = "PAGER";
+ public static final String PARAM_TYPE_TLX = "TLX"; // Telex
+
+ // Phone types existing in vCard 2.1 but not known to ContactsContract.
+ public static final String PARAM_TYPE_MODEM = "MODEM";
+ public static final String PARAM_TYPE_MSG = "MSG";
+ public static final String PARAM_TYPE_BBS = "BBS";
+ public static final String PARAM_TYPE_VIDEO = "VIDEO";
+
+ // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
+ // These types are basically encoded to "X-" parameters when composing vCard.
+ // Parser passes these when "X-" is added to the parameter or not.
+ public static final String PARAM_PHONE_EXTRA_TYPE_CALLBACK = "CALLBACK";
+ public static final String PARAM_PHONE_EXTRA_TYPE_RADIO = "RADIO";
+ public static final String PARAM_PHONE_EXTRA_TYPE_TTY_TDD = "TTY-TDD";
+ public static final String PARAM_PHONE_EXTRA_TYPE_ASSISTANT = "ASSISTANT";
+ // vCard composer translates this type to "WORK" + "PREF". Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN = "COMPANY-MAIN";
+ // vCard composer translates this type to "VOICE" Just for parsing.
+ public static final String PARAM_PHONE_EXTRA_TYPE_OTHER = "OTHER";
+
+ // TYPE parameters for postal addresses.
+ public static final String PARAM_ADR_TYPE_PARCEL = "PARCEL";
+ public static final String PARAM_ADR_TYPE_DOM = "DOM";
+ public static final String PARAM_ADR_TYPE_INTL = "INTL";
+
+ // TYPE parameters not officially valid but used in some vCard exporter.
+ // Do not use in composer side.
+ public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
+ // vCard 3.0.
+ public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
+ public interface ImportOnly {
+ public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
+ // Some device emits this "X-" parameter for expressing Google Talk,
+ // which is specifically invalid but should be always properly accepted, and emitted
+ // in some special case (for that device/application).
+ public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
+ }
+
+ /* package */ static final int MAX_DATA_COLUMN = 15;
+
+ /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
+ static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
+
+ private VCardConstants() {
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/ContactStruct.java b/core/java/android/pim/vcard/VCardEntry.java
index 36e5e23..1327770 100644
--- a/core/java/android/pim/vcard/ContactStruct.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -17,11 +17,14 @@ package android.pim.vcard;
import android.accounts.Account;
import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
import android.database.Cursor;
+import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
@@ -44,36 +47,37 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This class bridges between data structure of Contact app and VCard data.
*/
-public class ContactStruct {
- private static final String LOG_TAG = "vcard.ContactStruct";
-
- // Key: the name shown in VCard. e.g. "X-AIM", "X-ICQ"
- // Value: the result of {@link Contacts.ContactMethods#encodePredefinedImProtocol}
+public class VCardEntry {
+ private static final String LOG_TAG = "VCardEntry";
+
+ private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
+
+ private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
+ private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
+
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
-
+
static {
- sImMap.put(Constants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
- sImMap.put(Constants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
- sImMap.put(Constants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
- sImMap.put(Constants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
- sImMap.put(Constants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
- sImMap.put(Constants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
- sImMap.put(Constants.PROPERTY_X_GOOGLE_TALK_WITH_SPACE, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.PROPERTY_X_AIM, Im.PROTOCOL_AIM);
+ sImMap.put(VCardConstants.PROPERTY_X_MSN, Im.PROTOCOL_MSN);
+ sImMap.put(VCardConstants.PROPERTY_X_YAHOO, Im.PROTOCOL_YAHOO);
+ sImMap.put(VCardConstants.PROPERTY_X_ICQ, Im.PROTOCOL_ICQ);
+ sImMap.put(VCardConstants.PROPERTY_X_JABBER, Im.PROTOCOL_JABBER);
+ sImMap.put(VCardConstants.PROPERTY_X_SKYPE_USERNAME, Im.PROTOCOL_SKYPE);
+ sImMap.put(VCardConstants.PROPERTY_X_GOOGLE_TALK, Im.PROTOCOL_GOOGLE_TALK);
+ sImMap.put(VCardConstants.ImportOnly.PROPERTY_X_GOOGLE_TALK_WITH_SPACE,
+ Im.PROTOCOL_GOOGLE_TALK);
}
-
- /**
- * @hide only for testing
- */
+
static public class PhoneData {
public final int type;
public final String data;
@@ -87,17 +91,17 @@ public class ContactStruct {
this.label = label;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof PhoneData) {
+ if (!(obj instanceof PhoneData)) {
return false;
}
PhoneData phoneData = (PhoneData)obj;
return (type == phoneData.type && data.equals(phoneData.data) &&
label.equals(phoneData.label) && isPrimary == phoneData.isPrimary);
}
-
+
@Override
public String toString() {
return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
@@ -105,9 +109,6 @@ public class ContactStruct {
}
}
- /**
- * @hide only for testing
- */
static public class EmailData {
public final int type;
public final String data;
@@ -122,17 +123,17 @@ public class ContactStruct {
this.label = label;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof EmailData) {
+ if (!(obj instanceof EmailData)) {
return false;
}
EmailData emailData = (EmailData)obj;
return (type == emailData.type && data.equals(emailData.data) &&
label.equals(emailData.label) && isPrimary == emailData.isPrimary);
}
-
+
@Override
public String toString() {
return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
@@ -152,17 +153,12 @@ public class ContactStruct {
public final String region;
public final String postalCode;
public final String country;
-
public final int type;
-
- // Used only when type variable is TYPE_CUSTOM.
public final String label;
-
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
public boolean isPrimary;
- public PostalData(int type, List<String> propValueList,
- String label, boolean isPrimary) {
+
+ public PostalData(final int type, final List<String> propValueList,
+ final String label, boolean isPrimary) {
this.type = type;
dataArray = new String[ADDR_MAX_DATA_SIZE];
@@ -171,9 +167,9 @@ public class ContactStruct {
size = ADDR_MAX_DATA_SIZE;
}
- // adr-value = 0*6(text-value ";") text-value
- // ; PO Box, Extended Address, Street, Locality, Region, Postal
- // ; Code, Country Name
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
//
// Use Iterator assuming List may be LinkedList, though actually it is
// always ArrayList in the current implementation.
@@ -195,25 +191,24 @@ public class ContactStruct {
this.region = dataArray[4];
this.postalCode = dataArray[5];
this.country = dataArray[6];
-
this.label = label;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof PostalData) {
+ if (!(obj instanceof PostalData)) {
return false;
}
- PostalData postalData = (PostalData)obj;
- return (Arrays.equals(dataArray, postalData.dataArray) &&
+ final PostalData postalData = (PostalData)obj;
+ return (Arrays.equals(dataArray, postalData.dataArray) &&
(type == postalData.type &&
(type == StructuredPostal.TYPE_CUSTOM ?
(label == postalData.label) : true)) &&
(isPrimary == postalData.isPrimary));
}
-
- public String getFormattedAddress(int vcardType) {
+
+ public String getFormattedAddress(final int vcardType) {
StringBuilder builder = new StringBuilder();
boolean empty = true;
if (VCardConfig.isJapaneseDevice(vcardType)) {
@@ -223,9 +218,10 @@ public class ContactStruct {
if (!TextUtils.isEmpty(addressPart)) {
if (!empty) {
builder.append(' ');
+ } else {
+ empty = false;
}
builder.append(addressPart);
- empty = false;
}
}
} else {
@@ -234,122 +230,171 @@ public class ContactStruct {
if (!TextUtils.isEmpty(addressPart)) {
if (!empty) {
builder.append(' ');
+ } else {
+ empty = false;
}
builder.append(addressPart);
- empty = false;
}
}
}
return builder.toString().trim();
}
-
+
@Override
public String toString() {
return String.format("type: %d, label: %s, isPrimary: %s",
type, label, isPrimary);
}
}
-
- /**
- * @hide only for testing.
- */
+
static public class OrganizationData {
public final int type;
- public final String companyName;
- // can be changed in some VCard format.
- public String positionName;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
+ // non-final is Intentional: we may change the values since this info is separated into
+ // two parts in vCard: "ORG" + "TITLE".
+ public String companyName;
+ public String departmentName;
+ public String titleName;
public boolean isPrimary;
- public OrganizationData(int type, String companyName, String positionName,
+
+ public OrganizationData(int type,
+ String companyName,
+ String departmentName,
+ String titleName,
boolean isPrimary) {
this.type = type;
this.companyName = companyName;
- this.positionName = positionName;
+ this.departmentName = departmentName;
+ this.titleName = titleName;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof OrganizationData) {
+ if (!(obj instanceof OrganizationData)) {
return false;
}
OrganizationData organization = (OrganizationData)obj;
- return (type == organization.type && companyName.equals(organization.companyName) &&
- positionName.equals(organization.positionName) &&
+ return (type == organization.type &&
+ TextUtils.equals(companyName, organization.companyName) &&
+ TextUtils.equals(departmentName, organization.departmentName) &&
+ TextUtils.equals(titleName, organization.titleName) &&
isPrimary == organization.isPrimary);
}
-
+
+ public String getFormattedString() {
+ final StringBuilder builder = new StringBuilder();
+ if (!TextUtils.isEmpty(companyName)) {
+ builder.append(companyName);
+ }
+
+ if (!TextUtils.isEmpty(departmentName)) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append(departmentName);
+ }
+
+ if (!TextUtils.isEmpty(titleName)) {
+ if (builder.length() > 0) {
+ builder.append(", ");
+ }
+ builder.append(titleName);
+ }
+
+ return builder.toString();
+ }
+
@Override
public String toString() {
- return String.format("type: %d, company: %s, position: %s, isPrimary: %s",
- type, companyName, positionName, isPrimary);
+ return String.format(
+ "type: %d, company: %s, department: %s, title: %s, isPrimary: %s",
+ type, companyName, departmentName, titleName, isPrimary);
}
}
-
+
static public class ImData {
+ public final int protocol;
+ public final String customProtocol;
public final int type;
public final String data;
- public final String label;
public final boolean isPrimary;
-
- // TODO: ContactsConstant#PROTOCOL, ContactsConstant#CUSTOM_PROTOCOL should be used?
- public ImData(int type, String data, String label, boolean isPrimary) {
+
+ public ImData(final int protocol, final String customProtocol, final int type,
+ final String data, final boolean isPrimary) {
+ this.protocol = protocol;
+ this.customProtocol = customProtocol;
this.type = type;
this.data = data;
- this.label = label;
this.isPrimary = isPrimary;
}
-
+
@Override
public boolean equals(Object obj) {
- if (obj instanceof ImData) {
+ if (!(obj instanceof ImData)) {
return false;
}
ImData imData = (ImData)obj;
- return (type == imData.type && data.equals(imData.data) &&
- label.equals(imData.label) && isPrimary == imData.isPrimary);
+ return (type == imData.type && protocol == imData.protocol
+ && (customProtocol != null ? customProtocol.equals(imData.customProtocol) :
+ (imData.customProtocol == null))
+ && (data != null ? data.equals(imData.data) : (imData.data == null))
+ && isPrimary == imData.isPrimary);
}
-
+
@Override
public String toString() {
- return String.format("type: %d, data: %s, label: %s, isPrimary: %s",
- type, data, label, isPrimary);
+ return String.format(
+ "type: %d, protocol: %d, custom_protcol: %s, data: %s, isPrimary: %s",
+ type, protocol, customProtocol, data, isPrimary);
}
}
-
- /**
- * @hide only for testing.
- */
- static public class PhotoData {
+
+ public static class PhotoData {
public static final String FORMAT_FLASH = "SWF";
public final int type;
public final String formatName; // used when type is not defined in ContactsContract.
public final byte[] photoBytes;
+ public final boolean isPrimary;
- public PhotoData(int type, String formatName, byte[] photoBytes) {
+ public PhotoData(int type, String formatName, byte[] photoBytes, boolean isPrimary) {
this.type = type;
this.formatName = formatName;
this.photoBytes = photoBytes;
+ this.isPrimary = isPrimary;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PhotoData)) {
+ return false;
+ }
+ PhotoData photoData = (PhotoData)obj;
+ return (type == photoData.type &&
+ (formatName == null ? (photoData.formatName == null) :
+ formatName.equals(photoData.formatName)) &&
+ (Arrays.equals(photoBytes, photoData.photoBytes)) &&
+ (isPrimary == photoData.isPrimary));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("type: %d, format: %s: size: %d, isPrimary: %s",
+ type, formatName, photoBytes.length, isPrimary);
}
}
-
- static /* package */ class Property {
+
+ /* package */ static class Property {
private String mPropertyName;
private Map<String, Collection<String>> mParameterMap =
new HashMap<String, Collection<String>>();
private List<String> mPropertyValueList = new ArrayList<String>();
private byte[] mPropertyBytes;
-
- public Property() {
- clear();
- }
-
+
public void setPropertyName(final String propertyName) {
mPropertyName = propertyName;
}
-
+
public void addParameter(final String paramName, final String paramValue) {
Collection<String> values;
if (!mParameterMap.containsKey(paramName)) {
@@ -364,11 +409,11 @@ public class ContactStruct {
}
values.add(paramValue);
}
-
+
public void addToPropertyValueList(final String propertyValue) {
mPropertyValueList.add(propertyValue);
}
-
+
public void setPropertyBytes(final byte[] propertyBytes) {
mPropertyBytes = propertyBytes;
}
@@ -376,18 +421,19 @@ public class ContactStruct {
public final Collection<String> getParameters(String type) {
return mParameterMap.get(type);
}
-
+
public final List<String> getPropertyValueList() {
return mPropertyValueList;
}
-
+
public void clear() {
mPropertyName = null;
mParameterMap.clear();
mPropertyValueList.clear();
+ mPropertyBytes = null;
}
}
-
+
private String mFamilyName;
private String mGivenName;
private String mMiddleName;
@@ -396,19 +442,19 @@ public class ContactStruct {
// Used only when no family nor given name is found.
private String mFullName;
-
+
private String mPhoneticFamilyName;
private String mPhoneticGivenName;
private String mPhoneticMiddleName;
-
+
private String mPhoneticFullName;
private List<String> mNickNameList;
- private String mDisplayName;
+ private String mDisplayName;
private String mBirthday;
-
+
private List<String> mNoteList;
private List<PhoneData> mPhoneList;
private List<EmailData> mEmailList;
@@ -417,236 +463,48 @@ public class ContactStruct {
private List<ImData> mImList;
private List<PhotoData> mPhotoList;
private List<String> mWebsiteList;
-
+ private List<List<String>> mAndroidCustomPropertyList;
+
private final int mVCardType;
private final Account mAccount;
- // Each Column of four properties has ISPRIMARY field
- // (See android.provider.Contacts)
- // If false even after the parsing loop, we choose the first entry as a "primary"
- // entry.
- private boolean mPrefIsSet_Address;
- private boolean mPrefIsSet_Phone;
- private boolean mPrefIsSet_Email;
- private boolean mPrefIsSet_Organization;
-
- public ContactStruct() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC);
+ public VCardEntry() {
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
}
- public ContactStruct(int vcardType) {
+ public VCardEntry(int vcardType) {
this(vcardType, null);
}
- public ContactStruct(int vcardType, Account account) {
+ public VCardEntry(int vcardType, Account account) {
mVCardType = vcardType;
mAccount = account;
}
- /**
- * @hide only for testing.
- */
- public ContactStruct(String givenName,
- String familyName,
- String middleName,
- String prefix,
- String suffix,
- String phoneticGivenName,
- String pheneticFamilyName,
- String phoneticMiddleName,
- List<byte[]> photoBytesList,
- List<String> notes,
- List<PhoneData> phoneList,
- List<EmailData> emailList,
- List<PostalData> postalList,
- List<OrganizationData> organizationList,
- List<PhotoData> photoList,
- List<String> websiteList) {
- this(VCardConfig.VCARD_TYPE_DEFAULT);
- mGivenName = givenName;
- mFamilyName = familyName;
- mPrefix = prefix;
- mSuffix = suffix;
- mPhoneticGivenName = givenName;
- mPhoneticFamilyName = familyName;
- mPhoneticMiddleName = middleName;
- mEmailList = emailList;
- mPostalList = postalList;
- mOrganizationList = organizationList;
- mPhotoList = photoList;
- mWebsiteList = websiteList;
- }
-
- // All getter methods should be used carefully, since they may change
- // in the future as of 2009-09-24, on which I cannot be sure this structure
- // is completely consolidated.
- // When we are sure we will no longer change them, we'll be happy to
- // make it complete public (withouth @hide tag)
- //
- // Also note that these getter methods should be used only after
- // all properties being pushed into this object. If not, incorrect
- // value will "be stored in the local cache and" be returned to you.
-
- /**
- * @hide
- */
- public String getFamilyName() {
- return mFamilyName;
- }
-
- /**
- * @hide
- */
- public String getGivenName() {
- return mGivenName;
- }
-
- /**
- * @hide
- */
- public String getMiddleName() {
- return mMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPrefix() {
- return mPrefix;
- }
-
- /**
- * @hide
- */
- public String getSuffix() {
- return mSuffix;
- }
-
- /**
- * @hide
- */
- public String getFullName() {
- return mFullName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFamilyName() {
- return mPhoneticFamilyName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticGivenName() {
- return mPhoneticGivenName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticMiddleName() {
- return mPhoneticMiddleName;
- }
-
- /**
- * @hide
- */
- public String getPhoneticFullName() {
- return mPhoneticFullName;
- }
-
- /**
- * @hide
- */
- public final List<String> getNickNameList() {
- return mNickNameList;
- }
-
- /**
- * @hide
- */
- public String getDisplayName() {
- if (mDisplayName == null) {
- constructDisplayName();
- }
- return mDisplayName;
- }
-
- /**
- * @hide
- */
- public String getBirthday() {
- return mBirthday;
- }
-
- /**
- * @hide
- */
- public final List<PhotoData> getPhotoList() {
- return mPhotoList;
- }
-
- /**
- * @hide
- */
- public final List<String> getNotes() {
- return mNoteList;
- }
-
- /**
- * @hide
- */
- public final List<PhoneData> getPhoneList() {
- return mPhoneList;
- }
-
- /**
- * @hide
- */
- public final List<EmailData> getEmailList() {
- return mEmailList;
- }
-
- /**
- * @hide
- */
- public final List<PostalData> getPostalList() {
- return mPostalList;
- }
-
- /**
- * @hide
- */
- public final List<OrganizationData> getOrganizationList() {
- return mOrganizationList;
- }
-
- /**
- * Add a phone info to phoneList.
- * @param data phone number
- * @param type type col of content://contacts/phones
- * @param label lable col of content://contacts/phones
- */
- private void addPhone(int type, String data, String label, boolean isPrimary){
+ private void addPhone(int type, String data, String label, boolean isPrimary) {
if (mPhoneList == null) {
mPhoneList = new ArrayList<PhoneData>();
}
- StringBuilder builder = new StringBuilder();
- String trimed = data.trim();
- int length = trimed.length();
- for (int i = 0; i < length; i++) {
- char ch = trimed.charAt(i);
- if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
- builder.append(ch);
+ final StringBuilder builder = new StringBuilder();
+ final String trimed = data.trim();
+ final String formattedNumber;
+ if (type == Phone.TYPE_PAGER) {
+ formattedNumber = trimed;
+ } else {
+ final int length = trimed.length();
+ for (int i = 0; i < length; i++) {
+ char ch = trimed.charAt(i);
+ if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
+ builder.append(ch);
+ }
}
- }
-
- PhoneData phoneData = new PhoneData(type,
- PhoneNumberUtils.formatNumber(builder.toString()),
- label, isPrimary);
+ // Use NANP in default when there's no information about locale.
+ final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
+ PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
+ }
+ PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
mPhoneList.add(phoneData);
}
@@ -656,80 +514,149 @@ public class ContactStruct {
}
mNickNameList.add(nickName);
}
-
+
private void addEmail(int type, String data, String label, boolean isPrimary){
if (mEmailList == null) {
mEmailList = new ArrayList<EmailData>();
}
mEmailList.add(new EmailData(type, data, label, isPrimary));
}
-
+
private void addPostal(int type, List<String> propValueList, String label, boolean isPrimary){
if (mPostalList == null) {
- mPostalList = new ArrayList<PostalData>();
+ mPostalList = new ArrayList<PostalData>(0);
}
mPostalList.add(new PostalData(type, propValueList, label, isPrimary));
}
-
- private void addOrganization(int type, final String companyName,
- final String positionName, boolean isPrimary) {
+
+ /**
+ * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * {@link #handleTitleValue(String)}.
+ */
+ private void addNewOrganization(int type, final String companyName,
+ final String departmentName,
+ final String titleName, boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
- mOrganizationList.add(new OrganizationData(type, companyName, positionName, isPrimary));
+ mOrganizationList.add(new OrganizationData(type, companyName,
+ departmentName, titleName, isPrimary));
+ }
+
+ private static final List<String> sEmptyList =
+ Collections.unmodifiableList(new ArrayList<String>(0));
+
+ /**
+ * Set "ORG" related values to the appropriate data. If there's more than one
+ * {@link OrganizationData} objects, this input data are attached to the last one which
+ * does not have valid values (not including empty but only null). If there's no
+ * {@link OrganizationData} object, a new {@link OrganizationData} is created,
+ * whose title is set to null.
+ */
+ private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ if (orgList == null) {
+ orgList = sEmptyList;
+ }
+ final String companyName;
+ final String departmentName;
+ final int size = orgList.size();
+ switch (size) {
+ case 0: {
+ companyName = "";
+ departmentName = null;
+ break;
+ }
+ case 1: {
+ companyName = orgList.get(0);
+ departmentName = null;
+ break;
+ }
+ default: { // More than 1.
+ companyName = orgList.get(0);
+ // We're not sure which is the correct string for department.
+ // In order to keep all the data, concatinate the rest of elements.
+ StringBuilder builder = new StringBuilder();
+ for (int i = 1; i < size; i++) {
+ if (i > 1) {
+ builder.append(' ');
+ }
+ builder.append(orgList.get(i));
+ }
+ departmentName = builder.toString();
+ }
+ }
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" title which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ // Not use TextUtils.isEmpty() since ORG was set but the elements might be empty.
+ // e.g. "ORG;PREF:;" -> Both companyName and departmentName become empty but not null.
+ if (organizationData.companyName == null &&
+ organizationData.departmentName == null) {
+ // Probably the "TITLE" property comes before the "ORG" property via
+ // handleTitleLine().
+ organizationData.companyName = companyName;
+ organizationData.departmentName = departmentName;
+ organizationData.isPrimary = isPrimary;
+ return;
+ }
+ }
+ // No OrganizatioData is available. Create another one, with "null" title, which may be
+ // added via handleTitleValue().
+ addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ }
+
+ /**
+ * Set "title" value to the appropriate data. If there's more than one
+ * OrganizationData objects, this input is attached to the last one which does not
+ * have valid title value (not including empty but only null). If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * set to null.
+ */
+ private void handleTitleValue(final String title) {
+ if (mOrganizationList == null) {
+ // Create new first organization entry, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ return;
+ }
+ for (OrganizationData organizationData : mOrganizationList) {
+ if (organizationData.titleName == null) {
+ organizationData.titleName = title;
+ return;
+ }
+ }
+ // No Organization is available. Create another one, with "null" other info, which may be
+ // added via handleOrgValue().
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
}
-
- private void addIm(int type, String data, String label, boolean isPrimary) {
+
+ private void addIm(int protocol, String customProtocol, int type,
+ String propValue, boolean isPrimary) {
if (mImList == null) {
mImList = new ArrayList<ImData>();
}
- mImList.add(new ImData(type, data, label, isPrimary));
+ mImList.add(new ImData(protocol, customProtocol, type, propValue, isPrimary));
}
-
+
private void addNote(final String note) {
if (mNoteList == null) {
mNoteList = new ArrayList<String>(1);
}
mNoteList.add(note);
}
-
- private void addPhotoBytes(String formatName, byte[] photoBytes) {
+
+ private void addPhotoBytes(String formatName, byte[] photoBytes, boolean isPrimary) {
if (mPhotoList == null) {
mPhotoList = new ArrayList<PhotoData>(1);
}
- final PhotoData photoData = new PhotoData(0, null, photoBytes);
+ final PhotoData photoData = new PhotoData(0, null, photoBytes, isPrimary);
mPhotoList.add(photoData);
}
- /**
- * Set "position" value to the appropriate data. If there's more than one
- * OrganizationData objects, the value is set to the last one. If there's no
- * OrganizationData object, a new OrganizationData is created, whose company name is
- * empty.
- *
- * TODO: incomplete logic. fix this:
- *
- * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
- * know how to handle it in general cases...
- * ----
- * TITLE:Software Engineer
- * ORG:Google
- * ----
- */
- private void setPosition(String positionValue) {
- if (mOrganizationList == null) {
- mOrganizationList = new ArrayList<OrganizationData>();
- }
- int size = mOrganizationList.size();
- if (size == 0) {
- addOrganization(ContactsContract.CommonDataKinds.Organization.TYPE_OTHER,
- "", null, false);
- size = 1;
- }
- OrganizationData lastData = mOrganizationList.get(size - 1);
- lastData.positionName = positionValue;
- }
-
@SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) {
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
@@ -742,82 +669,120 @@ public class ContactStruct {
}
switch (size) {
- // fallthrough
- case 5:
- mSuffix = elems.get(4);
- case 4:
- mPrefix = elems.get(3);
- case 3:
- mMiddleName = elems.get(2);
- case 2:
- mGivenName = elems.get(1);
- default:
- mFamilyName = elems.get(0);
+ // fallthrough
+ case 5: mSuffix = elems.get(4);
+ case 4: mPrefix = elems.get(3);
+ case 3: mMiddleName = elems.get(2);
+ case 2: mGivenName = elems.get(1);
+ default: mFamilyName = elems.get(0);
}
}
-
+
/**
- * Some Japanese mobile phones use this field for phonetic name,
- * since vCard 2.1 does not have "SORT-STRING" type.
- * Also, in some cases, the field has some ';'s in it.
- * Assume the ';' means the same meaning in N property
+ * Note: Some Japanese mobile phones use this field for phonetic name,
+ * since vCard 2.1 does not have "SORT-STRING" type.
+ * Also, in some cases, the field has some ';'s in it.
+ * Assume the ';' means the same meaning in N property
*/
@SuppressWarnings("fallthrough")
private void handlePhoneticNameFromSound(List<String> elems) {
- // Family, Given, Middle. (1-3)
- // This is not from specification but mere assumption. Some Japanese phones use this order.
+ if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticMiddleName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ // This means the other properties like "X-PHONETIC-FIRST-NAME" was already found.
+ // Ignore "SOUND;X-IRMC-N".
+ return;
+ }
+
int size;
if (elems == null || (size = elems.size()) < 1) {
return;
}
+
+ // Assume that the order is "Family, Given, Middle".
+ // This is not from specification but mere assumption. Some Japanese phones use this order.
if (size > 3) {
size = 3;
}
+ if (elems.get(0).length() > 0) {
+ boolean onlyFirstElemIsNonEmpty = true;
+ for (int i = 1; i < size; i++) {
+ if (elems.get(i).length() > 0) {
+ onlyFirstElemIsNonEmpty = false;
+ break;
+ }
+ }
+ if (onlyFirstElemIsNonEmpty) {
+ final String[] namesArray = elems.get(0).split(" ");
+ final int nameArrayLength = namesArray.length;
+ if (nameArrayLength == 3) {
+ // Assume the string is "Family Middle Given".
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticMiddleName = namesArray[1];
+ mPhoneticGivenName = namesArray[2];
+ } else if (nameArrayLength == 2) {
+ // Assume the string is "Family Given" based on the Japanese mobile
+ // phones' preference.
+ mPhoneticFamilyName = namesArray[0];
+ mPhoneticGivenName = namesArray[1];
+ } else {
+ mPhoneticFullName = elems.get(0);
+ }
+ return;
+ }
+ }
+
switch (size) {
- // fallthrough
- case 3:
- mPhoneticMiddleName = elems.get(2);
- case 2:
- mPhoneticGivenName = elems.get(1);
- default:
- mPhoneticFamilyName = elems.get(0);
+ // fallthrough
+ case 3: mPhoneticMiddleName = elems.get(2);
+ case 2: mPhoneticGivenName = elems.get(1);
+ default: mPhoneticFamilyName = elems.get(0);
}
}
- public void addProperty(Property property) {
- String propName = property.mPropertyName;
+ public void addProperty(final Property property) {
+ final String propName = property.mPropertyName;
final Map<String, Collection<String>> paramMap = property.mParameterMap;
final List<String> propValueList = property.mPropertyValueList;
byte[] propBytes = property.mPropertyBytes;
-
+
if (propValueList.size() == 0) {
return;
}
final String propValue = listToString(propValueList).trim();
-
- if (propName.equals("VERSION")) {
+
+ if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
- } else if (propName.equals("FN")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
mFullName = propValue;
- } else if (propName.equals("NAME") && mFullName == null) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
mFullName = propValue;
- } else if (propName.equals("N")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_N)) {
handleNProperty(propValueList);
- } else if (propName.equals("SORT-STRING")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
- } else if (propName.equals("NICKNAME") || propName.equals("X-NICKNAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
+ propName.equals(VCardConstants.ImportOnly.PROPERTY_X_NICKNAME)) {
addNickName(propValue);
- } else if (propName.equals("SOUND")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- if (typeCollection != null && typeCollection.contains(Constants.ATTR_TYPE_X_IRMC_N)) {
- handlePhoneticNameFromSound(propValueList);
+ } else if (propName.equals(VCardConstants.PROPERTY_SOUND)) {
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ if (typeCollection != null
+ && typeCollection.contains(VCardConstants.PARAM_TYPE_X_IRMC_N)) {
+ // As of 2009-10-08, Parser side does not split a property value into separated
+ // values using ';' (in other words, propValueList.size() == 1),
+ // which is correct behavior from the view of vCard 2.1.
+ // But we want it to be separated, so do the separation here.
+ final List<String> phoneticNameList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
}
- } else if (propName.equals("ADR")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ADR)) {
boolean valuesAreAllEmpty = true;
for (String value : propValueList) {
if (value.length() > 0) {
@@ -832,27 +797,25 @@ public class ContactStruct {
int type = -1;
String label = "";
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Address) {
- // Only first "PREF" is considered.
- mPrefIsSet_Address = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = StructuredPostal.TYPE_HOME;
label = "";
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK) ||
- typeString.equalsIgnoreCase("COMPANY")) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK) ||
+ typeString.equalsIgnoreCase(VCardConstants.PARAM_EXTRA_TYPE_COMPANY)) {
// "COMPANY" seems emitted by Windows Mobile, which is not
// specifically supported by vCard 2.1. We assume this is same
// as "WORK".
type = StructuredPostal.TYPE_WORK;
label = "";
- } else if (typeString.equals("PARCEL") ||
- typeString.equals("DOM") ||
- typeString.equals("INTL")) {
+ } else if (typeString.equals(VCardConstants.PARAM_ADR_TYPE_PARCEL) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_DOM) ||
+ typeString.equals(VCardConstants.PARAM_ADR_TYPE_INTL)) {
// We do not have any appropriate way to store this information.
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -871,23 +834,21 @@ public class ContactStruct {
}
addPostal(type, propValueList, label, isPrimary);
- } else if (propName.equals("EMAIL")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_EMAIL)) {
int type = -1;
String label = null;
boolean isPrimary = false;
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Email) {
- // Only first "PREF" is considered.
- mPrefIsSet_Email = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_HOME)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_HOME)) {
type = Email.TYPE_HOME;
- } else if (typeString.equals(Constants.ATTR_TYPE_WORK)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_WORK)) {
type = Email.TYPE_WORK;
- } else if (typeString.equals(Constants.ATTR_TYPE_CELL)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_CELL)) {
type = Email.TYPE_MOBILE;
} else {
if (typeString.startsWith("X-") && type < 0) {
@@ -905,50 +866,48 @@ public class ContactStruct {
type = Email.TYPE_OTHER;
}
addEmail(type, propValue, label, isPrimary);
- } else if (propName.equals("ORG")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_ORG)) {
// vCard specification does not specify other types.
- int type = Organization.TYPE_WORK;
+ final int type = Organization.TYPE_WORK;
boolean isPrimary = false;
-
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF) && !mPrefIsSet_Organization) {
- // vCard specification officially does not have PREF in ORG.
- // This is just for safety.
- mPrefIsSet_Organization = true;
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
}
}
}
-
- StringBuilder builder = new StringBuilder();
- for (Iterator<String> iter = propValueList.iterator(); iter.hasNext();) {
- builder.append(iter.next());
- if (iter.hasNext()) {
- builder.append(' ');
- }
- }
- addOrganization(type, builder.toString(), "", isPrimary);
- } else if (propName.equals("TITLE")) {
- setPosition(propValue);
- } else if (propName.equals("ROLE")) {
- setPosition(propValue);
- } else if (propName.equals("PHOTO") || propName.equals("LOGO")) {
- String formatName = null;
- Collection<String> typeCollection = paramMap.get("TYPE");
- if (typeCollection != null) {
- formatName = typeCollection.iterator().next();
- }
+ handleOrgValue(type, propValueList, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
+ handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
+ // This conflicts with TITLE. Ignore for now...
+ // handleTitleValue(propValue);
+ } else if (propName.equals(VCardConstants.PROPERTY_PHOTO) ||
+ propName.equals(VCardConstants.PROPERTY_LOGO)) {
Collection<String> paramMapValue = paramMap.get("VALUE");
if (paramMapValue != null && paramMapValue.contains("URL")) {
// Currently we do not have appropriate example for testing this case.
} else {
- addPhotoBytes(formatName, propBytes);
+ final Collection<String> typeCollection = paramMap.get("TYPE");
+ String formatName = null;
+ boolean isPrimary = false;
+ if (typeCollection != null) {
+ for (String typeValue : typeCollection) {
+ if (VCardConstants.PARAM_TYPE_PREF.equals(typeValue)) {
+ isPrimary = true;
+ } else if (formatName == null){
+ formatName = typeValue;
+ }
+ }
+ }
+ addPhotoBytes(formatName, propBytes, isPrimary);
}
- } else if (propName.equals("TEL")) {
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- Object typeObject = VCardUtils.getPhoneTypeFromStrings(typeCollection);
+ } else if (propName.equals(VCardConstants.PROPERTY_TEL)) {
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final Object typeObject =
+ VCardUtils.getPhoneTypeFromStrings(typeCollection, propValue);
final int type;
final String label;
if (typeObject instanceof Integer) {
@@ -958,66 +917,68 @@ public class ContactStruct {
type = Phone.TYPE_CUSTOM;
label = typeObject.toString();
}
-
+
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
addPhone(type, propValue, label, isPrimary);
- } else if (propName.equals(Constants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_SKYPE_PSTNNUMBER)) {
// The phone number available via Skype.
- Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
- // XXX: should use TYPE_CUSTOM + the label "Skype"? (which may need localization)
- int type = Phone.TYPE_OTHER;
- final String label = null;
+ Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
+ final int type = Phone.TYPE_OTHER;
final boolean isPrimary;
- if (!mPrefIsSet_Phone && typeCollection != null &&
- typeCollection.contains(Constants.ATTR_TYPE_PREF)) {
- mPrefIsSet_Phone = true;
+ if (typeCollection != null && typeCollection.contains(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
} else {
isPrimary = false;
}
- addPhone(type, propValue, label, isPrimary);
- } else if (sImMap.containsKey(propName)){
- int type = sImMap.get(propName);
+ addPhone(type, propValue, null, isPrimary);
+ } else if (sImMap.containsKey(propName)) {
+ final int protocol = sImMap.get(propName);
boolean isPrimary = false;
- final Collection<String> typeCollection = paramMap.get(Constants.ATTR_TYPE);
+ int type = -1;
+ final Collection<String> typeCollection = paramMap.get(VCardConstants.PARAM_TYPE);
if (typeCollection != null) {
for (String typeString : typeCollection) {
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
isPrimary = true;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_HOME)) {
- type = Phone.TYPE_HOME;
- } else if (typeString.equalsIgnoreCase(Constants.ATTR_TYPE_WORK)) {
- type = Phone.TYPE_WORK;
+ } else if (type < 0) {
+ if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_HOME)) {
+ type = Im.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase(VCardConstants.PARAM_TYPE_WORK)) {
+ type = Im.TYPE_WORK;
+ }
}
}
}
if (type < 0) {
type = Phone.TYPE_HOME;
}
- addIm(type, propValue, null, isPrimary);
- } else if (propName.equals("NOTE")) {
+ addIm(protocol, null, type, propValue, isPrimary);
+ } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
addNote(propValue);
- } else if (propName.equals("URL")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_URL)) {
if (mWebsiteList == null) {
mWebsiteList = new ArrayList<String>(1);
}
mWebsiteList.add(propValue);
- } else if (propName.equals("X-PHONETIC-FIRST-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
+ mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
- } else if (propName.equals("X-PHONETIC-MIDDLE-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
mPhoneticMiddleName = propValue;
- } else if (propName.equals("X-PHONETIC-LAST-NAME")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_LAST_NAME)) {
mPhoneticFamilyName = propValue;
- } else if (propName.equals("BDAY")) {
- mBirthday = propValue;
- /*} else if (propName.equals("REV")) {
+ } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
+ final List<String> customPropertyList =
+ VCardUtils.constructListFromValue(propValue,
+ VCardConfig.isV30(mVCardType));
+ handleAndroidCustomProperty(customPropertyList);
+ /*} else if (propName.equals("REV")) {
// Revision of this VCard entry. I think we can ignore this.
} else if (propName.equals("UID")) {
} else if (propName.equals("KEY")) {
@@ -1044,43 +1005,23 @@ public class ContactStruct {
}
}
+ private void handleAndroidCustomProperty(final List<String> customPropertyList) {
+ if (mAndroidCustomPropertyList == null) {
+ mAndroidCustomPropertyList = new ArrayList<List<String>>();
+ }
+ mAndroidCustomPropertyList.add(customPropertyList);
+ }
+
/**
* Construct the display name. The constructed data must not be null.
*/
private void constructDisplayName() {
- if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
- StringBuilder builder = new StringBuilder();
- List<String> nameList;
- switch (VCardConfig.getNameOrderType(mVCardType)) {
- case VCardConfig.NAME_ORDER_JAPANESE:
- if (VCardUtils.containsOnlyPrintableAscii(mFamilyName) &&
- VCardUtils.containsOnlyPrintableAscii(mGivenName)) {
- nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
- } else {
- nameList = Arrays.asList(mPrefix, mFamilyName, mMiddleName, mGivenName, mSuffix);
- }
- break;
- case VCardConfig.NAME_ORDER_EUROPE:
- nameList = Arrays.asList(mPrefix, mMiddleName, mGivenName, mFamilyName, mSuffix);
- break;
- default:
- nameList = Arrays.asList(mPrefix, mGivenName, mMiddleName, mFamilyName, mSuffix);
- break;
- }
- boolean first = true;
- for (String namePart : nameList) {
- if (!TextUtils.isEmpty(namePart)) {
- if (first) {
- first = false;
- } else {
- builder.append(' ');
- }
- builder.append(namePart);
- }
- }
- mDisplayName = builder.toString();
- } else if (!TextUtils.isEmpty(mFullName)) {
+ // FullName (created via "FN" or "NAME" field) is prefered.
+ if (!TextUtils.isEmpty(mFullName)) {
mDisplayName = mFullName;
+ } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
+ mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
+ mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
} else if (!(TextUtils.isEmpty(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
@@ -1091,46 +1032,31 @@ public class ContactStruct {
mDisplayName = mPhoneList.get(0).data;
} else if (mPostalList != null && mPostalList.size() > 0) {
mDisplayName = mPostalList.get(0).getFormattedAddress(mVCardType);
+ } else if (mOrganizationList != null && mOrganizationList.size() > 0) {
+ mDisplayName = mOrganizationList.get(0).getFormattedString();
}
if (mDisplayName == null) {
mDisplayName = "";
}
}
-
+
/**
- * Consolidate several fielsds (like mName) using name candidates,
+ * Consolidate several fielsds (like mName) using name candidates,
*/
public void consolidateFields() {
constructDisplayName();
-
+
if (mPhoneticFullName != null) {
mPhoneticFullName = mPhoneticFullName.trim();
}
-
- // If there is no "PREF", we choose the first entries as primary.
- if (!mPrefIsSet_Phone && mPhoneList != null && mPhoneList.size() > 0) {
- mPhoneList.get(0).isPrimary = true;
- }
-
- if (!mPrefIsSet_Address && mPostalList != null && mPostalList.size() > 0) {
- mPostalList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Email && mEmailList != null && mEmailList.size() > 0) {
- mEmailList.get(0).isPrimary = true;
- }
- if (!mPrefIsSet_Organization && mOrganizationList != null && mOrganizationList.size() > 0) {
- mOrganizationList.get(0).isPrimary = true;
- }
}
-
- // From GoogleSource.java in Contacts app.
- private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
- public void pushIntoContentResolver(ContentResolver resolver) {
+ public Uri pushIntoContentResolver(ContentResolver resolver) {
ArrayList<ContentProviderOperation> operationList =
- new ArrayList<ContentProviderOperation>();
+ new ArrayList<ContentProviderOperation>();
+ // After applying the batch the first result's Uri is returned so it is important that
+ // the RawContact is the first operation that gets inserted into the list
ContentProviderOperation.Builder builder =
ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
String myGroupsId = null;
@@ -1139,7 +1065,6 @@ public class ContactStruct {
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
// Assume that caller side creates this group if it does not exist.
- // TODO: refactor this code along with the change in GoogleSource.java
if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
Groups.SOURCE_ID },
@@ -1161,7 +1086,7 @@ public class ContactStruct {
}
operationList.add(builder.build());
- {
+ if (!nameFieldsAreEmpty()) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
@@ -1172,31 +1097,31 @@ public class ContactStruct {
builder.withValue(StructuredName.PREFIX, mPrefix);
builder.withValue(StructuredName.SUFFIX, mSuffix);
- builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
- builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
- builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ if (!(TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName))) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticGivenName);
+ builder.withValue(StructuredName.PHONETIC_FAMILY_NAME, mPhoneticFamilyName);
+ builder.withValue(StructuredName.PHONETIC_MIDDLE_NAME, mPhoneticMiddleName);
+ } else if (!TextUtils.isEmpty(mPhoneticFullName)) {
+ builder.withValue(StructuredName.PHONETIC_GIVEN_NAME, mPhoneticFullName);
+ }
builder.withValue(StructuredName.DISPLAY_NAME, getDisplayName());
operationList.add(builder.build());
}
if (mNickNameList != null && mNickNameList.size() > 0) {
- boolean first = true;
for (String nickName : mNickNameList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Nickname.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE);
-
builder.withValue(Nickname.TYPE, Nickname.TYPE_DEFAULT);
builder.withValue(Nickname.NAME, nickName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
- }
operationList.add(builder.build());
}
}
-
+
if (mPhoneList != null) {
for (PhoneData phoneData : mPhoneList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1209,30 +1134,34 @@ public class ContactStruct {
}
builder.withValue(Phone.NUMBER, phoneData.data);
if (phoneData.isPrimary) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ builder.withValue(Phone.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mOrganizationList != null) {
- boolean first = true;
for (OrganizationData organizationData : mOrganizationList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Organization.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-
- // Currently, we do not use TYPE_CUSTOM.
builder.withValue(Organization.TYPE, organizationData.type);
- builder.withValue(Organization.COMPANY, organizationData.companyName);
- builder.withValue(Organization.TITLE, organizationData.positionName);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
+ if (organizationData.companyName != null) {
+ builder.withValue(Organization.COMPANY, organizationData.companyName);
+ }
+ if (organizationData.departmentName != null) {
+ builder.withValue(Organization.DEPARTMENT, organizationData.departmentName);
+ }
+ if (organizationData.titleName != null) {
+ builder.withValue(Organization.TITLE, organizationData.titleName);
+ }
+ if (organizationData.isPrimary) {
+ builder.withValue(Organization.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
}
-
+
if (mEmailList != null) {
for (EmailData emailData : mEmailList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1250,7 +1179,7 @@ public class ContactStruct {
operationList.add(builder.build());
}
}
-
+
if (mPostalList != null) {
for (PostalData postalData : mPostalList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -1259,45 +1188,41 @@ public class ContactStruct {
operationList.add(builder.build());
}
}
-
+
if (mImList != null) {
for (ImData imData : mImList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Im.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
-
builder.withValue(Im.TYPE, imData.type);
- if (imData.type == Im.TYPE_CUSTOM) {
- builder.withValue(Im.LABEL, imData.label);
+ builder.withValue(Im.PROTOCOL, imData.protocol);
+ if (imData.protocol == Im.PROTOCOL_CUSTOM) {
+ builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
}
- builder.withValue(Im.DATA, imData.data);
if (imData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
}
}
-
+
if (mNoteList != null) {
for (String note : mNoteList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Note.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE);
-
builder.withValue(Note.NOTE, note);
operationList.add(builder.build());
}
}
-
+
if (mPhotoList != null) {
- boolean first = true;
for (PhotoData photoData : mPhotoList) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Photo.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
builder.withValue(Photo.PHOTO, photoData.photoBytes);
- if (first) {
- builder.withValue(Data.IS_PRIMARY, 1);
- first = false;
+ if (photoData.isPrimary) {
+ builder.withValue(Photo.IS_PRIMARY, 1);
}
operationList.add(builder.build());
}
@@ -1310,12 +1235,12 @@ public class ContactStruct {
builder.withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE);
builder.withValue(Website.URL, website);
// There's no information about the type of URL in vCard.
- // We use TYPE_HOME for safety.
- builder.withValue(Website.TYPE, Website.TYPE_HOME);
+ // We use TYPE_HOMEPAGE for safety.
+ builder.withValue(Website.TYPE, Website.TYPE_HOMEPAGE);
operationList.add(builder.build());
}
}
-
+
if (!TextUtils.isEmpty(mBirthday)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
@@ -1325,6 +1250,36 @@ public class ContactStruct {
operationList.add(builder.build());
}
+ if (mAndroidCustomPropertyList != null) {
+ for (List<String> customPropertyList : mAndroidCustomPropertyList) {
+ int size = customPropertyList.size();
+ if (size < 2 || TextUtils.isEmpty(customPropertyList.get(0))) {
+ continue;
+ } else if (size > VCardConstants.MAX_DATA_COLUMN + 1) {
+ size = VCardConstants.MAX_DATA_COLUMN + 1;
+ customPropertyList =
+ customPropertyList.subList(0, VCardConstants.MAX_DATA_COLUMN + 2);
+ }
+
+ int i = 0;
+ for (final String customPropertyValue : customPropertyList) {
+ if (i == 0) {
+ final String mimeType = customPropertyValue;
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, mimeType);
+ } else { // 1 <= i && i <= MAX_DATA_COLUMNS
+ if (!TextUtils.isEmpty(customPropertyValue)) {
+ builder.withValue("data" + i, customPropertyValue);
+ }
+ }
+
+ i++;
+ }
+ operationList.add(builder.build());
+ }
+ }
+
if (myGroupsId != null) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
@@ -1334,18 +1289,49 @@ public class ContactStruct {
}
try {
- resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
+ ContentProviderResult[] results = resolver.applyBatch(
+ ContactsContract.AUTHORITY, operationList);
+ // the first result is always the raw_contact. return it's uri so
+ // that it can be found later. do null checking for badly behaving
+ // ContentResolvers
+ return (results == null || results.length == 0 || results[0] == null)
+ ? null
+ : results[0].uri;
} catch (RemoteException e) {
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ return null;
} catch (OperationApplicationException e) {
Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+ return null;
}
}
+ public static VCardEntry buildFromResolver(ContentResolver resolver) {
+ return buildFromResolver(resolver, Contacts.CONTENT_URI);
+ }
+
+ public static VCardEntry buildFromResolver(ContentResolver resolver, Uri uri) {
+
+ return null;
+ }
+
+ private boolean nameFieldsAreEmpty() {
+ return (TextUtils.isEmpty(mFamilyName)
+ && TextUtils.isEmpty(mMiddleName)
+ && TextUtils.isEmpty(mGivenName)
+ && TextUtils.isEmpty(mPrefix)
+ && TextUtils.isEmpty(mSuffix)
+ && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mPhoneticFamilyName)
+ && TextUtils.isEmpty(mPhoneticMiddleName)
+ && TextUtils.isEmpty(mPhoneticGivenName)
+ && TextUtils.isEmpty(mPhoneticFullName));
+ }
+
public boolean isIgnorable() {
return getDisplayName().length() == 0;
}
-
+
private String listToString(List<String> list){
final int size = list.size();
if (size > 1) {
@@ -1364,4 +1350,99 @@ public class ContactStruct {
return "";
}
}
+
+ // All getter methods should be used carefully, since they may change
+ // in the future as of 2009-10-05, on which I cannot be sure this structure
+ // is completely consolidated.
+ //
+ // Also note that these getter methods should be used only after
+ // all properties being pushed into this object. If not, incorrect
+ // value will "be stored in the local cache and" be returned to you.
+
+ public String getFamilyName() {
+ return mFamilyName;
+ }
+
+ public String getGivenName() {
+ return mGivenName;
+ }
+
+ public String getMiddleName() {
+ return mMiddleName;
+ }
+
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public String getFullName() {
+ return mFullName;
+ }
+
+ public String getPhoneticFamilyName() {
+ return mPhoneticFamilyName;
+ }
+
+ public String getPhoneticGivenName() {
+ return mPhoneticGivenName;
+ }
+
+ public String getPhoneticMiddleName() {
+ return mPhoneticMiddleName;
+ }
+
+ public String getPhoneticFullName() {
+ return mPhoneticFullName;
+ }
+
+ public final List<String> getNickNameList() {
+ return mNickNameList;
+ }
+
+ public String getBirthday() {
+ return mBirthday;
+ }
+
+ public final List<String> getNotes() {
+ return mNoteList;
+ }
+
+ public final List<PhoneData> getPhoneList() {
+ return mPhoneList;
+ }
+
+ public final List<EmailData> getEmailList() {
+ return mEmailList;
+ }
+
+ public final List<PostalData> getPostalList() {
+ return mPostalList;
+ }
+
+ public final List<OrganizationData> getOrganizationList() {
+ return mOrganizationList;
+ }
+
+ public final List<ImData> getImList() {
+ return mImList;
+ }
+
+ public final List<PhotoData> getPhotoList() {
+ return mPhotoList;
+ }
+
+ public final List<String> getWebsiteList() {
+ return mWebsiteList;
+ }
+
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ constructDisplayName();
+ }
+ return mDisplayName;
+ }
}
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
new file mode 100644
index 0000000..59a2baf
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -0,0 +1,68 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * <P>
+ * {@link VCardEntryHandler} implementation which commits the entry to ContentResolver.
+ * </P>
+ * <P>
+ * Note:<BR />
+ * Each vCard may contain big photo images encoded by BASE64,
+ * If we store all vCard entries in memory, OutOfMemoryError may be thrown.
+ * Thus, this class push each VCard entry into ContentResolver immediately.
+ * </P>
+ */
+public class VCardEntryCommitter implements VCardEntryHandler {
+ public static String LOG_TAG = "VCardEntryComitter";
+
+ private final ContentResolver mContentResolver;
+ private long mTimeToCommit;
+ private ArrayList<Uri> mCreatedUris = new ArrayList<Uri>();
+
+ public VCardEntryCommitter(ContentResolver resolver) {
+ mContentResolver = resolver;
+ }
+
+ public void onStart() {
+ }
+
+ public void onEnd() {
+ if (VCardConfig.showPerformanceLog()) {
+ Log.d(LOG_TAG, String.format("time to commit entries: %d ms", mTimeToCommit));
+ }
+ }
+
+ public void onEntryCreated(final VCardEntry contactStruct) {
+ long start = System.currentTimeMillis();
+ mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
+ mTimeToCommit += System.currentTimeMillis() - start;
+ }
+
+ /**
+ * Returns the list of created Uris. This list should not be modified by the caller as it is
+ * not a clone.
+ */
+ public ArrayList<Uri> getCreatedUris() {
+ return mCreatedUris;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardDataBuilder.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index d2026d0..290ca2b 100644
--- a/core/java/android/pim/vcard/VCardDataBuilder.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -30,123 +30,105 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-/**
- * VBuilder for VCard. VCard may contain big photo images encoded by BASE64,
- * If we store all VNode entries in memory like VDataBuilder.java,
- * OutOfMemoryError may be thrown. Thus, this class push each VCard entry into
- * ContentResolver immediately.
- */
-public class VCardDataBuilder implements VCardBuilder {
- static private String LOG_TAG = "VCardDataBuilder";
-
+public class VCardEntryConstructor implements VCardInterpreter {
+ private static String LOG_TAG = "VCardEntryConstructor";
+
/**
* If there's no other information available, this class uses this charset for encoding
- * byte arrays.
+ * byte arrays to String.
*/
- static public String TARGET_CHARSET = "UTF-8";
-
- private ContactStruct.Property mCurrentProperty = new ContactStruct.Property();
- private ContactStruct mCurrentContactStruct;
+ /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
+
+ private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
+ private VCardEntry mCurrentContactStruct;
private String mParamType;
/**
- * The charset using which VParser parses the text.
+ * The charset using which {@link VCardInterpreter} parses the text.
*/
- private String mSourceCharset;
-
+ private String mInputCharset;
+
/**
* The charset with which byte array is encoded to String.
*/
- private String mTargetCharset;
- private boolean mStrictLineBreakParsing;
-
+ final private String mCharsetForDecodedBytes;
+ final private boolean mStrictLineBreakParsing;
final private int mVCardType;
final private Account mAccount;
- // Just for testing.
+ /** For measuring performance. */
private long mTimePushIntoContentResolver;
-
- private List<EntryHandler> mEntryHandlers = new ArrayList<EntryHandler>();
-
- public VCardDataBuilder() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC, null);
+
+ final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+
+ public VCardEntryConstructor() {
+ this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
}
- /**
- * @hide
- */
- public VCardDataBuilder(int vcardType) {
+ public VCardEntryConstructor(final int vcardType) {
this(null, null, false, vcardType, null);
}
- /**
- * @hide
- */
- public VCardDataBuilder(String charset,
- boolean strictLineBreakParsing, int vcardType, Account account) {
+ public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
+ final int vcardType, final Account account) {
this(null, charset, strictLineBreakParsing, vcardType, account);
}
-
- /**
- * @hide
- */
- public VCardDataBuilder(String sourceCharset,
- String targetCharset,
- boolean strictLineBreakParsing,
- int vcardType,
- Account account) {
- if (sourceCharset != null) {
- mSourceCharset = sourceCharset;
+
+ public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
+ final boolean strictLineBreakParsing, final int vcardType,
+ final Account account) {
+ if (inputCharset != null) {
+ mInputCharset = inputCharset;
} else {
- mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+ mInputCharset = VCardConfig.DEFAULT_CHARSET;
}
- if (targetCharset != null) {
- mTargetCharset = targetCharset;
+ if (charsetForDetodedBytes != null) {
+ mCharsetForDecodedBytes = charsetForDetodedBytes;
} else {
- mTargetCharset = TARGET_CHARSET;
+ mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
}
mStrictLineBreakParsing = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
-
- public void addEntryHandler(EntryHandler entryHandler) {
+
+ public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
public void start() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onParsingStart();
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onStart();
}
}
public void end() {
- for (EntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onParsingEnd();
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
+ entryHandler.onEnd();
}
}
/**
+ * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
+ */
+ public void clear() {
+ mCurrentContactStruct = null;
+ mCurrentProperty = new VCardEntry.Property();
+ }
+
+ /**
* Assume that VCard is not nested. In other words, this code does not accept
*/
- public void startRecord(String type) {
- // TODO: add the method clear() instead of using null for reducing GC?
+ public void startEntry() {
if (mCurrentContactStruct != null) {
- // This means startRecord() is called inside startRecord() - endRecord() block.
- // TODO: should throw some Exception
Log.e(LOG_TAG, "Nested VCard code is not supported now.");
}
- if (!type.equalsIgnoreCase("VCARD")) {
- // TODO: add test case for this
- Log.e(LOG_TAG, "This is not VCARD!");
- }
-
- mCurrentContactStruct = new ContactStruct(mVCardType, mAccount);
+ mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
}
- public void endRecord() {
+ public void endEntry() {
mCurrentContactStruct.consolidateFields();
- for (EntryHandler entryHandler : mEntryHandlers) {
+ for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEntryCreated(mCurrentContactStruct);
}
mCurrentContactStruct = null;
@@ -165,9 +147,8 @@ public class VCardDataBuilder implements VCardBuilder {
}
public void propertyGroup(String group) {
- // ContactStruct does not support Group.
}
-
+
public void propertyParamType(String type) {
if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -184,26 +165,26 @@ public class VCardDataBuilder implements VCardBuilder {
mCurrentProperty.addParameter(mParamType, value);
mParamType = null;
}
-
- private String encodeString(String originalString, String targetCharset) {
- if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+
+ private String encodeString(String originalString, String charsetForDecodedBytes) {
+ if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
return originalString;
}
- Charset charset = Charset.forName(mSourceCharset);
+ Charset charset = Charset.forName(mInputCharset);
ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
- return new String(bytes, targetCharset);
+ return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return null;
}
}
-
- private String handleOneValue(String value, String targetCharset, String encoding) {
+
+ private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) {
mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
@@ -269,9 +250,9 @@ public class VCardDataBuilder implements VCardBuilder {
}
byte[] bytes;
try {
- bytes = builder.toString().getBytes(mSourceCharset);
+ bytes = builder.toString().getBytes(mInputCharset);
} catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
bytes = builder.toString().getBytes();
}
@@ -283,38 +264,37 @@ public class VCardDataBuilder implements VCardBuilder {
}
try {
- return new String(bytes, targetCharset);
+ return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return new String(bytes);
}
}
// Unknown encoding. Fall back to default.
}
- return encodeString(value, targetCharset);
+ return encodeString(value, charsetForDecodedBytes);
}
public void propertyValues(List<String> values) {
- if (values == null || values.size() == 0) {
+ if (values == null || values.isEmpty()) {
return;
}
final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
- String charset =
+ final String charset =
((charsetCollection != null) ? charsetCollection.iterator().next() : null);
- String targetCharset = CharsetUtils.nameForDefaultVendor(charset);
-
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
- String encoding =
+ final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
- if (targetCharset == null || targetCharset.length() == 0) {
- targetCharset = mTargetCharset;
+
+ String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
+ if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
+ charsetForDecodedBytes = mCharsetForDecodedBytes;
}
-
- for (String value : values) {
+
+ for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
- handleOneValue(value, targetCharset, encoding));
+ handleOneValue(value, charsetForDecodedBytes, encoding));
}
}
diff --git a/core/java/android/pim/vcard/VCardEntryCounter.java b/core/java/android/pim/vcard/VCardEntryCounter.java
index f99b46c..7bab50d 100644
--- a/core/java/android/pim/vcard/VCardEntryCounter.java
+++ b/core/java/android/pim/vcard/VCardEntryCounter.java
@@ -17,29 +17,32 @@ package android.pim.vcard;
import java.util.List;
-public class VCardEntryCounter implements VCardBuilder {
+/**
+ * The class which just counts the number of vCard entries in the specified input.
+ */
+public class VCardEntryCounter implements VCardInterpreter {
private int mCount;
-
+
public int getCount() {
return mCount;
}
-
+
public void start() {
}
-
+
public void end() {
}
- public void startRecord(String type) {
+ public void startEntry() {
}
- public void endRecord() {
+ public void endEntry() {
mCount++;
}
-
+
public void startProperty() {
}
-
+
public void endProperty() {
}
@@ -57,4 +60,4 @@ public class VCardEntryCounter implements VCardBuilder {
public void propertyValues(List<String> values) {
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/pim/vcard/EntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 7fb8114..83a67fe 100644
--- a/core/java/android/pim/vcard/EntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,23 +16,23 @@
package android.pim.vcard;
/**
- * Unlike {@link VCardBuilder}, this (and {@link VCardDataBuilder}) assumes
- * "each VCard entry should be correctly parsed and passed to each EntryHandler object",
+ * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
+ * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
*/
-public interface EntryHandler {
+public interface VCardEntryHandler {
/**
* Called when the parsing started.
*/
- public void onParsingStart();
+ public void onStart();
/**
* The method called when one VCard entry is successfully created
*/
- public void onEntryCreated(final ContactStruct entry);
+ public void onEntryCreated(final VCardEntry entry);
/**
* Called when the parsing ended.
* Able to be use this method for showing performance log, etc.
*/
- public void onParsingEnd();
+ public void onEnd();
}
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
new file mode 100644
index 0000000..b5237c0
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -0,0 +1,102 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.List;
+
+/**
+ * <P>
+ * The interface which should be implemented by the classes which have to analyze each
+ * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * </P>
+ * <P>
+ * Here, there are several terms specific to vCard (and this library).
+ * </P>
+ * <P>
+ * The term "entry" is one vCard representation in the input, which should start with "BEGIN:VCARD"
+ * and end with "END:VCARD".
+ * </P>
+ * <P>
+ * The term "property" is one line in vCard entry, which consists of "group", "property name",
+ * "parameter(param) names and values", and "property values".
+ * </P>
+ * <P>
+ * e.g. group1.propName;paramName1=paramValue1;paramName2=paramValue2;propertyValue1;propertyValue2...
+ * </P>
+ */
+public interface VCardInterpreter {
+ /**
+ * Called when vCard interpretation started.
+ */
+ void start();
+
+ /**
+ * Called when vCard interpretation finished.
+ */
+ void end();
+
+ /**
+ * Called when parsing one vCard entry started.
+ * More specifically, this method is called when "BEGIN:VCARD" is read.
+ */
+ void startEntry();
+
+ /**
+ * Called when parsing one vCard entry ended.
+ * More specifically, this method is called when "END:VCARD" is read.
+ * Note that {@link #startEntry()} may be called since
+ * vCard (especially 2.1) allows nested vCard.
+ */
+ void endEntry();
+
+ /**
+ * Called when reading one property started.
+ */
+ void startProperty();
+
+ /**
+ * Called when reading one property ended.
+ */
+ void endProperty();
+
+ /**
+ * @param group A group name. This method may be called more than once or may not be
+ * called at all, depending on how many gruoups are appended to the property.
+ */
+ void propertyGroup(String group);
+
+ /**
+ * @param name A property name like "N", "FN", "ADR", etc.
+ */
+ void propertyName(String name);
+
+ /**
+ * @param type A parameter name like "ENCODING", "CHARSET", etc.
+ */
+ void propertyParamType(String type);
+
+ /**
+ * @param value A parameter value. This method may be called without
+ * {@link #propertyParamType(String)} being called (when the vCard is vCard 2.1).
+ */
+ void propertyParamValue(String value);
+
+ /**
+ * @param values List of property values. The size of values would be 1 unless
+ * coressponding property name is "N", "ADR", or "ORG".
+ */
+ void propertyValues(List<String> values);
+}
diff --git a/core/java/android/pim/vcard/VCardBuilderCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index e3985b6..99f81f7 100644
--- a/core/java/android/pim/vcard/VCardBuilderCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -18,81 +18,84 @@ package android.pim.vcard;
import java.util.Collection;
import java.util.List;
-public class VCardBuilderCollection implements VCardBuilder {
-
- private final Collection<VCardBuilder> mVCardBuilderCollection;
+/**
+ * The {@link VCardInterpreter} implementation which aggregates more than one
+ * {@link VCardInterpreter} objects and make a user object treat them as one
+ * {@link VCardInterpreter} object.
+ */
+public class VCardInterpreterCollection implements VCardInterpreter {
+ private final Collection<VCardInterpreter> mInterpreterCollection;
- public VCardBuilderCollection(Collection<VCardBuilder> vBuilderCollection) {
- mVCardBuilderCollection = vBuilderCollection;
+ public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
+ mInterpreterCollection = interpreterCollection;
}
-
- public Collection<VCardBuilder> getVCardBuilderBaseCollection() {
- return mVCardBuilderCollection;
+
+ public Collection<VCardInterpreter> getCollection() {
+ return mInterpreterCollection;
}
-
+
public void start() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.start();
}
}
-
+
public void end() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.end();
}
}
- public void startRecord(String type) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
- builder.startRecord(type);
+ public void startEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.startEntry();
}
}
-
- public void endRecord() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
- builder.endRecord();
+
+ public void endEntry() {
+ for (VCardInterpreter builder : mInterpreterCollection) {
+ builder.endEntry();
}
}
public void startProperty() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.startProperty();
}
}
-
public void endProperty() {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.endProperty();
}
}
public void propertyGroup(String group) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyGroup(group);
}
}
public void propertyName(String name) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyName(name);
}
}
public void propertyParamType(String type) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamType(type);
}
}
public void propertyParamValue(String value) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyParamValue(value);
}
}
public void propertyValues(List<String> values) {
- for (VCardBuilder builder : mVCardBuilderCollection) {
+ for (VCardInterpreter builder : mInterpreterCollection) {
builder.propertyValues(values);
}
}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index b5e5049..57c52a6 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -21,63 +21,74 @@ import java.io.IOException;
import java.io.InputStream;
public abstract class VCardParser {
-
+ protected final int mParseType;
protected boolean mCanceled;
-
+
+ public VCardParser() {
+ this(VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ public VCardParser(int parseType) {
+ mParseType = parseType;
+ }
+
/**
+ * <P>
* Parses the given stream and send the VCard data into VCardBuilderBase object.
- *
+ * </P.
+ * <P>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
* formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
* In some exreme case, some VCard may have different charsets in one VCard (though
* we do not see any device which emits such kind of malicious data)
- *
+ * </P>
+ * <P>
* In order to avoid "misunderstanding" charset as much as possible, this method
* use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (with "CHARSET=..." attribute), the string is decoded to raw bytes and encoded to
+ * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
* the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
* characters, which is not completely sure. In some cases, this "decoding-encoding"
* scheme may fail. To avoid the case,
- *
- * We recommend you to use VCardSourceDetector and detect which kind of source the
+ * </P>
+ * <P>
+ * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
* VCard comes from and explicitly specify a charset using the result.
- *
+ * </P>
+ *
* @param is The source to parse.
- * @param builder The VCardBuilderBase object which used to construct data. If you want to
- * include multiple VCardBuilderBase objects in this field, consider using
- * {#link VCardBuilderCollection} class.
+ * @param interepreter A {@link VCardInterpreter} object which used to construct data.
* @return Returns true for success. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, VCardBuilder builder)
+ public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
/**
+ * <P>
* The method variants which accept charset.
- *
+ * </P>
+ * <P>
* RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
* UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
* phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
- * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification
- * (e.g. W53K).
- *
+ * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
+ * </P>
+ *
* @param is The source to parse.
* @param charset Charset to be used.
* @param builder The VCardBuilderBase object.
* @return Returns true when successful. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, String charset, VCardBuilder builder)
+ public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
throws IOException, VCardException;
/**
* The method variants which tells this object the operation is already canceled.
- * XXX: Is this really necessary?
- * @hide
*/
public abstract void parse(InputStream is, String charset,
- VCardBuilder builder, boolean canceled)
+ VCardInterpreter builder, boolean canceled)
throws IOException, VCardException;
/**
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 11b3888..fe8cfb0 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,11 +15,11 @@
*/
package android.pim.vcard;
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException;
import android.pim.vcard.exception.VCardInvalidCommentLineException;
import android.pim.vcard.exception.VCardInvalidLineException;
import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardNotSupportedException;
import android.pim.vcard.exception.VCardVersionException;
import android.util.Log;
@@ -31,13 +31,14 @@ import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Set;
/**
- * This class is used to parse vcard. Please refer to vCard Specification 2.1.
+ * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
*/
public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "vcard.VCardParser_V21";
-
+ private static final String LOG_TAG = "VCardParser_V21";
+
/** Store the known-type */
private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
@@ -52,7 +53,7 @@ public class VCardParser_V21 extends VCardParser {
/** Store the known-value */
private static final HashSet<String> sKnownValueSet = new HashSet<String>(
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-
+
/** Store the property names available in vCard 2.1 */
private static final HashSet<String> sAvailablePropertyNameSetV21 =
new HashSet<String>(Arrays.asList(
@@ -72,7 +73,7 @@ public class VCardParser_V21 extends VCardParser {
private String mPreviousLine;
/** The builder to build parsed data */
- protected VCardBuilder mBuilder = null;
+ protected VCardInterpreter mBuilder = null;
/**
* The encoding type. "Encoding" in vCard is different from "Charset".
@@ -82,7 +83,7 @@ public class VCardParser_V21 extends VCardParser {
protected final String sDefaultEncoding = "8BIT";
- // Should not directly read a line from this. Use getLine() instead.
+ // Should not directly read a line from this object. Use getLine() instead.
protected BufferedReader mReader;
// In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
@@ -91,9 +92,10 @@ public class VCardParser_V21 extends VCardParser {
// In order to reduce warning message as much as possible, we hold the value which made Logger
// emit a warning message.
- protected HashSet<String> mWarningValueMap = new HashSet<String>();
-
- // Just for debugging
+ protected Set<String> mUnknownTypeMap = new HashSet<String>();
+ protected Set<String> mUnknownValueMap = new HashSet<String>();
+
+ // For measuring performance.
private long mTimeTotal;
private long mTimeReadStartRecord;
private long mTimeReadEndRecord;
@@ -106,23 +108,25 @@ public class VCardParser_V21 extends VCardParser {
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
-
- /**
- * Create a new VCard parser.
- */
+
public VCardParser_V21() {
- super();
+ this(null);
}
public VCardParser_V21(VCardSourceDetector detector) {
- super();
- if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) {
+ this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ }
+
+ public VCardParser_V21(int parseType) {
+ super(parseType);
+ if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
mNestCount = 1;
}
}
-
+
/**
- * Parse the file at the given position
+ * Parses the file at the given position.
+ *
* vcard_file = [wsls] vcard [wsls]
*/
protected void parseVCardFile() throws IOException, VCardException {
@@ -146,18 +150,22 @@ public class VCardParser_V21 extends VCardParser {
}
}
- protected String getVersion() {
- return "2.1";
+ protected int getVersion() {
+ return VCardConfig.FLAG_V21;
}
-
+
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
/**
* @return true when the propertyName is a valid property name.
*/
protected boolean isValidPropertyName(String propertyName) {
if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
propertyName.startsWith("X-")) &&
- !mWarningValueMap.contains(propertyName)) {
- mWarningValueMap.add(propertyName);
+ !mUnknownTypeMap.contains(propertyName)) {
+ mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
}
return true;
@@ -194,11 +202,11 @@ public class VCardParser_V21 extends VCardParser {
}
}
}
-
+
/**
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF
- * "END" [ws] ":" [ws] "VCARD"
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
*/
private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
boolean allowGarbage = false;
@@ -219,7 +227,7 @@ public class VCardParser_V21 extends VCardParser {
long start;
if (mBuilder != null) {
start = System.currentTimeMillis();
- mBuilder.startRecord("VCARD");
+ mBuilder.startEntry();
mTimeReadStartRecord += System.currentTimeMillis() - start;
}
start = System.currentTimeMillis();
@@ -228,7 +236,7 @@ public class VCardParser_V21 extends VCardParser {
readEndVCard(true, false);
if (mBuilder != null) {
start = System.currentTimeMillis();
- mBuilder.endRecord();
+ mBuilder.endEntry();
mTimeReadEndRecord += System.currentTimeMillis() - start;
}
return true;
@@ -239,8 +247,7 @@ public class VCardParser_V21 extends VCardParser {
* @throws IOException
* @throws VCardException
*/
- protected boolean readBeginVCard(boolean allowGarbage)
- throws IOException, VCardException {
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
String line;
do {
while (true) {
@@ -255,8 +262,9 @@ public class VCardParser_V21 extends VCardParser {
int length = strArray.length;
// Though vCard 2.1/3.0 specification does not allow lower cases,
- // some data may have them, so we allow it (Actually, previous code
- // had explicitly allowed "BEGIN:vCard" though there's no example).
+ // vCard file emitted by some external vCard expoter have such invalid Strings.
+ // So we allow it.
+ // e.g. BEGIN:vCard
if (length == 2 &&
strArray[0].trim().equalsIgnoreCase("BEGIN") &&
strArray[1].trim().equalsIgnoreCase("VCARD")) {
@@ -279,7 +287,7 @@ public class VCardParser_V21 extends VCardParser {
/**
* The arguments useCache and allowGarbase are usually true and false accordingly when
* this function is called outside this function itself.
- *
+ *
* @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
* is used.
* @param allowGarbage When true, ignore non "END:VCARD" line.
@@ -322,7 +330,6 @@ public class VCardParser_V21 extends VCardParser {
* / item
*/
protected void parseItems() throws IOException, VCardException {
- /* items *CRLF item / item */
boolean ended = false;
if (mBuilder != null) {
@@ -363,12 +370,12 @@ public class VCardParser_V21 extends VCardParser {
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
+ * / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
- String line = getNonEmptyLine();
+ final String line = getNonEmptyLine();
long start = System.currentTimeMillis();
String[] propertyNameAndValue = separateLineAndHandleGroup(line);
@@ -399,9 +406,10 @@ public class VCardParser_V21 extends VCardParser {
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
- } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersion())) {
+ } else if (propertyName.equals("VERSION") &&
+ !propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
+ propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
@@ -409,23 +417,22 @@ public class VCardParser_V21 extends VCardParser {
return false;
}
- throw new VCardException("Unknown property name: \"" +
- propertyName + "\"");
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
}
static private final int STATE_GROUP_OR_PROPNAME = 0;
static private final int STATE_PARAMS = 1;
- // vCard 3.1 specification allows double-quoted param-value, while vCard 2.1 does not.
+ // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
// This is just for safety.
static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
+
protected String[] separateLineAndHandleGroup(String line) throws VCardException {
- int length = line.length();
int state = STATE_GROUP_OR_PROPNAME;
int nameIndex = 0;
- String[] propertyNameAndValue = new String[2];
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
if (length > 0 && line.charAt(0) == '#') {
throw new VCardInvalidCommentLineException();
}
@@ -433,87 +440,89 @@ public class VCardParser_V21 extends VCardParser {
for (int i = 0; i < length; i++) {
char ch = line.charAt(i);
switch (state) {
- case STATE_GROUP_OR_PROPNAME:
- if (ch == ':') {
- String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- } else if (ch == '.') {
- String groupName = line.substring(nameIndex, i);
- if (mBuilder != null) {
- mBuilder.propertyGroup(groupName);
- }
- nameIndex = i + 1;
- } else if (ch == ';') {
- String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
+ case STATE_GROUP_OR_PROPNAME: {
+ if (ch == ':') {
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mBuilder != null) {
+ mBuilder.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') {
+ String groupName = line.substring(nameIndex, i);
+ if (mBuilder != null) {
+ mBuilder.propertyGroup(groupName);
+ }
+ nameIndex = i + 1;
+ } else if (ch == ';') {
+ String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ if (mBuilder != null) {
+ mBuilder.propertyName(propertyName);
+ }
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS;
}
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- nameIndex = i + 1;
- state = STATE_PARAMS;
+ break;
}
- break;
- case STATE_PARAMS:
- if (ch == '"') {
- state = STATE_PARAMS_IN_DQUOTE;
- } else if (ch == ';') {
- handleParams(line.substring(nameIndex, i));
- nameIndex = i + 1;
- } else if (ch == ':') {
- handleParams(line.substring(nameIndex, i));
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') {
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') {
+ handleParams(line.substring(nameIndex, i));
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
}
- return propertyNameAndValue;
+ break;
}
- break;
- case STATE_PARAMS_IN_DQUOTE:
- if (ch == '"') {
- state = STATE_PARAMS;
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ state = STATE_PARAMS;
+ }
+ break;
}
- break;
}
}
throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
}
-
-
+
/**
- * params = ";" [ws] paramlist
- * paramlist = paramlist [ws] ";" [ws] param
- * / param
- * param = "TYPE" [ws] "=" [ws] ptypeval
- * / "VALUE" [ws] "=" [ws] pvalueval
- * / "ENCODING" [ws] "=" [ws] pencodingval
- * / "CHARSET" [ws] "=" [ws] charsetval
- * / "LANGUAGE" [ws] "=" [ws] langval
- * / "X-" word [ws] "=" [ws] word
- * / knowntype
+ * params = ";" [ws] paramlist
+ * paramlist = paramlist [ws] ";" [ws] param
+ * / param
+ * param = "TYPE" [ws] "=" [ws] ptypeval
+ * / "VALUE" [ws] "=" [ws] pvalueval
+ * / "ENCODING" [ws] "=" [ws] pencodingval
+ * / "CHARSET" [ws] "=" [ws] charsetval
+ * / "LANGUAGE" [ws] "=" [ws] langval
+ * / "X-" word [ws] "=" [ws] word
+ * / knowntype
*/
protected void handleParams(String params) throws VCardException {
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
- String paramName = strArray[0].trim();
+ final String paramName = strArray[0].trim().toUpperCase();
String paramValue = strArray[1].trim();
if (paramName.equals("TYPE")) {
handleType(paramValue);
@@ -531,19 +540,27 @@ public class VCardParser_V21 extends VCardParser {
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
- handleType(strArray[0]);
+ handleParamWithoutName(strArray[0]);
}
}
/**
+ * vCard 3.0 parser may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /**
* ptypeval = knowntype / "X-" word
*/
protected void handleType(final String ptypeval) {
String upperTypeValue = ptypeval;
if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
- !mWarningValueMap.contains(ptypeval)) {
- mWarningValueMap.add(ptypeval);
- Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval);
+ !mUnknownTypeMap.contains(ptypeval)) {
+ mUnknownTypeMap.add(ptypeval);
+ Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("TYPE");
@@ -554,15 +571,16 @@ public class VCardParser_V21 extends VCardParser {
/**
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
- protected void handleValue(final String pvalueval) throws VCardException {
- if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
- pvalueval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("VALUE");
- mBuilder.propertyParamValue(pvalueval);
- }
- } else {
- throw new VCardException("Unknown value \"" + pvalueval + "\"");
+ protected void handleValue(final String pvalueval) {
+ if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
+ pvalueval.startsWith("X-") &&
+ !mUnknownValueMap.contains(pvalueval)) {
+ mUnknownValueMap.add(pvalueval);
+ Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
+ }
+ if (mBuilder != null) {
+ mBuilder.propertyParamType("VALUE");
+ mBuilder.propertyParamValue(pvalueval);
}
}
@@ -583,8 +601,8 @@ public class VCardParser_V21 extends VCardParser {
}
/**
- * vCard specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
- * but some vCard contains other charset, so we allow them.
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but today's vCard often contains other charset, so we allow them.
*/
protected void handleCharset(String charsetval) {
if (mBuilder != null) {
@@ -592,7 +610,7 @@ public class VCardParser_V21 extends VCardParser {
mBuilder.propertyParamValue(charsetval);
}
}
-
+
/**
* See also Section 7.1 of RFC 1521
*/
@@ -630,12 +648,12 @@ public class VCardParser_V21 extends VCardParser {
mBuilder.propertyParamValue(paramValue);
}
}
-
- protected void handlePropertyValue(String propertyName, String propertyValue) throws
- IOException, VCardException {
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- long start = System.currentTimeMillis();
- String result = getQuotedPrintable(propertyValue);
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
@@ -644,11 +662,11 @@ public class VCardParser_V21 extends VCardParser {
mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (mEncoding.equalsIgnoreCase("BASE64") ||
mEncoding.equalsIgnoreCase("B")) {
- long start = System.currentTimeMillis();
+ final long start = System.currentTimeMillis();
// It is very rare, but some BASE64 data may be so big that
// OutOfMemoryError occurs. To ignore such cases, use try-catch.
try {
- String result = getBase64(propertyValue);
+ final String result = getBase64(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
@@ -668,7 +686,7 @@ public class VCardParser_V21 extends VCardParser {
Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
}
- long start = System.currentTimeMillis();
+ final long start = System.currentTimeMillis();
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(maybeUnescapeText(propertyValue));
@@ -772,70 +790,46 @@ public class VCardParser_V21 extends VCardParser {
}
if (mBuilder != null) {
- StringBuilder builder = new StringBuilder();
- ArrayList<String> list = new ArrayList<String>();
- int length = propertyValue.length();
- for (int i = 0; i < length; i++) {
- char ch = propertyValue.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char nextCh = propertyValue.charAt(i + 1);
- String unescapedString = maybeUnescapeCharacter(nextCh);
- if (unescapedString != null) {
- builder.append(unescapedString);
- i++;
- } else {
- builder.append(ch);
- }
- } else if (ch == ';') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else {
- builder.append(ch);
- }
- }
- list.add(builder.toString());
- mBuilder.propertyValues(list);
+ mBuilder.propertyValues(VCardUtils.constructListFromValue(
+ propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
-
+
/**
* vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
- *
- * item = ...
- * / [groups "."] "AGENT"
- * [params] ":" vcard CRLF
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF "END" [ws] ":" [ws] "VCARD"
- *
+ *
+ * item = ...
+ * / [groups "."] "AGENT"
+ * [params] ":" vcard CRLF
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF "END" [ws] ":" [ws] "VCARD"
*/
- protected void handleAgent(String propertyValue) throws VCardException {
- throw new VCardNotSupportedException("AGENT Property is not supported now.");
- /* This is insufficient support. Also, AGENT Property is very rare.
- Ignore it for now.
-
- String[] strArray = propertyValue.split(":", 2);
- if (!(strArray.length == 2 ||
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD"))) {
- throw new VCardException("BEGIN:VCARD != \"" + propertyValue + "\"");
+ protected void handleAgent(final String propertyValue) throws VCardException {
+ if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+ // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+ return;
+ } else {
+ throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
}
- parseItems();
- readEndVCard();
- */
+ // TODO: Support AGENT property.
}
/**
* For vCard 3.0.
*/
- protected String maybeUnescapeText(String text) {
+ protected String maybeUnescapeText(final String text) {
return text;
}
-
+
/**
* Returns unescaped String if the character should be unescaped. Return null otherwise.
* e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
*/
- protected String maybeUnescapeCharacter(char ch) {
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(final char ch) {
// Original vCard 2.1 specification does not allow transformation
// "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
// this class allowed them, so keep it as is.
@@ -847,14 +841,17 @@ public class VCardParser_V21 extends VCardParser {
}
@Override
- public boolean parse(InputStream is, VCardBuilder builder)
+ public boolean parse(final InputStream is, final VCardInterpreter builder)
throws IOException, VCardException {
return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
}
@Override
- public boolean parse(InputStream is, String charset, VCardBuilder builder)
+ public boolean parse(InputStream is, String charset, VCardInterpreter builder)
throws IOException, VCardException {
+ if (charset == null) {
+ charset = VCardConfig.DEFAULT_CHARSET;
+ }
final InputStreamReader tmpReader = new InputStreamReader(is, charset);
if (VCardConfig.showPerformanceLog()) {
mReader = new CustomBufferedReader(tmpReader);
@@ -882,7 +879,7 @@ public class VCardParser_V21 extends VCardParser {
}
@Override
- public void parse(InputStream is, String charset, VCardBuilder builder, boolean canceled)
+ public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
throws IOException, VCardException {
mCanceled = canceled;
parse(is, charset, builder);
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 384649a..4ecfe97 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -23,12 +23,12 @@ import java.util.Arrays;
import java.util.HashSet;
/**
- * This class is used to parse vcard3.0. <br>
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
+ * The class used to parse vCard 3.0.
+ * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
*/
public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "vcard.VCardParser_V30";
-
+ private static final String LOG_TAG = "VCardParser_V30";
+
private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
@@ -46,31 +46,64 @@ public class VCardParser_V30 extends VCardParser_V21 {
private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
private String mPreviousLine;
-
+
private boolean mEmittedAgentWarning = false;
-
+
+ /**
+ * True when the caller wants the parser to be strict about the input.
+ * Currently this is only for testing.
+ */
+ private final boolean mStrictParsing;
+
+ public VCardParser_V30() {
+ super();
+ mStrictParsing = false;
+ }
+
+ /**
+ * @param strictParsing when true, this object throws VCardException when the vcard is not
+ * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
+ * is not fully yet for being used with this flag and may not notice invalid line(s).
+ *
+ * @hide currently only for testing!
+ */
+ public VCardParser_V30(boolean strictParsing) {
+ super();
+ mStrictParsing = strictParsing;
+ }
+
+ public VCardParser_V30(int parseMode) {
+ super(parseMode);
+ mStrictParsing = false;
+ }
+
@Override
- protected String getVersion() {
- return Constants.VERSION_V30;
+ protected int getVersion() {
+ return VCardConfig.FLAG_V30;
}
-
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V30;
+ }
+
@Override
protected boolean isValidPropertyName(String propertyName) {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
acceptablePropsWithoutParam.contains(propertyName) ||
propertyName.startsWith("X-")) &&
- !mWarningValueMap.contains(propertyName)) {
- mWarningValueMap.add(propertyName);
+ !mUnknownTypeMap.contains(propertyName)) {
+ mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
}
return true;
}
-
+
@Override
protected boolean isValidEncoding(String encoding) {
return sAcceptableEncodingV30.contains(encoding.toUpperCase());
}
-
+
@Override
protected String getLine() throws IOException {
if (mPreviousLine != null) {
@@ -152,17 +185,17 @@ public class VCardParser_V30 extends VCardParser_V21 {
/**
- * vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF
- * 1*(contentline)
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
* ;A vCard object MUST include the VERSION, FN and N types.
- * [group "."] "END" ":" "VCARD" 1*CRLF
+ * [group "."] "END" ":" "VCARD" 1 * CRLF
*/
@Override
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
return super.readBeginVCard(allowGarbage);
}
-
+
@Override
protected void readEndVCard(boolean useCache, boolean allowGarbage)
throws IOException, VCardException {
@@ -189,17 +222,21 @@ public class VCardParser_V30 extends VCardParser_V21 {
}
}
}
-
+
@Override
protected void handleAnyParam(String paramName, String paramValue) {
- // vCard 3.0 accept comma-separated multiple values, but
- // current PropertyNode does not accept it.
- // For now, we do not split the values.
- //
- // TODO: fix this.
super.handleAnyParam(paramName, paramValue);
}
-
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ if (mStrictParsing) {
+ throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
+ } else {
+ super.handleParamWithoutName(paramValue);
+ }
+ }
+
/**
* vCard 3.0 defines
*
@@ -224,7 +261,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
@Override
protected void handleAgent(String propertyValue) {
- // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.0.
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
//
// e.g.
// AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
@@ -239,13 +276,13 @@ public class VCardParser_V30 extends VCardParser_V21 {
// AGENT;VALUE=uri:
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
//
- // This is not VCARD. Should we support this?
- // throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
+ // This is not vCard. Should we support this?
+ //
+ // Just ignore the line for now, since we cannot know how to handle it...
if (!mEmittedAgentWarning) {
Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
mEmittedAgentWarning = true;
}
- // Just ignore the line for now, since we cannot know how to handle it...
}
/**
@@ -256,7 +293,7 @@ public class VCardParser_V30 extends VCardParser_V21 {
protected String getBase64(String firstString) throws IOException, VCardException {
StringBuilder builder = new StringBuilder();
builder.append(firstString);
-
+
while (true) {
String line = getLine();
if (line == null) {
@@ -280,10 +317,14 @@ public class VCardParser_V30 extends VCardParser_V21 {
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
*
- * Note: Apple escape ':' into '\:' while does not escape '\'
- */
+ * Note: Apple escapes ':' into '\:' while does not escape '\'
+ */
@Override
protected String maybeUnescapeText(String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(String text) {
StringBuilder builder = new StringBuilder();
int length = text.length();
for (int i = 0; i < length; i++) {
@@ -299,15 +340,19 @@ public class VCardParser_V30 extends VCardParser_V21 {
builder.append(ch);
}
}
- return builder.toString();
+ return builder.toString();
}
-
+
@Override
protected String maybeUnescapeCharacter(char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
- }
+ }
}
}
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7e2be2b..7297c50 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -25,15 +25,7 @@ import java.util.Set;
* Currently this implementation is very premature.
* @hide
*/
-public class VCardSourceDetector implements VCardBuilder {
- // Should only be used in package.
- static final int TYPE_UNKNOWN = 0;
- static final int TYPE_APPLE = 1;
- static final int TYPE_JAPANESE_MOBILE_PHONE = 2; // Used in Japanese mobile phones.
- static final int TYPE_FOMA = 3; // Used in some Japanese FOMA mobile phones.
- static final int TYPE_WINDOWS_MOBILE_JP = 4;
- // TODO: Excel, etc.
-
+public class VCardSourceDetector implements VCardInterpreter {
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID"));
@@ -51,7 +43,7 @@ public class VCardSourceDetector implements VCardBuilder {
"X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
- private int mType = TYPE_UNKNOWN;
+ private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
// Some mobile phones (like FOMA) tells us the charset of the data.
private boolean mNeedParseSpecifiedCharset;
private String mSpecifiedCharset;
@@ -62,7 +54,7 @@ public class VCardSourceDetector implements VCardBuilder {
public void end() {
}
- public void startRecord(String type) {
+ public void startEntry() {
}
public void startProperty() {
@@ -72,7 +64,7 @@ public class VCardSourceDetector implements VCardBuilder {
public void endProperty() {
}
- public void endRecord() {
+ public void endEntry() {
}
public void propertyGroup(String group) {
@@ -80,21 +72,21 @@ public class VCardSourceDetector implements VCardBuilder {
public void propertyName(String name) {
if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
- mType = TYPE_FOMA;
+ mType = VCardConfig.PARSE_TYPE_FOMA;
mNeedParseSpecifiedCharset = true;
return;
}
- if (mType != TYPE_UNKNOWN) {
+ if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = TYPE_WINDOWS_MOBILE_JP;
+ mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
} else if (FOMA_SIGNS.contains(name)) {
- mType = TYPE_FOMA;
+ mType = VCardConfig.PARSE_TYPE_FOMA;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = TYPE_JAPANESE_MOBILE_PHONE;
+ mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) {
- mType = TYPE_APPLE;
+ mType = VCardConfig.PARSE_TYPE_APPLE;
}
}
@@ -110,7 +102,7 @@ public class VCardSourceDetector implements VCardBuilder {
}
}
- int getType() {
+ /* package */ int getEstimatedType() {
return mType;
}
@@ -124,14 +116,14 @@ public class VCardSourceDetector implements VCardBuilder {
return mSpecifiedCharset;
}
switch (mType) {
- case TYPE_WINDOWS_MOBILE_JP:
- case TYPE_FOMA:
- case TYPE_JAPANESE_MOBILE_PHONE:
- return "SHIFT_JIS";
- case TYPE_APPLE:
- return "UTF-8";
- default:
- return null;
+ case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
+ case VCardConfig.PARSE_TYPE_FOMA:
+ case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+ return "SHIFT_JIS";
+ case VCardConfig.PARSE_TYPE_APPLE:
+ return "UTF-8";
+ default:
+ return null;
}
}
}
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index dd44288..11b112b 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -16,16 +16,19 @@
package android.pim.vcard;
import android.content.ContentProviderOperation;
-import android.content.ContentValues;
import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -33,58 +36,81 @@ import java.util.Set;
* Utilities for VCard handling codes.
*/
public class VCardUtils {
- /*
- * TODO: some of methods in this class should be placed to the more appropriate place...
- */
-
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
- // converted to two attribute Strings. These only contain some minor fields valid in both
+ // converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
private static final Map<Integer, String> sKnownPhoneTypesMap_ItoS;
- private static final Set<String> sPhoneTypesSetUnknownToContacts;
-
- private static final Map<String, Integer> sKnownPhoneTypesMap_StoI;
-
+ private static final Set<String> sPhoneTypesUnknownToContactsSet;
+ private static final Map<String, Integer> sKnownPhoneTypeMap_StoI;
+ private static final Map<Integer, String> sKnownImPropNameMap_ItoS;
+ private static final Set<String> sMobilePhoneLabelSet;
+
static {
sKnownPhoneTypesMap_ItoS = new HashMap<Integer, String>();
- sKnownPhoneTypesMap_StoI = new HashMap<String, Integer>();
-
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, Constants.ATTR_TYPE_CAR);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CAR, Phone.TYPE_CAR);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, Constants.ATTR_TYPE_PAGER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PAGER, Phone.TYPE_PAGER);
- sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, Constants.ATTR_TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_ISDN, Phone.TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI = new HashMap<String, Integer>();
+
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_CAR, VCardConstants.PARAM_TYPE_CAR);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CAR, Phone.TYPE_CAR);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_PAGER, VCardConstants.PARAM_TYPE_PAGER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_PAGER, Phone.TYPE_PAGER);
+ sKnownPhoneTypesMap_ItoS.put(Phone.TYPE_ISDN, VCardConstants.PARAM_TYPE_ISDN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_ISDN, Phone.TYPE_ISDN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_HOME, Phone.TYPE_HOME);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_WORK, Phone.TYPE_WORK);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_CELL, Phone.TYPE_MOBILE);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_HOME, Phone.TYPE_HOME);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_WORK, Phone.TYPE_WORK);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_TYPE_CELL, Phone.TYPE_MOBILE);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_OTHER, Phone.TYPE_OTHER);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_CALLBACK, Phone.TYPE_CALLBACK);
- sKnownPhoneTypesMap_StoI.put(
- Constants.ATTR_TYPE_PHONE_EXTRA_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_RADIO, Phone.TYPE_RADIO);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TELEX, Phone.TYPE_TELEX);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_TTY_TDD, Phone.TYPE_TTY_TDD);
- sKnownPhoneTypesMap_StoI.put(Constants.ATTR_TYPE_PHONE_EXTRA_ASSISTANT, Phone.TYPE_ASSISTANT);
-
- sPhoneTypesSetUnknownToContacts = new HashSet<String>();
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MODEM);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_MSG);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_BBS);
- sPhoneTypesSetUnknownToContacts.add(Constants.ATTR_TYPE_VIDEO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_OTHER, Phone.TYPE_OTHER);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_CALLBACK,
+ Phone.TYPE_CALLBACK);
+ sKnownPhoneTypeMap_StoI.put(
+ VCardConstants.PARAM_PHONE_EXTRA_TYPE_COMPANY_MAIN, Phone.TYPE_COMPANY_MAIN);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_RADIO, Phone.TYPE_RADIO);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_TTY_TDD,
+ Phone.TYPE_TTY_TDD);
+ sKnownPhoneTypeMap_StoI.put(VCardConstants.PARAM_PHONE_EXTRA_TYPE_ASSISTANT,
+ Phone.TYPE_ASSISTANT);
+
+ sPhoneTypesUnknownToContactsSet = new HashSet<String>();
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MODEM);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_MSG);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_BBS);
+ sPhoneTypesUnknownToContactsSet.add(VCardConstants.PARAM_TYPE_VIDEO);
+
+ sKnownImPropNameMap_ItoS = new HashMap<Integer, String>();
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_GOOGLE_TALK,
+ VCardConstants.PROPERTY_X_GOOGLE_TALK);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_QQ, VCardConstants.PROPERTY_X_QQ);
+ sKnownImPropNameMap_ItoS.put(Im.PROTOCOL_NETMEETING, VCardConstants.PROPERTY_X_NETMEETING);
+
+ // \u643A\u5E2F\u96FB\u8A71 = Full-width Hiragana "Keitai-Denwa" (mobile phone)
+ // \u643A\u5E2F = Full-width Hiragana "Keitai" (mobile phone)
+ // \u30B1\u30A4\u30BF\u30A4 = Full-width Katakana "Keitai" (mobile phone)
+ // \uFF79\uFF72\uFF80\uFF72 = Half-width Katakana "Keitai" (mobile phone)
+ sMobilePhoneLabelSet = new HashSet<String>(Arrays.asList(
+ "MOBILE", "\u643A\u5E2F\u96FB\u8A71", "\u643A\u5E2F", "\u30B1\u30A4\u30BF\u30A4",
+ "\uFF79\uFF72\uFF80\uFF72"));
}
-
- public static String getPhoneAttributeString(Integer type) {
+
+ public static String getPhoneTypeString(Integer type) {
return sKnownPhoneTypesMap_ItoS.get(type);
}
-
+
/**
* Returns Interger when the given types can be parsed as known type. Returns String object
* when not, which should be set to label.
*/
- public static Object getPhoneTypeFromStrings(Collection<String> types) {
+ public static Object getPhoneTypeFromStrings(Collection<String> types,
+ String number) {
+ if (number == null) {
+ number = "";
+ }
int type = -1;
String label = null;
boolean isFax = false;
@@ -92,18 +118,37 @@ public class VCardUtils {
if (types != null) {
for (String typeString : types) {
- typeString = typeString.toUpperCase();
- if (typeString.equals(Constants.ATTR_TYPE_PREF)) {
+ if (typeString == null) {
+ continue;
+ }
+ typeString = typeString.toUpperCase();
+ if (typeString.equals(VCardConstants.PARAM_TYPE_PREF)) {
hasPref = true;
- } else if (typeString.equals(Constants.ATTR_TYPE_FAX)) {
+ } else if (typeString.equals(VCardConstants.PARAM_TYPE_FAX)) {
isFax = true;
} else {
if (typeString.startsWith("X-") && type < 0) {
typeString = typeString.substring(2);
}
- Integer tmp = sKnownPhoneTypesMap_StoI.get(typeString);
+ if (typeString.length() == 0) {
+ continue;
+ }
+ final Integer tmp = sKnownPhoneTypeMap_StoI.get(typeString);
if (tmp != null) {
- type = tmp;
+ final int typeCandidate = tmp;
+ // TYPE_PAGER is prefered when the number contains @ surronded by
+ // a pager number and a domain name.
+ // e.g.
+ // o 1111@domain.com
+ // x @domain.com
+ // x 1111@
+ final int indexOfAt = number.indexOf("@");
+ if ((typeCandidate == Phone.TYPE_PAGER
+ && 0 < indexOfAt && indexOfAt < number.length() - 1)
+ || type < 0
+ || type == Phone.TYPE_CUSTOM) {
+ type = tmp;
+ }
} else if (type < 0) {
type = Phone.TYPE_CUSTOM;
label = typeString;
@@ -134,37 +179,54 @@ public class VCardUtils {
return type;
}
}
-
- public static boolean isValidPhoneAttribute(String phoneAttribute, int vcardType) {
- // TODO: check the following.
- // - it may violate vCard spec
- // - it may contain non-ASCII characters
- //
- // TODO: use vcardType
- return (phoneAttribute.startsWith("X-") || phoneAttribute.startsWith("x-") ||
- sPhoneTypesSetUnknownToContacts.contains(phoneAttribute));
+
+ @SuppressWarnings("deprecation")
+ public static boolean isMobilePhoneLabel(final String label) {
+ // For backward compatibility.
+ // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
+ // To support mobile type at that time, this custom label had been used.
+ return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
+ || sMobilePhoneLabelSet.contains(label));
+ }
+
+ public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
+ return sPhoneTypesUnknownToContactsSet.contains(label);
+ }
+
+ public static String getPropertyNameForIm(final int protocol) {
+ return sKnownImPropNameMap_ItoS.get(protocol);
}
-
- public static String[] sortNameElements(int vcardType,
- String familyName, String middleName, String givenName) {
- String[] list = new String[3];
- switch (VCardConfig.getNameOrderType(vcardType)) {
- case VCardConfig.NAME_ORDER_JAPANESE:
- // TODO: Should handle Ascii case?
- list[0] = familyName;
- list[1] = middleName;
- list[2] = givenName;
- break;
- case VCardConfig.NAME_ORDER_EUROPE:
- list[0] = middleName;
- list[1] = givenName;
- list[2] = familyName;
- break;
- default:
- list[0] = givenName;
- list[1] = middleName;
- list[2] = familyName;
- break;
+
+ public static String[] sortNameElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ final String[] list = new String[3];
+ final int nameOrderType = VCardConfig.getNameOrderType(vcardType);
+ switch (nameOrderType) {
+ case VCardConfig.NAME_ORDER_JAPANESE: {
+ if (containsOnlyPrintableAscii(familyName) &&
+ containsOnlyPrintableAscii(givenName)) {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ } else {
+ list[0] = familyName;
+ list[1] = middleName;
+ list[2] = givenName;
+ }
+ break;
+ }
+ case VCardConfig.NAME_ORDER_EUROPE: {
+ list[0] = middleName;
+ list[1] = givenName;
+ list[2] = familyName;
+ break;
+ }
+ default: {
+ list[0] = givenName;
+ list[1] = middleName;
+ list[2] = familyName;
+ break;
+ }
}
return list;
}
@@ -181,12 +243,11 @@ public class VCardUtils {
* Inserts postal data into the builder object.
*
* Note that the data structure of ContactsContract is different from that defined in vCard.
- * So some conversion may be performed in this method. See also
- * {{@link #getVCardPostalElements(ContentValues)}
+ * So some conversion may be performed in this method.
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
- final ContactStruct.PostalData postalData) {
+ final VCardEntry.PostalData postalData) {
builder.withValueBackReference(StructuredPostal.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
@@ -195,9 +256,22 @@ public class VCardUtils {
builder.withValue(StructuredPostal.LABEL, postalData.label);
}
+ final String streetString;
+ if (TextUtils.isEmpty(postalData.street)) {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = null;
+ } else {
+ streetString = postalData.extendedAddress;
+ }
+ } else {
+ if (TextUtils.isEmpty(postalData.extendedAddress)) {
+ streetString = postalData.street;
+ } else {
+ streetString = postalData.street + " " + postalData.extendedAddress;
+ }
+ }
builder.withValue(StructuredPostal.POBOX, postalData.pobox);
- // Extended address is dropped since there's no relevant entry in ContactsContract.
- builder.withValue(StructuredPostal.STREET, postalData.street);
+ builder.withValue(StructuredPostal.STREET, streetString);
builder.withValue(StructuredPostal.CITY, postalData.localty);
builder.withValue(StructuredPostal.REGION, postalData.region);
builder.withValue(StructuredPostal.POSTCODE, postalData.postalCode);
@@ -209,71 +283,24 @@ public class VCardUtils {
builder.withValue(Data.IS_PRIMARY, 1);
}
}
-
- /**
- * Returns String[] containing address information based on vCard spec
- * (PO Box, Extended Address, Street, Locality, Region, Postal Code, Country Name).
- * All String objects are non-null ("" is used when the relevant data is empty).
- *
- * Note that the data structure of ContactsContract is different from that defined in vCard.
- * So some conversion may be performed in this method. See also
- * {{@link #insertStructuredPostalDataUsingContactsStruct(int,
- * android.content.ContentProviderOperation.Builder,
- * android.pim.vcard.ContactStruct.PostalData)}
- */
- public static String[] getVCardPostalElements(ContentValues contentValues) {
- String[] dataArray = new String[7];
- dataArray[0] = contentValues.getAsString(StructuredPostal.POBOX);
- if (dataArray[0] == null) {
- dataArray[0] = "";
- }
- // Extended addr. There's no relevant data in ContactsContract.
- dataArray[1] = "";
- dataArray[2] = contentValues.getAsString(StructuredPostal.STREET);
- if (dataArray[2] == null) {
- dataArray[2] = "";
- }
- // Assume that localty == city
- dataArray[3] = contentValues.getAsString(StructuredPostal.CITY);
- if (dataArray[3] == null) {
- dataArray[3] = "";
- }
- String region = contentValues.getAsString(StructuredPostal.REGION);
- if (!TextUtils.isEmpty(region)) {
- dataArray[4] = region;
- } else {
- dataArray[4] = "";
- }
- dataArray[5] = contentValues.getAsString(StructuredPostal.POSTCODE);
- if (dataArray[5] == null) {
- dataArray[5] = "";
- }
- dataArray[6] = contentValues.getAsString(StructuredPostal.COUNTRY);
- if (dataArray[6] == null) {
- dataArray[6] = "";
- }
- return dataArray;
- }
-
- public static String constructNameFromElements(int nameOrderType,
- String familyName, String middleName, String givenName) {
- return constructNameFromElements(nameOrderType, familyName, middleName, givenName,
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName) {
+ return constructNameFromElements(vcardType, familyName, middleName, givenName,
null, null);
}
- public static String constructNameFromElements(int nameOrderType,
- String familyName, String middleName, String givenName,
- String prefix, String suffix) {
- StringBuilder builder = new StringBuilder();
- String[] nameList = sortNameElements(nameOrderType,
- familyName, middleName, givenName);
+ public static String constructNameFromElements(final int vcardType,
+ final String familyName, final String middleName, final String givenName,
+ final String prefix, final String suffix) {
+ final StringBuilder builder = new StringBuilder();
+ final String[] nameList = sortNameElements(vcardType, familyName, middleName, givenName);
boolean first = true;
if (!TextUtils.isEmpty(prefix)) {
first = false;
builder.append(prefix);
}
- for (String namePart : nameList) {
+ for (final String namePart : nameList) {
if (!TextUtils.isEmpty(namePart)) {
if (first) {
first = false;
@@ -291,18 +318,52 @@ public class VCardUtils {
}
return builder.toString();
}
-
- public static boolean containsOnlyPrintableAscii(String str) {
- if (TextUtils.isEmpty(str)) {
+
+ public static List<String> constructListFromValue(final String value,
+ final boolean isV30) {
+ final List<String> list = new ArrayList<String>();
+ StringBuilder builder = new StringBuilder();
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ final String unescapedString =
+ (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
+ VCardParser_V21.unescapeCharacter(nextCh));
+ if (unescapedString != null) {
+ builder.append(unescapedString);
+ i++;
+ } else {
+ builder.append(ch);
+ }
+ } else if (ch == ';') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else {
+ builder.append(ch);
+ }
+ }
+ list.add(builder.toString());
+ return list;
+ }
+
+ public static boolean containsOnlyPrintableAscii(final String...values) {
+ if (values == null) {
return true;
}
-
- final int length = str.length();
- final int asciiFirst = 0x20;
- final int asciiLast = 0x126;
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int c = str.codePointAt(i);
- if (c < asciiFirst || asciiLast < c) {
+ return containsOnlyPrintableAscii(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ if (!TextUtils.isPrintableAsciiOnly(value)) {
return false;
}
}
@@ -314,23 +375,37 @@ public class VCardUtils {
* or not, which is required by vCard 2.1.
* See the definition of "7bit" in vCard 2.1 spec for more information.
*/
- public static boolean containsOnlyNonCrLfPrintableAscii(String str) {
- if (TextUtils.isEmpty(str)) {
+ public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
+ if (values == null) {
return true;
}
+ return containsOnlyNonCrLfPrintableAscii(Arrays.asList(values));
+ }
- final int length = str.length();
+ public static boolean containsOnlyNonCrLfPrintableAscii(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
final int asciiFirst = 0x20;
- final int asciiLast = 0x126;
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int c = str.codePointAt(i);
- if (c < asciiFirst || asciiLast < c || c == '\n' || c == '\r') {
- return false;
+ final int asciiLast = 0x7E; // included
+ for (final String value : values) {
+ if (TextUtils.isEmpty(value)) {
+ continue;
+ }
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast)) {
+ return false;
+ }
}
}
return true;
}
+ private static final Set<Character> sUnAcceptableAsciiInV21WordSet =
+ new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
+
/**
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
@@ -340,86 +415,79 @@ public class VCardUtils {
* such kind of input but must never output it unless the target is very specific
* to the device which is able to parse the malformed input.
*/
- public static boolean containsOnlyAlphaDigitHyphen(String str) {
- if (TextUtils.isEmpty(str)) {
+ public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
+ if (values == null) {
return true;
}
+ return containsOnlyAlphaDigitHyphen(Arrays.asList(values));
+ }
- final int lowerAlphabetFirst = 0x41; // included ('A')
- final int lowerAlphabetLast = 0x5b; // not included ('[')
- final int upperAlphabetFirst = 0x61; // included ('a')
- final int upperAlphabetLast = 0x7b; // included ('{')
- final int digitFirst = 0x30; // included ('0')
- final int digitLast = 0x39; // included ('9')
+ public static boolean containsOnlyAlphaDigitHyphen(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ final int upperAlphabetFirst = 0x41; // A
+ final int upperAlphabetAfterLast = 0x5b; // [
+ final int lowerAlphabetFirst = 0x61; // a
+ final int lowerAlphabetAfterLast = 0x7b; // {
+ final int digitFirst = 0x30; // 0
+ final int digitAfterLast = 0x3A; // :
final int hyphen = '-';
- final int length = str.length();
- for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
- int codepoint = str.codePointAt(i);
- if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetLast) ||
- (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetLast) ||
- (digitFirst <= codepoint && codepoint < digitLast) ||
+ for (final String str : values) {
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ final int length = str.length();
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ int codepoint = str.codePointAt(i);
+ if (!((lowerAlphabetFirst <= codepoint && codepoint < lowerAlphabetAfterLast) ||
+ (upperAlphabetFirst <= codepoint && codepoint < upperAlphabetAfterLast) ||
+ (digitFirst <= codepoint && codepoint < digitAfterLast) ||
(codepoint == hyphen))) {
- return false;
+ return false;
+ }
}
}
return true;
}
-
- // TODO: Replace wth the method in Base64 class.
- private static char PAD = '=';
- private static final char[] ENCODE64 = {
- 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
- 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
- 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
- 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
- };
-
- static public String encodeBase64(byte[] data) {
- if (data == null) {
- return "";
- }
- char[] charBuffer = new char[(data.length + 2) / 3 * 4];
- int position = 0;
- int _3byte = 0;
- for (int i=0; i<data.length-2; i+=3) {
- _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = ENCODE64[_3byte & 0x3F];
+ /**
+ * <P>
+ * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
+ * </P>
+ * <P>
+ * vCard 2.1 specifies:<BR />
+ * word = &lt;any printable 7bit us-ascii except []=:., &gt;
+ * </P>
+ */
+ public static boolean isV21Word(final String value) {
+ if (TextUtils.isEmpty(value)) {
+ return true;
}
- switch(data.length % 3) {
- case 1: // [111111][11 0000][0000 00][000000]
- _3byte = ((data[data.length-1] & 0xFF) << 16);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = PAD;
- charBuffer[position++] = PAD;
- break;
- case 2: // [111111][11 1111][1111 00][000000]
- _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
- charBuffer[position++] = ENCODE64[_3byte >> 18];
- charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
- charBuffer[position++] = ENCODE64[(_3byte >> 6) & 0x3F];
- charBuffer[position++] = PAD;
- break;
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ final int length = value.length();
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int c = value.codePointAt(i);
+ if (!(asciiFirst <= c && c <= asciiLast) ||
+ sUnAcceptableAsciiInV21WordSet.contains((char)c)) {
+ return false;
+ }
}
-
- return new String(charBuffer);
+ return true;
}
-
- static public String toHalfWidthString(String orgString) {
+
+ public static String toHalfWidthString(final String orgString) {
if (TextUtils.isEmpty(orgString)) {
return null;
}
- StringBuilder builder = new StringBuilder();
- int length = orgString.length();
- for (int i = 0; i < length; i++) {
+ final StringBuilder builder = new StringBuilder();
+ final int length = orgString.length();
+ for (int i = 0; i < length; i = orgString.offsetByCodePoints(i, 1)) {
// All Japanese character is able to be expressed by char.
// Do not need to use String#codepPointAt().
- char ch = orgString.charAt(i);
- CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
+ final char ch = orgString.charAt(i);
+ final String halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
if (halfWidthText != null) {
builder.append(halfWidthText);
} else {
@@ -428,368 +496,50 @@ public class VCardUtils {
}
return builder.toString();
}
-
- private VCardUtils() {
- }
-}
-
-/**
- * TextUtils especially for Japanese.
- * TODO: make this in android.text in the future
- */
-class JapaneseUtils {
- static private final Map<Character, String> sHalfWidthMap =
- new HashMap<Character, String>();
-
- static {
- // There's no logical mapping rule in Unicode. Sigh.
- sHalfWidthMap.put('\u3001', "\uFF64");
- sHalfWidthMap.put('\u3002', "\uFF61");
- sHalfWidthMap.put('\u300C', "\uFF62");
- sHalfWidthMap.put('\u300D', "\uFF63");
- sHalfWidthMap.put('\u301C', "~");
- sHalfWidthMap.put('\u3041', "\uFF67");
- sHalfWidthMap.put('\u3042', "\uFF71");
- sHalfWidthMap.put('\u3043', "\uFF68");
- sHalfWidthMap.put('\u3044', "\uFF72");
- sHalfWidthMap.put('\u3045', "\uFF69");
- sHalfWidthMap.put('\u3046', "\uFF73");
- sHalfWidthMap.put('\u3047', "\uFF6A");
- sHalfWidthMap.put('\u3048', "\uFF74");
- sHalfWidthMap.put('\u3049', "\uFF6B");
- sHalfWidthMap.put('\u304A', "\uFF75");
- sHalfWidthMap.put('\u304B', "\uFF76");
- sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u304D', "\uFF77");
- sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u304F', "\uFF78");
- sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u3051', "\uFF79");
- sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u3053', "\uFF7A");
- sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u3055', "\uFF7B");
- sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u3057', "\uFF7C");
- sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u3059', "\uFF7D");
- sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u305B', "\uFF7E");
- sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u305D', "\uFF7F");
- sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u305F', "\uFF80");
- sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u3061', "\uFF81");
- sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u3063', "\uFF6F");
- sHalfWidthMap.put('\u3064', "\uFF82");
- sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u3066', "\uFF83");
- sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u3068', "\uFF84");
- sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u306A', "\uFF85");
- sHalfWidthMap.put('\u306B', "\uFF86");
- sHalfWidthMap.put('\u306C', "\uFF87");
- sHalfWidthMap.put('\u306D', "\uFF88");
- sHalfWidthMap.put('\u306E', "\uFF89");
- sHalfWidthMap.put('\u306F', "\uFF8A");
- sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u3072', "\uFF8B");
- sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u3075', "\uFF8C");
- sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u3078', "\uFF8D");
- sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u307B', "\uFF8E");
- sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u307E', "\uFF8F");
- sHalfWidthMap.put('\u307F', "\uFF90");
- sHalfWidthMap.put('\u3080', "\uFF91");
- sHalfWidthMap.put('\u3081', "\uFF92");
- sHalfWidthMap.put('\u3082', "\uFF93");
- sHalfWidthMap.put('\u3083', "\uFF6C");
- sHalfWidthMap.put('\u3084', "\uFF94");
- sHalfWidthMap.put('\u3085', "\uFF6D");
- sHalfWidthMap.put('\u3086', "\uFF95");
- sHalfWidthMap.put('\u3087', "\uFF6E");
- sHalfWidthMap.put('\u3088', "\uFF96");
- sHalfWidthMap.put('\u3089', "\uFF97");
- sHalfWidthMap.put('\u308A', "\uFF98");
- sHalfWidthMap.put('\u308B', "\uFF99");
- sHalfWidthMap.put('\u308C', "\uFF9A");
- sHalfWidthMap.put('\u308D', "\uFF9B");
- sHalfWidthMap.put('\u308E', "\uFF9C");
- sHalfWidthMap.put('\u308F', "\uFF9C");
- sHalfWidthMap.put('\u3090', "\uFF72");
- sHalfWidthMap.put('\u3091', "\uFF74");
- sHalfWidthMap.put('\u3092', "\uFF66");
- sHalfWidthMap.put('\u3093', "\uFF9D");
- sHalfWidthMap.put('\u309B', "\uFF9E");
- sHalfWidthMap.put('\u309C', "\uFF9F");
- sHalfWidthMap.put('\u30A1', "\uFF67");
- sHalfWidthMap.put('\u30A2', "\uFF71");
- sHalfWidthMap.put('\u30A3', "\uFF68");
- sHalfWidthMap.put('\u30A4', "\uFF72");
- sHalfWidthMap.put('\u30A5', "\uFF69");
- sHalfWidthMap.put('\u30A6', "\uFF73");
- sHalfWidthMap.put('\u30A7', "\uFF6A");
- sHalfWidthMap.put('\u30A8', "\uFF74");
- sHalfWidthMap.put('\u30A9', "\uFF6B");
- sHalfWidthMap.put('\u30AA', "\uFF75");
- sHalfWidthMap.put('\u30AB', "\uFF76");
- sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
- sHalfWidthMap.put('\u30AD', "\uFF77");
- sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
- sHalfWidthMap.put('\u30AF', "\uFF78");
- sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
- sHalfWidthMap.put('\u30B1', "\uFF79");
- sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
- sHalfWidthMap.put('\u30B3', "\uFF7A");
- sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
- sHalfWidthMap.put('\u30B5', "\uFF7B");
- sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
- sHalfWidthMap.put('\u30B7', "\uFF7C");
- sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
- sHalfWidthMap.put('\u30B9', "\uFF7D");
- sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
- sHalfWidthMap.put('\u30BB', "\uFF7E");
- sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
- sHalfWidthMap.put('\u30BD', "\uFF7F");
- sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
- sHalfWidthMap.put('\u30BF', "\uFF80");
- sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
- sHalfWidthMap.put('\u30C1', "\uFF81");
- sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
- sHalfWidthMap.put('\u30C3', "\uFF6F");
- sHalfWidthMap.put('\u30C4', "\uFF82");
- sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
- sHalfWidthMap.put('\u30C6', "\uFF83");
- sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
- sHalfWidthMap.put('\u30C8', "\uFF84");
- sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
- sHalfWidthMap.put('\u30CA', "\uFF85");
- sHalfWidthMap.put('\u30CB', "\uFF86");
- sHalfWidthMap.put('\u30CC', "\uFF87");
- sHalfWidthMap.put('\u30CD', "\uFF88");
- sHalfWidthMap.put('\u30CE', "\uFF89");
- sHalfWidthMap.put('\u30CF', "\uFF8A");
- sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
- sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
- sHalfWidthMap.put('\u30D2', "\uFF8B");
- sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
- sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
- sHalfWidthMap.put('\u30D5', "\uFF8C");
- sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
- sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
- sHalfWidthMap.put('\u30D8', "\uFF8D");
- sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
- sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
- sHalfWidthMap.put('\u30DB', "\uFF8E");
- sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
- sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
- sHalfWidthMap.put('\u30DE', "\uFF8F");
- sHalfWidthMap.put('\u30DF', "\uFF90");
- sHalfWidthMap.put('\u30E0', "\uFF91");
- sHalfWidthMap.put('\u30E1', "\uFF92");
- sHalfWidthMap.put('\u30E2', "\uFF93");
- sHalfWidthMap.put('\u30E3', "\uFF6C");
- sHalfWidthMap.put('\u30E4', "\uFF94");
- sHalfWidthMap.put('\u30E5', "\uFF6D");
- sHalfWidthMap.put('\u30E6', "\uFF95");
- sHalfWidthMap.put('\u30E7', "\uFF6E");
- sHalfWidthMap.put('\u30E8', "\uFF96");
- sHalfWidthMap.put('\u30E9', "\uFF97");
- sHalfWidthMap.put('\u30EA', "\uFF98");
- sHalfWidthMap.put('\u30EB', "\uFF99");
- sHalfWidthMap.put('\u30EC', "\uFF9A");
- sHalfWidthMap.put('\u30ED', "\uFF9B");
- sHalfWidthMap.put('\u30EE', "\uFF9C");
- sHalfWidthMap.put('\u30EF', "\uFF9C");
- sHalfWidthMap.put('\u30F0', "\uFF72");
- sHalfWidthMap.put('\u30F1', "\uFF74");
- sHalfWidthMap.put('\u30F2', "\uFF66");
- sHalfWidthMap.put('\u30F3', "\uFF9D");
- sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
- sHalfWidthMap.put('\u30F5', "\uFF76");
- sHalfWidthMap.put('\u30F6', "\uFF79");
- sHalfWidthMap.put('\u30FB', "\uFF65");
- sHalfWidthMap.put('\u30FC', "\uFF70");
- sHalfWidthMap.put('\uFF01', "!");
- sHalfWidthMap.put('\uFF02', "\"");
- sHalfWidthMap.put('\uFF03', "#");
- sHalfWidthMap.put('\uFF04', "$");
- sHalfWidthMap.put('\uFF05', "%");
- sHalfWidthMap.put('\uFF06', "&");
- sHalfWidthMap.put('\uFF07', "'");
- sHalfWidthMap.put('\uFF08', "(");
- sHalfWidthMap.put('\uFF09', ")");
- sHalfWidthMap.put('\uFF0A', "*");
- sHalfWidthMap.put('\uFF0B', "+");
- sHalfWidthMap.put('\uFF0C', ",");
- sHalfWidthMap.put('\uFF0D', "-");
- sHalfWidthMap.put('\uFF0E', ".");
- sHalfWidthMap.put('\uFF0F', "/");
- sHalfWidthMap.put('\uFF10', "0");
- sHalfWidthMap.put('\uFF11', "1");
- sHalfWidthMap.put('\uFF12', "2");
- sHalfWidthMap.put('\uFF13', "3");
- sHalfWidthMap.put('\uFF14', "4");
- sHalfWidthMap.put('\uFF15', "5");
- sHalfWidthMap.put('\uFF16', "6");
- sHalfWidthMap.put('\uFF17', "7");
- sHalfWidthMap.put('\uFF18', "8");
- sHalfWidthMap.put('\uFF19', "9");
- sHalfWidthMap.put('\uFF1A', ":");
- sHalfWidthMap.put('\uFF1B', ";");
- sHalfWidthMap.put('\uFF1C', "<");
- sHalfWidthMap.put('\uFF1D', "=");
- sHalfWidthMap.put('\uFF1E', ">");
- sHalfWidthMap.put('\uFF1F', "?");
- sHalfWidthMap.put('\uFF20', "@");
- sHalfWidthMap.put('\uFF21', "A");
- sHalfWidthMap.put('\uFF22', "B");
- sHalfWidthMap.put('\uFF23', "C");
- sHalfWidthMap.put('\uFF24', "D");
- sHalfWidthMap.put('\uFF25', "E");
- sHalfWidthMap.put('\uFF26', "F");
- sHalfWidthMap.put('\uFF27', "G");
- sHalfWidthMap.put('\uFF28', "H");
- sHalfWidthMap.put('\uFF29', "I");
- sHalfWidthMap.put('\uFF2A', "J");
- sHalfWidthMap.put('\uFF2B', "K");
- sHalfWidthMap.put('\uFF2C', "L");
- sHalfWidthMap.put('\uFF2D', "M");
- sHalfWidthMap.put('\uFF2E', "N");
- sHalfWidthMap.put('\uFF2F', "O");
- sHalfWidthMap.put('\uFF30', "P");
- sHalfWidthMap.put('\uFF31', "Q");
- sHalfWidthMap.put('\uFF32', "R");
- sHalfWidthMap.put('\uFF33', "S");
- sHalfWidthMap.put('\uFF34', "T");
- sHalfWidthMap.put('\uFF35', "U");
- sHalfWidthMap.put('\uFF36', "V");
- sHalfWidthMap.put('\uFF37', "W");
- sHalfWidthMap.put('\uFF38', "X");
- sHalfWidthMap.put('\uFF39', "Y");
- sHalfWidthMap.put('\uFF3A', "Z");
- sHalfWidthMap.put('\uFF3B', "[");
- sHalfWidthMap.put('\uFF3C', "\\");
- sHalfWidthMap.put('\uFF3D', "]");
- sHalfWidthMap.put('\uFF3E', "^");
- sHalfWidthMap.put('\uFF3F', "_");
- sHalfWidthMap.put('\uFF41', "a");
- sHalfWidthMap.put('\uFF42', "b");
- sHalfWidthMap.put('\uFF43', "c");
- sHalfWidthMap.put('\uFF44', "d");
- sHalfWidthMap.put('\uFF45', "e");
- sHalfWidthMap.put('\uFF46', "f");
- sHalfWidthMap.put('\uFF47', "g");
- sHalfWidthMap.put('\uFF48', "h");
- sHalfWidthMap.put('\uFF49', "i");
- sHalfWidthMap.put('\uFF4A', "j");
- sHalfWidthMap.put('\uFF4B', "k");
- sHalfWidthMap.put('\uFF4C', "l");
- sHalfWidthMap.put('\uFF4D', "m");
- sHalfWidthMap.put('\uFF4E', "n");
- sHalfWidthMap.put('\uFF4F', "o");
- sHalfWidthMap.put('\uFF50', "p");
- sHalfWidthMap.put('\uFF51', "q");
- sHalfWidthMap.put('\uFF52', "r");
- sHalfWidthMap.put('\uFF53', "s");
- sHalfWidthMap.put('\uFF54', "t");
- sHalfWidthMap.put('\uFF55', "u");
- sHalfWidthMap.put('\uFF56', "v");
- sHalfWidthMap.put('\uFF57', "w");
- sHalfWidthMap.put('\uFF58', "x");
- sHalfWidthMap.put('\uFF59', "y");
- sHalfWidthMap.put('\uFF5A', "z");
- sHalfWidthMap.put('\uFF5B', "{");
- sHalfWidthMap.put('\uFF5C', "|");
- sHalfWidthMap.put('\uFF5D', "}");
- sHalfWidthMap.put('\uFF5E', "~");
- sHalfWidthMap.put('\uFF61', "\uFF61");
- sHalfWidthMap.put('\uFF62', "\uFF62");
- sHalfWidthMap.put('\uFF63', "\uFF63");
- sHalfWidthMap.put('\uFF64', "\uFF64");
- sHalfWidthMap.put('\uFF65', "\uFF65");
- sHalfWidthMap.put('\uFF66', "\uFF66");
- sHalfWidthMap.put('\uFF67', "\uFF67");
- sHalfWidthMap.put('\uFF68', "\uFF68");
- sHalfWidthMap.put('\uFF69', "\uFF69");
- sHalfWidthMap.put('\uFF6A', "\uFF6A");
- sHalfWidthMap.put('\uFF6B', "\uFF6B");
- sHalfWidthMap.put('\uFF6C', "\uFF6C");
- sHalfWidthMap.put('\uFF6D', "\uFF6D");
- sHalfWidthMap.put('\uFF6E', "\uFF6E");
- sHalfWidthMap.put('\uFF6F', "\uFF6F");
- sHalfWidthMap.put('\uFF70', "\uFF70");
- sHalfWidthMap.put('\uFF71', "\uFF71");
- sHalfWidthMap.put('\uFF72', "\uFF72");
- sHalfWidthMap.put('\uFF73', "\uFF73");
- sHalfWidthMap.put('\uFF74', "\uFF74");
- sHalfWidthMap.put('\uFF75', "\uFF75");
- sHalfWidthMap.put('\uFF76', "\uFF76");
- sHalfWidthMap.put('\uFF77', "\uFF77");
- sHalfWidthMap.put('\uFF78', "\uFF78");
- sHalfWidthMap.put('\uFF79', "\uFF79");
- sHalfWidthMap.put('\uFF7A', "\uFF7A");
- sHalfWidthMap.put('\uFF7B', "\uFF7B");
- sHalfWidthMap.put('\uFF7C', "\uFF7C");
- sHalfWidthMap.put('\uFF7D', "\uFF7D");
- sHalfWidthMap.put('\uFF7E', "\uFF7E");
- sHalfWidthMap.put('\uFF7F', "\uFF7F");
- sHalfWidthMap.put('\uFF80', "\uFF80");
- sHalfWidthMap.put('\uFF81', "\uFF81");
- sHalfWidthMap.put('\uFF82', "\uFF82");
- sHalfWidthMap.put('\uFF83', "\uFF83");
- sHalfWidthMap.put('\uFF84', "\uFF84");
- sHalfWidthMap.put('\uFF85', "\uFF85");
- sHalfWidthMap.put('\uFF86', "\uFF86");
- sHalfWidthMap.put('\uFF87', "\uFF87");
- sHalfWidthMap.put('\uFF88', "\uFF88");
- sHalfWidthMap.put('\uFF89', "\uFF89");
- sHalfWidthMap.put('\uFF8A', "\uFF8A");
- sHalfWidthMap.put('\uFF8B', "\uFF8B");
- sHalfWidthMap.put('\uFF8C', "\uFF8C");
- sHalfWidthMap.put('\uFF8D', "\uFF8D");
- sHalfWidthMap.put('\uFF8E', "\uFF8E");
- sHalfWidthMap.put('\uFF8F', "\uFF8F");
- sHalfWidthMap.put('\uFF90', "\uFF90");
- sHalfWidthMap.put('\uFF91', "\uFF91");
- sHalfWidthMap.put('\uFF92', "\uFF92");
- sHalfWidthMap.put('\uFF93', "\uFF93");
- sHalfWidthMap.put('\uFF94', "\uFF94");
- sHalfWidthMap.put('\uFF95', "\uFF95");
- sHalfWidthMap.put('\uFF96', "\uFF96");
- sHalfWidthMap.put('\uFF97', "\uFF97");
- sHalfWidthMap.put('\uFF98', "\uFF98");
- sHalfWidthMap.put('\uFF99', "\uFF99");
- sHalfWidthMap.put('\uFF9A', "\uFF9A");
- sHalfWidthMap.put('\uFF9B', "\uFF9B");
- sHalfWidthMap.put('\uFF9C', "\uFF9C");
- sHalfWidthMap.put('\uFF9D', "\uFF9D");
- sHalfWidthMap.put('\uFF9E', "\uFF9E");
- sHalfWidthMap.put('\uFF9F', "\uFF9F");
- sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
- }
/**
- * Return half-width version of that character if possible. Return null if not possible
- * @param ch input character
- * @return CharSequence object if the mapping for ch exists. Return null otherwise.
+ * Guesses the format of input image. Currently just the first few bytes are used.
+ * The type "GIF", "PNG", or "JPEG" is returned when possible. Returns null when
+ * the guess failed.
+ * @param input Image as byte array.
+ * @return The image type or null when the type cannot be determined.
*/
- public static CharSequence tryGetHalfWidthText(char ch) {
- if (sHalfWidthMap.containsKey(ch)) {
- return sHalfWidthMap.get(ch);
+ public static String guessImageType(final byte[] input) {
+ if (input == null) {
+ return null;
+ }
+ if (input.length >= 3 && input[0] == 'G' && input[1] == 'I' && input[2] == 'F') {
+ return "GIF";
+ } else if (input.length >= 4 && input[0] == (byte) 0x89
+ && input[1] == 'P' && input[2] == 'N' && input[3] == 'G') {
+ // Note: vCard 2.1 officially does not support PNG, but we may have it and
+ // using X- word like "X-PNG" may not let importers know it is PNG.
+ // So we use the String "PNG" as is...
+ return "PNG";
+ } else if (input.length >= 2 && input[0] == (byte) 0xff
+ && input[1] == (byte) 0xd8) {
+ return "JPEG";
} else {
return null;
}
}
-} \ No newline at end of file
+
+ /**
+ * @return True when all the given values are null or empty Strings.
+ */
+ public static boolean areAllEmpty(final String...values) {
+ if (values == null) {
+ return true;
+ }
+
+ for (final String value : values) {
+ if (!TextUtils.isEmpty(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private VCardUtils() {
+ }
+}
diff --git a/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
new file mode 100644
index 0000000..e72c7df
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardAgentNotSupportedException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard.exception;
+
+public class VCardAgentNotSupportedException extends VCardNotSupportedException {
+ public VCardAgentNotSupportedException() {
+ super();
+ }
+
+ public VCardAgentNotSupportedException(String message) {
+ super(message);
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
index 84ee950..aa27627 100644
--- a/core/java/android/preference/EditTextPreference.java
+++ b/core/java/android/preference/EditTextPreference.java
@@ -28,7 +28,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.EditText;
-import android.widget.LinearLayout;
/**
* A {@link Preference} that allows for string
@@ -128,7 +127,7 @@ public class EditTextPreference extends DialogPreference {
ViewGroup container = (ViewGroup) dialogView
.findViewById(com.android.internal.R.id.edittext_container);
if (container != null) {
- container.addView(editText, ViewGroup.LayoutParams.FILL_PARENT,
+ container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 08a2a9f..197d976 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -188,17 +188,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Preference);
- if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
- a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
- // This preference has a custom layout defined (not one taken from
- // the default style)
- mHasSpecifiedLayout = true;
- }
- a.recycle();
-
- a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
- defStyle, 0);
+ com.android.internal.R.styleable.Preference, defStyle, 0);
for (int i = a.getIndexCount(); i >= 0; i--) {
int attr = a.getIndex(i);
switch (attr) {
@@ -252,6 +242,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
}
}
a.recycle();
+
+ if (!getClass().getName().startsWith("android.preference")) {
+ // For subclasses not in this package, assume the worst and don't cache views
+ mHasSpecifiedLayout = true;
+ }
}
/**
@@ -332,11 +327,11 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setWidgetLayoutResource(int)
*/
public void setLayoutResource(int layoutResId) {
-
- if (!mHasSpecifiedLayout) {
+ if (layoutResId != mLayoutResId) {
+ // Layout changed
mHasSpecifiedLayout = true;
}
-
+
mLayoutResId = layoutResId;
}
@@ -360,6 +355,10 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
* @see #setLayoutResource(int)
*/
public void setWidgetLayoutResource(int widgetLayoutResId) {
+ if (widgetLayoutResId != mWidgetLayoutResId) {
+ // Layout changed
+ mHasSpecifiedLayout = true;
+ }
mWidgetLayoutResId = widgetLayoutResId;
}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 837ce91..726793d 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -24,7 +24,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
-import android.view.Window;
/**
* Shows a hierarchy of {@link Preference} objects as
@@ -81,6 +80,8 @@ public abstract class PreferenceActivity extends ListActivity implements
private PreferenceManager mPreferenceManager;
+ private Bundle mSavedInstanceState;
+
/**
* The starting request code given out to preference framework.
*/
@@ -137,15 +138,19 @@ public abstract class PreferenceActivity extends ListActivity implements
@Override
protected void onRestoreInstanceState(Bundle state) {
- super.onRestoreInstanceState(state);
-
Bundle container = state.getBundle(PREFERENCES_TAG);
if (container != null) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.restoreHierarchyState(container);
+ mSavedInstanceState = state;
+ return;
}
}
+
+ // Only call this if we didn't save the instance state for later.
+ // If we did save it, it will be restored when we bind the adapter.
+ super.onRestoreInstanceState(state);
}
@Override
@@ -176,6 +181,10 @@ public abstract class PreferenceActivity extends ListActivity implements
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.bind(getListView());
+ if (mSavedInstanceState != null) {
+ super.onRestoreInstanceState(mSavedInstanceState);
+ mSavedInstanceState = null;
+ }
}
}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
index 14c0054..a908ecd 100644
--- a/core/java/android/preference/PreferenceGroupAdapter.java
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -69,7 +69,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
* count once--when the adapter is being set). We will not recycle views for
* Preference subclasses seen after the count has been returned.
*/
- private List<String> mPreferenceClassNames;
+ private ArrayList<PreferenceLayout> mPreferenceLayouts;
+
+ private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
/**
* Blocks the mPreferenceClassNames from being changed anymore.
@@ -86,14 +88,37 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
};
+ private static class PreferenceLayout implements Comparable<PreferenceLayout> {
+ private int resId;
+ private int widgetResId;
+ private String name;
+
+ public int compareTo(PreferenceLayout other) {
+ int compareNames = name.compareTo(other.name);
+ if (compareNames == 0) {
+ if (resId == other.resId) {
+ if (widgetResId == other.widgetResId) {
+ return 0;
+ } else {
+ return widgetResId - other.widgetResId;
+ }
+ } else {
+ return resId - other.resId;
+ }
+ } else {
+ return compareNames;
+ }
+ }
+ }
+
public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
mPreferenceGroup = preferenceGroup;
// If this group gets or loses any children, let us know
mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
-
+
mPreferenceList = new ArrayList<Preference>();
- mPreferenceClassNames = new ArrayList<String>();
-
+ mPreferenceLayouts = new ArrayList<PreferenceLayout>();
+
syncMyPreferences();
}
@@ -102,7 +127,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
if (mIsSyncing) {
return;
}
-
+
mIsSyncing = true;
}
@@ -128,7 +153,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
preferences.add(preference);
- if (!mHasReturnedViewTypeCount) {
+ if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
addPreferenceClassName(preference);
}
@@ -143,15 +168,28 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
}
}
+ /**
+ * Creates a string that includes the preference name, layout id and widget layout id.
+ * If a particular preference type uses 2 different resources, they will be treated as
+ * different view types.
+ */
+ private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
+ PreferenceLayout pl = in != null? in : new PreferenceLayout();
+ pl.name = preference.getClass().getName();
+ pl.resId = preference.getLayoutResource();
+ pl.widgetResId = preference.getWidgetLayoutResource();
+ return pl;
+ }
+
private void addPreferenceClassName(Preference preference) {
- final String name = preference.getClass().getName();
- int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
-
+ final PreferenceLayout pl = createPreferenceLayout(preference, null);
+ int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
+
// Only insert if it doesn't exist (when it is negative).
if (insertPos < 0) {
// Convert to insert index
insertPos = insertPos * -1 - 1;
- mPreferenceClassNames.add(insertPos, name);
+ mPreferenceLayouts.add(insertPos, pl);
}
}
@@ -171,19 +209,15 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
public View getView(int position, View convertView, ViewGroup parent) {
final Preference preference = this.getItem(position);
-
- if (preference.hasSpecifiedLayout()) {
- // If the preference had specified a layout (as opposed to the
- // default), don't use convert views.
+ // Build a PreferenceLayout to compare with known ones that are cacheable.
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ // If it's not one of the cached ones, set the convertView to null so that
+ // the layout gets re-created by the Preference.
+ if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
convertView = null;
- } else {
- // TODO: better way of doing this
- final String name = preference.getClass().getName();
- if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
- convertView = null;
- }
}
-
+
return preference.getView(convertView, parent);
}
@@ -225,8 +259,9 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
return IGNORE_ITEM_VIEW_TYPE;
}
- final String name = preference.getClass().getName();
- int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+ mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
+
+ int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
if (viewType < 0) {
// This is a class that was seen after we returned the count, so
// don't recycle it.
@@ -242,7 +277,7 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn
mHasReturnedViewTypeCount = true;
}
- return Math.max(1, mPreferenceClassNames.size());
+ return Math.max(1, mPreferenceLayouts.size());
}
}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index fe3471d..2b20946 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -134,10 +134,7 @@ public class PreferenceManager {
private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
- /**
- * @hide
- */
- public PreferenceManager(Activity activity, int firstRequestCode) {
+ PreferenceManager(Activity activity, int firstRequestCode) {
mActivity = activity;
mNextRequestCode = firstRequestCode;
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index a264594..970d520 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -230,10 +230,14 @@ public class VolumePreference extends SeekBarPreference implements
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
-
if (mSeekBar != null) {
- mSeekBar.setProgress(System.getInt(mContext.getContentResolver(),
- System.VOLUME_SETTINGS[mStreamType], 0));
+ int volume = System.getInt(mContext.getContentResolver(),
+ System.VOLUME_SETTINGS[mStreamType], -1);
+ // Works around an atomicity problem with volume updates
+ // TODO: Fix the actual issue, probably in AudioService
+ if (volume >= 0) {
+ mSeekBar.setProgress(volume);
+ }
}
}
};
diff --git a/core/java/android/provider/Applications.java b/core/java/android/provider/Applications.java
index 0b0ce58..7aabc50 100644
--- a/core/java/android/provider/Applications.java
+++ b/core/java/android/provider/Applications.java
@@ -16,67 +16,112 @@
package android.provider;
-import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.database.Cursor;
import android.net.Uri;
-import android.widget.SimpleCursorAdapter;
+
+import java.util.List;
/**
- * <p>The Applications provider gives information about installed applications.</p>
- *
- * <p>This provider provides the following columns:
- *
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
- *
- * <thead>
- * <tr><th>Column Name</th> <th>Description</th> </tr>
- * </thead>
+ * The Applications provider gives information about installed applications.
*
- * <tbody>
- * <tr><th>{@link SearchManager#SUGGEST_COLUMN_TEXT_1}</th>
- * <td>The application name.</td>
- * </tr>
- *
- * <tr><th>{@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}</th>
- * <td>The component to be used when forming the intent.</td>
- * </tr>
- *
- * <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_1}</th>
- * <td>The application's icon resource id, prepended by its package name and
- * separated by a colon, e.g., "com.android.alarmclock:2130837524". The
- * package name is required for an activity interpreting this value to
- * be able to correctly access the icon drawable, for example, in an override of
- * {@link SimpleCursorAdapter#setViewImage(android.widget.ImageView, String)}.</td>
- * </tr>
- *
- * <tr><th>{@link SearchManager#SUGGEST_COLUMN_ICON_2}</th>
- * <td><i>Unused - column provided to conform to the {@link SearchManager} stipulation
- * that all providers provide either both or neither of
- * {@link SearchManager#SUGGEST_COLUMN_ICON_1} and
- * {@link SearchManager#SUGGEST_COLUMN_ICON_2}.</td>
- * </tr>
- *
- * @hide pending API council approval - should be unhidden at the same time as
- * {@link SearchManager#SUGGEST_COLUMN_INTENT_COMPONENT}
+ * @hide Only used by ApplicationsProvider so far.
*/
public class Applications {
- private static final String TAG = "Applications";
/**
* The content authority for this provider.
- *
- * @hide
*/
public static final String AUTHORITY = "applications";
/**
* The content:// style URL for this provider
- *
- * @hide
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
/**
+ * The content path for application component URIs.
+ */
+ public static final String APPLICATION_PATH = "applications";
+
+ /**
+ * The content path for application search.
+ */
+ public static final String SEARCH_PATH = "search";
+
+ private static final String APPLICATION_SUB_TYPE = "vnd.android.application";
+
+ /**
+ * The MIME type for a single application item.
+ */
+ public static final String APPLICATION_ITEM_TYPE =
+ ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + APPLICATION_SUB_TYPE;
+
+ /**
+ * The MIME type for a list of application items.
+ */
+ public static final String APPLICATION_DIR_TYPE =
+ ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + APPLICATION_SUB_TYPE;
+
+ /**
* no public constructor since this is a utility class
*/
private Applications() {}
+
+ /**
+ * Gets a cursor with application search results.
+ * See {@link ApplicationColumns} for the columns available in the returned cursor.
+ */
+ public static Cursor search(ContentResolver resolver, String query) {
+ Uri searchUri = CONTENT_URI.buildUpon().appendPath(SEARCH_PATH).appendPath(query).build();
+ return resolver.query(searchUri, null, null, null, null);
+ }
+
+ /**
+ * Gets the application component name from an application URI.
+ *
+ * @param appUri A URI of the form
+ * "content://applications/applications/&lt;packageName&gt;/&lt;className&gt;".
+ * @return The component name for the application, or
+ * <code>null</code> if the given URI was <code>null</code>
+ * or malformed.
+ */
+ public static ComponentName uriToComponentName(Uri appUri) {
+ if (appUri == null) return null;
+ if (!ContentResolver.SCHEME_CONTENT.equals(appUri.getScheme())) return null;
+ if (!AUTHORITY.equals(appUri.getAuthority())) return null;
+ List<String> pathSegments = appUri.getPathSegments();
+ if (pathSegments.size() != 3) return null;
+ if (!APPLICATION_PATH.equals(pathSegments.get(0))) return null;
+ String packageName = pathSegments.get(1);
+ String name = pathSegments.get(2);
+ return new ComponentName(packageName, name);
+ }
+
+ /**
+ * Gets the URI for an application component.
+ *
+ * @param packageName The name of the application's package.
+ * @param className The class name of the application.
+ * @return A URI of the form
+ * "content://applications/applications/&lt;packageName&gt;/&lt;className&gt;".
+ */
+ public static Uri componentNameToUri(String packageName, String className) {
+ return Applications.CONTENT_URI.buildUpon()
+ .appendEncodedPath(APPLICATION_PATH)
+ .appendPath(packageName)
+ .appendPath(className)
+ .build();
+ }
+
+ /**
+ * The columns in application cursors, like those returned by
+ * {@link Applications#search(ContentResolver, String)}.
+ */
+ public interface ApplicationColumns extends BaseColumns {
+ public static final String NAME = "name";
+ public static final String ICON = "icon";
+ public static final String URI = "uri";
+ }
}
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index c8b7f99..2fba1d7 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
+import android.os.AsyncTask;
import android.util.Log;
import android.webkit.WebIconDatabase;
@@ -34,12 +35,6 @@ public class Browser {
Uri.parse("content://browser/bookmarks");
/**
- * The inline scheme to show embedded content in a browser.
- * @hide
- */
- public static final Uri INLINE_URI = Uri.parse("inline:");
-
- /**
* The name of extra data when starting Browser with ACTION_VIEW or
* ACTION_SEARCH intent.
* <p>
@@ -62,45 +57,13 @@ public class Browser {
"com.android.browser.application_id";
/**
- * The content to be rendered when url's scheme is inline.
- * @hide
- */
- public static final String EXTRA_INLINE_CONTENT ="com.android.browser.inline.content";
-
- /**
- * The encoding of the inlined content for inline scheme.
- * @hide
- */
- public static final String EXTRA_INLINE_ENCODING ="com.android.browser.inline.encoding";
-
- /**
- * The url used when the inline content is falied to render.
- * @hide
- */
- public static final String EXTRA_INLINE_FAILURL ="com.android.browser.inline.failurl";
-
- /**
- * The name of the extra data in the VIEW intent. The data is in boolean.
- * <p>
- * If the Browser is handling the intent and the setting for
- * USE_LOCATION_FOR_SERVICES is allow, the Browser will send the location in
- * the POST data if this extra data is presented and it is true.
- * <p>
- * pending api approval
- * @hide
- */
- public static final String EXTRA_APPEND_LOCATION = "com.android.browser.append_location";
-
- /**
- * The name of the extra data in the VIEW intent. The data is in the format of
- * a byte array.
+ * The name of the extra data in the VIEW intent. The data are key/value
+ * pairs in the format of Bundle. They will be sent in the HTTP request
+ * headers for the provided url. The keys can't be the standard HTTP headers
+ * as they are set by the WebView. The url's schema must be http(s).
* <p>
- * Any value sent here will be passed in the http request to the provided url as post data.
- * <p>
- * pending api approval
- * @hide
*/
- public static final String EXTRA_POST_DATA = "com.android.browser.post_data";
+ public static final String EXTRA_HEADERS = "com.android.browser.headers";
/* if you change column order you must also change indices
below */
@@ -108,7 +71,7 @@ public class Browser {
BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL,
- BookmarkColumns.TOUCH_ICON };
+ BookmarkColumns.TOUCH_ICON, BookmarkColumns.USER_ENTERED };
/* these indices dependent on HISTORY_PROJECTION */
public static final int HISTORY_PROJECTION_ID_INDEX = 0;
@@ -173,9 +136,24 @@ public class Browser {
c.startActivity(i);
}
+ /**
+ * Stores a Bitmap extra in an {@link Intent} representing the screenshot of
+ * a page to share. When receiving an {@link Intent#ACTION_SEND} from the
+ * Browser, use this to access the screenshot.
+ * @hide
+ */
+ public final static String EXTRA_SHARE_SCREENSHOT = "share_screenshot";
+
+ /**
+ * Stores a Bitmap extra in an {@link Intent} representing the favicon of a
+ * page to share. When receiving an {@link Intent#ACTION_SEND} from the
+ * Browser, use this to access the favicon.
+ * @hide
+ */
+ public final static String EXTRA_SHARE_FAVICON = "share_favicon";
+
public static final void sendString(Context c, String s) {
- sendString(c, s,
- c.getText(com.android.internal.R.string.sendText).toString());
+ sendString(c, s, c.getString(com.android.internal.R.string.sendText));
}
/**
@@ -225,6 +203,57 @@ public class Browser {
new String[] { BookmarkColumns.URL }, null, null, null);
}
+ private static final void addOrUrlEquals(StringBuilder sb) {
+ sb.append(" OR " + BookmarkColumns.URL + " = ");
+ }
+
+ /**
+ * Return a Cursor with all history/bookmarks that are similar to url,
+ * where similar means 'http(s)://' and 'www.' are optional, but the rest
+ * of the url is the same.
+ * @param cr The ContentResolver used to access the database.
+ * @param url The url to compare to.
+ * @hide
+ */
+ public static final Cursor getVisitedLike(ContentResolver cr, String url) {
+ boolean secure = false;
+ String compareString = url;
+ if (compareString.startsWith("http://")) {
+ compareString = compareString.substring(7);
+ } else if (compareString.startsWith("https://")) {
+ compareString = compareString.substring(8);
+ secure = true;
+ }
+ if (compareString.startsWith("www.")) {
+ compareString = compareString.substring(4);
+ }
+ StringBuilder whereClause = null;
+ if (secure) {
+ whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ "https://" + compareString);
+ addOrUrlEquals(whereClause);
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ "https://www." + compareString);
+ } else {
+ whereClause = new StringBuilder(BookmarkColumns.URL + " = ");
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ compareString);
+ addOrUrlEquals(whereClause);
+ String wwwString = "www." + compareString;
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ wwwString);
+ addOrUrlEquals(whereClause);
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ "http://" + compareString);
+ addOrUrlEquals(whereClause);
+ DatabaseUtils.appendEscapedSQLString(whereClause,
+ "http://" + wwwString);
+ }
+ return cr.query(BOOKMARKS_URI, HISTORY_PROJECTION,
+ whereClause.toString(), null, null);
+ }
+
/**
* Update the visited history to acknowledge that a site has been
* visited.
@@ -232,44 +261,53 @@ public class Browser {
* Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param url The site being visited.
- * @param real Whether this is an actual visit, and should be added to the
- * number of visits.
+ * @param real If true, this is an actual visit, and should add to the
+ * number of visits. If false, the user entered it manually.
*/
public static final void updateVisitedHistory(ContentResolver cr,
String url, boolean real) {
long now = new Date().getTime();
+ Cursor c = null;
try {
- StringBuilder sb = new StringBuilder(BookmarkColumns.URL + " = ");
- DatabaseUtils.appendEscapedSQLString(sb, url);
- Cursor c = cr.query(
- BOOKMARKS_URI,
- HISTORY_PROJECTION,
- sb.toString(),
- null,
- null);
+ c = getVisitedLike(cr, url);
/* We should only get one answer that is exactly the same. */
if (c.moveToFirst()) {
ContentValues map = new ContentValues();
if (real) {
map.put(BookmarkColumns.VISITS, c
.getInt(HISTORY_PROJECTION_VISITS_INDEX) + 1);
+ } else {
+ map.put(BookmarkColumns.USER_ENTERED, 1);
}
map.put(BookmarkColumns.DATE, now);
- cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null);
+ String[] projection = new String[]
+ { Integer.valueOf(c.getInt(0)).toString() };
+ cr.update(BOOKMARKS_URI, map, "_id = ?", projection);
} else {
truncateHistory(cr);
ContentValues map = new ContentValues();
+ int visits;
+ int user_entered;
+ if (real) {
+ visits = 1;
+ user_entered = 0;
+ } else {
+ visits = 0;
+ user_entered = 1;
+ }
map.put(BookmarkColumns.URL, url);
- map.put(BookmarkColumns.VISITS, real ? 1 : 0);
+ map.put(BookmarkColumns.VISITS, visits);
map.put(BookmarkColumns.DATE, now);
map.put(BookmarkColumns.BOOKMARK, 0);
map.put(BookmarkColumns.TITLE, url);
map.put(BookmarkColumns.CREATED, 0);
+ map.put(BookmarkColumns.USER_ENTERED, user_entered);
cr.insert(BOOKMARKS_URI, map);
}
- c.deactivate();
} catch (IllegalStateException e) {
- return;
+ Log.e(LOGTAG, "updateVisitedHistory", e);
+ } finally {
+ if (c != null) c.close();
}
}
@@ -280,23 +318,27 @@ public class Browser {
* @hide pending API council approval
*/
public static final String[] getVisitedHistory(ContentResolver cr) {
+ Cursor c = null;
+ String[] str = null;
try {
String[] projection = new String[] {
"url"
};
- Cursor c = cr.query(BOOKMARKS_URI, projection, "visits > 0", null,
+ c = cr.query(BOOKMARKS_URI, projection, "visits > 0", null,
null);
- String[] str = new String[c.getCount()];
+ str = new String[c.getCount()];
int i = 0;
while (c.moveToNext()) {
str[i] = c.getString(0);
i++;
}
- c.deactivate();
- return str;
} catch (IllegalStateException e) {
- return new String[0];
+ Log.e(LOGTAG, "getVisitedHistory", e);
+ str = new String[0];
+ } finally {
+ if (c != null) c.close();
}
+ return str;
}
/**
@@ -311,9 +353,10 @@ public class Browser {
* @param cr The ContentResolver used to access the database.
*/
public static final void truncateHistory(ContentResolver cr) {
+ Cursor c = null;
try {
// Select non-bookmark history, ordered by date
- Cursor c = cr.query(
+ c = cr.query(
BOOKMARKS_URI,
TRUNCATE_HISTORY_PROJECTION,
"bookmark = 0",
@@ -325,16 +368,16 @@ public class Browser {
for (int i = 0; i < TRUNCATE_N_OLDEST; i++) {
// Log.v(LOGTAG, "truncate history " +
// c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
- deleteHistoryWhere(
- cr, "_id = " +
- c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX));
+ cr.delete(BOOKMARKS_URI, "_id = " +
+ c.getInt(TRUNCATE_HISTORY_PROJECTION_ID_INDEX),
+ null);
if (!c.moveToNext()) break;
}
}
- c.deactivate();
} catch (IllegalStateException e) {
Log.e(LOGTAG, "truncateHistory", e);
- return;
+ } finally {
+ if (c != null) c.close();
}
}
@@ -345,8 +388,10 @@ public class Browser {
* @return boolean True if the history can be cleared.
*/
public static final boolean canClearHistory(ContentResolver cr) {
+ Cursor c = null;
+ boolean ret = false;
try {
- Cursor c = cr.query(
+ c = cr.query(
BOOKMARKS_URI,
new String [] { BookmarkColumns._ID,
BookmarkColumns.BOOKMARK,
@@ -355,12 +400,13 @@ public class Browser {
null,
null
);
- boolean ret = c.moveToFirst();
- c.deactivate();
- return ret;
+ ret = c.moveToFirst();
} catch (IllegalStateException e) {
- return false;
+ Log.e(LOGTAG, "canClearHistory", e);
+ } finally {
+ if (c != null) c.close();
}
+ return ret;
}
/**
@@ -384,57 +430,57 @@ public class Browser {
*/
private static final void deleteHistoryWhere(ContentResolver cr,
String whereClause) {
+ Cursor c = null;
try {
- Cursor c = cr.query(BOOKMARKS_URI,
+ c = cr.query(BOOKMARKS_URI,
HISTORY_PROJECTION,
whereClause,
null,
null);
- if (!c.moveToFirst()) {
- c.deactivate();
- return;
- }
-
- final WebIconDatabase iconDb = WebIconDatabase.getInstance();
- /* Delete favicons, and revert bookmarks which have been visited
- * to simply bookmarks.
- */
- StringBuffer sb = new StringBuffer();
- boolean firstTime = true;
- do {
- String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
- boolean isBookmark =
- c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
- if (isBookmark) {
- if (firstTime) {
- firstTime = false;
+ if (c.moveToFirst()) {
+ final WebIconDatabase iconDb = WebIconDatabase.getInstance();
+ /* Delete favicons, and revert bookmarks which have been visited
+ * to simply bookmarks.
+ */
+ StringBuffer sb = new StringBuffer();
+ boolean firstTime = true;
+ do {
+ String url = c.getString(HISTORY_PROJECTION_URL_INDEX);
+ boolean isBookmark =
+ c.getInt(HISTORY_PROJECTION_BOOKMARK_INDEX) == 1;
+ if (isBookmark) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(" OR ");
+ }
+ sb.append("( _id = ");
+ sb.append(c.getInt(0));
+ sb.append(" )");
} else {
- sb.append(" OR ");
+ iconDb.releaseIconForPageUrl(url);
}
- sb.append("( _id = ");
- sb.append(c.getInt(0));
- sb.append(" )");
- } else {
- iconDb.releaseIconForPageUrl(url);
- }
- } while (c.moveToNext());
- c.deactivate();
+ } while (c.moveToNext());
- if (!firstTime) {
- ContentValues map = new ContentValues();
- map.put(BookmarkColumns.VISITS, 0);
- map.put(BookmarkColumns.DATE, 0);
- /* FIXME: Should I also remove the title? */
- cr.update(BOOKMARKS_URI, map, sb.toString(), null);
- }
+ if (!firstTime) {
+ ContentValues map = new ContentValues();
+ map.put(BookmarkColumns.VISITS, 0);
+ map.put(BookmarkColumns.DATE, 0);
+ /* FIXME: Should I also remove the title? */
+ cr.update(BOOKMARKS_URI, map, sb.toString(), null);
+ }
- String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
- if (whereClause != null) {
- deleteWhereClause += " AND " + whereClause;
+ String deleteWhereClause = BookmarkColumns.BOOKMARK + " = 0";
+ if (whereClause != null) {
+ deleteWhereClause += " AND " + whereClause;
+ }
+ cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
}
- cr.delete(BOOKMARKS_URI, deleteWhereClause, null);
} catch (IllegalStateException e) {
+ Log.e(LOGTAG, "deleteHistoryWhere", e);
return;
+ } finally {
+ if (c != null) c.close();
}
}
@@ -489,8 +535,9 @@ public class Browser {
*/
public static final void addSearchUrl(ContentResolver cr, String search) {
long now = new Date().getTime();
+ Cursor c = null;
try {
- Cursor c = cr.query(
+ c = cr.query(
SEARCHES_URI,
SEARCHES_PROJECTION,
SEARCHES_WHERE_CLAUSE,
@@ -505,10 +552,10 @@ public class Browser {
} else {
cr.insert(SEARCHES_URI, map);
}
- c.deactivate();
} catch (IllegalStateException e) {
Log.e(LOGTAG, "addSearchUrl", e);
- return;
+ } finally {
+ if (c != null) c.close();
}
}
/**
@@ -527,7 +574,9 @@ public class Browser {
}
/**
- * Request all icons from the database.
+ * Request all icons from the database. This call must either be called
+ * in the main thread or have had Looper.prepare() invoked in the calling
+ * thread.
* Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param where Clause to be used to limit the query from the database.
@@ -537,23 +586,8 @@ public class Browser {
*/
public static final void requestAllIcons(ContentResolver cr, String where,
WebIconDatabase.IconListener listener) {
- try {
- final Cursor c = cr.query(
- BOOKMARKS_URI,
- HISTORY_PROJECTION,
- where, null, null);
- if (c.moveToFirst()) {
- final WebIconDatabase db = WebIconDatabase.getInstance();
- do {
- db.requestIconForPageUrl(
- c.getString(HISTORY_PROJECTION_URL_INDEX),
- listener);
- } while (c.moveToNext());
- }
- c.deactivate();
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "requestAllIcons", e);
- }
+ WebIconDatabase.getInstance()
+ .bulkRequestIconForPageUrl(cr, where, listener);
}
public static class BookmarkColumns implements BaseColumns {
@@ -572,6 +606,10 @@ public class Browser {
* @hide
*/
public static final String TOUCH_ICON = "touch_icon";
+ /**
+ * @hide
+ */
+ public static final String USER_ENTERED = "user_entered";
}
public static class SearchColumns implements BaseColumns {
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f046cef..9a09805 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -16,34 +16,27 @@
package android.provider;
+import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.CursorEntityIterator;
+import android.content.Entity;
+import android.content.EntityIterator;
import android.content.Intent;
import android.database.Cursor;
+import android.database.DatabaseUtils;
import android.net.Uri;
+import android.os.RemoteException;
import android.pim.ICalendar;
-import android.pim.RecurrenceSet;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.Log;
-import android.accounts.Account;
-import com.android.internal.database.ArrayListCursor;
-import com.google.android.gdata.client.AndroidGDataClient;
-import com.google.android.gdata.client.AndroidXmlParserFactory;
-import com.google.wireless.gdata.calendar.client.CalendarClient;
-import com.google.wireless.gdata.calendar.data.EventEntry;
-import com.google.wireless.gdata.calendar.data.Who;
-import com.google.wireless.gdata.calendar.parser.xml.XmlCalendarGDataParserFactory;
-import com.google.wireless.gdata.data.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Vector;
/**
* The Calendar provider contains all calendar events.
@@ -57,8 +50,7 @@ public final class Calendar {
/**
* Broadcast Action: An event reminder.
*/
- public static final String
- EVENT_REMINDER_ACTION = "android.intent.action.EVENT_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
@@ -67,7 +59,7 @@ public final class Calendar {
public static final String EVENT_BEGIN_TIME = "beginTime";
public static final String EVENT_END_TIME = "endTime";
- public static final String AUTHORITY = "calendar";
+ public static final String AUTHORITY = "com.android.calendar";
/**
* The content:// style URL for the top-level calendar authority
@@ -76,16 +68,20 @@ public final class Calendar {
Uri.parse("content://" + AUTHORITY);
/**
+ * 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)}.
+ */
+ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+
+ /**
* Columns from the Calendars table that other tables join into themselves.
*/
public interface CalendarsColumns
{
/**
- * A string that uniquely identifies this contact to its source
- */
- public static final String SOURCE_ID = "sourceid";
-
- /**
* The color of the calendar
* <P>Type: INTEGER (color value)</P>
*/
@@ -137,13 +133,81 @@ public final class Calendar {
* <p>Type: String (blob)</p>
*/
public static final String SYNC_STATE = "sync_state";
+
+ /**
+ * The account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT = "_sync_account";
+
+ /**
+ * The type of the account that was used to sync the entry to the device.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+ /**
+ * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_ID = "_sync_id";
+
+ /**
+ * The last time, from the sync source's point of view, that this row has been synchronized.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_TIME = "_sync_time";
+
+ /**
+ * The version of the row, as assigned by the server.
+ * <P>Type: TEXT</P>
+ */
+ public static final String _SYNC_VERSION = "_sync_version";
+
+ /**
+ * 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>
+ */
+ public static final String _SYNC_DATA = "_sync_local_id";
+
+ /**
+ * Used only in persistent providers, and only during merging.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_MARK = "_sync_mark";
+
+ /**
+ * Used to indicate that local, unsynced, changes are present.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DIRTY = "_sync_dirty";
+
+ /**
+ * The name of the account instance to which this row belongs, which when paired with
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * <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.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ACCOUNT_TYPE = "account_type";
}
/**
* Contains a list of available calendars.
*/
- public static class Calendars implements BaseColumns, SyncConstValue, CalendarsColumns
+ public static class Calendars implements BaseColumns, CalendarsColumns
{
+ private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars._SYNC_ACCOUNT + "=?"
+ + " AND " + Calendars._SYNC_ACCOUNT_TYPE + "=?";
+
public static final Cursor query(ContentResolver cr, String[] projection,
String where, String orderBy)
{
@@ -173,16 +237,14 @@ public final class Calendar {
public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
// delete all calendars that match this account
return Calendar.Calendars.delete(cr,
- Calendar.Calendars._SYNC_ACCOUNT + "=? AND "
- + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=?",
- new String[] {account.name, account.type});
+ WHERE_DELETE_FOR_ACCOUNT,
+ new String[] { account.name, account.type });
}
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/calendars");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calendars");
/**
* The default sort order for this table
@@ -225,6 +287,13 @@ public final class Calendar {
* <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 interface AttendeesColumns {
@@ -282,10 +351,8 @@ public final class Calendar {
public static final int ATTENDEE_STATUS_TENTATIVE = 4;
}
- public static final class Attendees implements BaseColumns,
- AttendeesColumns, EventsColumns {
- public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/attendees");
+ public static final class Attendees implements BaseColumns, AttendeesColumns, EventsColumns {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/attendees");
// TODO: fill out this class when we actually start utilizing attendees
// in the calendar application.
@@ -341,11 +408,17 @@ public final class Calendar {
* This field is copied here so that we can efficiently filter out
* events that are declined without having to look in the Attendees
* table.
- *
+ *
* <P>Type: INTEGER (int)</P>
*/
public static final String SELF_ATTENDEE_STATUS = "selfAttendeeStatus";
-
+
+ /**
+ * This column is available for use by sync adapters
+ * <P>Type: TEXT</P>
+ */
+ public static final String SYNC_ADAPTER_DATA = "syncAdapterData";
+
/**
* The comments feed uri.
* <P>Type: TEXT</P>
@@ -514,13 +587,215 @@ public final class Calendar {
* <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.
+ */
+ public static final class EventsEntity implements BaseColumns, EventsColumns, CalendarsColumns {
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/event_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);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ private final ContentResolver mResolver;
+ private final ContentProviderClient mProvider;
+
+ private static final String[] REMINDERS_PROJECTION = new String[] {
+ Reminders.MINUTES,
+ Reminders.METHOD,
+ };
+ private static final int COLUMN_MINUTES = 0;
+ private static final int COLUMN_METHOD = 1;
+
+ private static final String[] ATTENDEES_PROJECTION = new String[] {
+ Attendees.ATTENDEE_NAME,
+ Attendees.ATTENDEE_EMAIL,
+ Attendees.ATTENDEE_RELATIONSHIP,
+ Attendees.ATTENDEE_TYPE,
+ Attendees.ATTENDEE_STATUS,
+ };
+ private static final int COLUMN_ATTENDEE_NAME = 0;
+ private static final int COLUMN_ATTENDEE_EMAIL = 1;
+ private static final int COLUMN_ATTENDEE_RELATIONSHIP = 2;
+ private static final int COLUMN_ATTENDEE_TYPE = 3;
+ private static final int COLUMN_ATTENDEE_STATUS = 4;
+ private static final String[] EXTENDED_PROJECTION = new String[] {
+ ExtendedProperties._ID,
+ ExtendedProperties.NAME,
+ ExtendedProperties.VALUE
+ };
+ private static final int COLUMN_ID = 0;
+ private static final int COLUMN_NAME = 1;
+ private static final int COLUMN_VALUE = 2;
+
+ private static final String WHERE_EVENT_ID = "event_id=?";
+
+ public EntityIteratorImpl(Cursor cursor, ContentResolver resolver) {
+ super(cursor);
+ mResolver = resolver;
+ mProvider = null;
+ }
+
+ public EntityIteratorImpl(Cursor cursor, ContentProviderClient provider) {
+ super(cursor);
+ mResolver = null;
+ mProvider = provider;
+ }
+
+ @Override
+ public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+ // we expect the cursor is already at the row we need to read from
+ final long eventId = cursor.getLong(cursor.getColumnIndexOrThrow(Events._ID));
+ 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, ALL_DAY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBILITY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, TRANSPARENCY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ HAS_EXTENDED_PROPERTIES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, RDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXRULE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EXDATE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORIGINAL_EVENT);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
+ ORIGINAL_INSTANCE_TIME);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ORIGINAL_ALL_DAY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_DATE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, HAS_ATTENDEE_DATA);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ GUESTS_CAN_INVITE_OTHERS);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_MODIFY);
+ 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, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.URL);
+
+ Entity entity = new Entity(cv);
+ Cursor subCursor;
+ if (mResolver != null) {
+ subCursor = mResolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues reminderValues = new ContentValues();
+ reminderValues.put(Reminders.MINUTES, subCursor.getInt(COLUMN_MINUTES));
+ reminderValues.put(Reminders.METHOD, subCursor.getInt(COLUMN_METHOD));
+ entity.addSubValue(Reminders.CONTENT_URI, reminderValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues attendeeValues = new ContentValues();
+ attendeeValues.put(Attendees.ATTENDEE_NAME,
+ subCursor.getString(COLUMN_ATTENDEE_NAME));
+ attendeeValues.put(Attendees.ATTENDEE_EMAIL,
+ subCursor.getString(COLUMN_ATTENDEE_EMAIL));
+ attendeeValues.put(Attendees.ATTENDEE_RELATIONSHIP,
+ subCursor.getInt(COLUMN_ATTENDEE_RELATIONSHIP));
+ attendeeValues.put(Attendees.ATTENDEE_TYPE,
+ subCursor.getInt(COLUMN_ATTENDEE_TYPE));
+ attendeeValues.put(Attendees.ATTENDEE_STATUS,
+ subCursor.getInt(COLUMN_ATTENDEE_STATUS));
+ entity.addSubValue(Attendees.CONTENT_URI, attendeeValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ if (mResolver != null) {
+ subCursor = mResolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ } else {
+ subCursor = mProvider.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROJECTION,
+ WHERE_EVENT_ID,
+ new String[] { Long.toString(eventId) } /* selectionArgs */,
+ null /* sortOrder */);
+ }
+ try {
+ while (subCursor.moveToNext()) {
+ ContentValues extendedValues = new ContentValues();
+ extendedValues.put(ExtendedProperties._ID,
+ subCursor.getString(COLUMN_ID));
+ extendedValues.put(ExtendedProperties.NAME,
+ subCursor.getString(COLUMN_NAME));
+ extendedValues.put(ExtendedProperties.VALUE,
+ subCursor.getString(COLUMN_VALUE));
+ entity.addSubValue(ExtendedProperties.CONTENT_URI, extendedValues);
+ }
+ } finally {
+ subCursor.close();
+ }
+
+ cursor.moveToNext();
+ return entity;
+ }
+ }
}
/**
* Contains one entry per calendar event. Recurring events show up as a single entry.
*/
- public static final class Events implements BaseColumns, SyncConstValue,
- EventsColumns, CalendarsColumns {
+ public static final class Events implements BaseColumns, EventsColumns, CalendarsColumns {
private static final String[] FETCH_ENTRY_COLUMNS =
new String[] { Events._SYNC_ACCOUNT, Events._SYNC_ID };
@@ -532,8 +807,6 @@ public final class Calendar {
AttendeesColumns.ATTENDEE_TYPE,
AttendeesColumns.ATTENDEE_STATUS };
- private static CalendarClient sCalendarClient = null;
-
public static final Cursor query(ContentResolver cr, String[] projection) {
return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
}
@@ -554,172 +827,14 @@ public final class Calendar {
return null;
}
- public static final Uri insertVEvent(ContentResolver cr,
- ICalendar.Component event, long calendarId, int status,
- ContentValues values) {
-
- // TODO: define VEVENT component names as constants in some
- // appropriate class (ICalendar.Component?).
-
- values.clear();
-
- // title
- String title = extractValue(event, "SUMMARY");
- if (TextUtils.isEmpty(title)) {
- if (Config.LOGD) {
- Log.d(TAG, "No SUMMARY provided for event. "
- + "Cannot import.");
- }
- return null;
- }
- values.put(TITLE, title);
-
- // status
- values.put(STATUS, status);
-
- // description
- String description = extractValue(event, "DESCRIPTION");
- if (!TextUtils.isEmpty(description)) {
- values.put(DESCRIPTION, description);
- }
-
- // where
- String where = extractValue(event, "LOCATION");
- if (!StringUtils.isEmpty(where)) {
- values.put(EVENT_LOCATION, where);
- }
-
- // Calendar ID
- values.put(CALENDAR_ID, calendarId);
-
- boolean timesSet = false;
-
- // TODO: deal with VALARMs
-
- // dtstart & dtend
- Time time = new Time(Time.TIMEZONE_UTC);
- String dtstart = null;
- String dtend = null;
- String duration = null;
- ICalendar.Property dtstartProp = event.getFirstProperty("DTSTART");
- // TODO: handle "floating" timezone (no timezone specified).
- if (dtstartProp != null) {
- dtstart = dtstartProp.getValue();
- if (!TextUtils.isEmpty(dtstart)) {
- ICalendar.Parameter tzidParam =
- dtstartProp.getFirstParameter("TZID");
- if (tzidParam != null && tzidParam.value != null) {
- time.clear(tzidParam.value);
- }
- try {
- time.parse(dtstart);
- } catch (Exception e) {
- if (Config.LOGD) {
- Log.d(TAG, "Cannot parse dtstart " + dtstart, e);
- }
- return null;
- }
- if (time.allDay) {
- values.put(ALL_DAY, 1);
- }
- values.put(DTSTART, time.toMillis(false /* use isDst */));
- values.put(EVENT_TIMEZONE, time.timezone);
- }
-
- ICalendar.Property dtendProp = event.getFirstProperty("DTEND");
- if (dtendProp != null) {
- dtend = dtendProp.getValue();
- if (!TextUtils.isEmpty(dtend)) {
- // TODO: make sure the timezones are the same for
- // start, end.
- try {
- time.parse(dtend);
- } catch (Exception e) {
- if (Config.LOGD) {
- Log.d(TAG, "Cannot parse dtend " + dtend, e);
- }
- return null;
- }
- values.put(DTEND, time.toMillis(false /* use isDst */));
- }
- } else {
- // look for a duration
- ICalendar.Property durationProp =
- event.getFirstProperty("DURATION");
- if (durationProp != null) {
- duration = durationProp.getValue();
- if (!TextUtils.isEmpty(duration)) {
- // TODO: check that it is valid?
- values.put(DURATION, duration);
- }
- }
- }
- }
- if (TextUtils.isEmpty(dtstart) ||
- (TextUtils.isEmpty(dtend) && TextUtils.isEmpty(duration))) {
- if (Config.LOGD) {
- Log.d(TAG, "No DTSTART or DTEND/DURATION defined.");
- }
- return null;
- }
-
- // rrule
- if (!RecurrenceSet.populateContentValues(event, values)) {
- return null;
- }
-
- return cr.insert(CONTENT_URI, values);
- }
-
- /**
- * Returns a singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- * @param cr The ContentResolver used to lookup the address of the calendar server in the
- * settings database.
- * @return The singleton instance of the CalendarClient used to fetch entries from the
- * calendar server.
- */
- private static synchronized CalendarClient getCalendarClient(ContentResolver cr) {
- if (sCalendarClient == null) {
- sCalendarClient = new CalendarClient(
- new AndroidGDataClient(cr),
- new XmlCalendarGDataParserFactory(new AndroidXmlParserFactory()));
- }
- return sCalendarClient;
- }
-
- /**
- * Extracts the attendees information out of event and adds it to a new ArrayList of columns
- * within the supplied ArrayList of rows. These rows are expected to be used within an
- * {@link ArrayListCursor}.
- */
- private static final void extractAttendeesIntoArrayList(EventEntry event,
- ArrayList<ArrayList> rows) {
- Log.d(TAG, "EVENT: " + event.toString());
- Vector<Who> attendees = (Vector<Who>) event.getAttendees();
-
- int numAttendees = attendees == null ? 0 : attendees.size();
-
- for (int i = 0; i < numAttendees; ++i) {
- Who attendee = attendees.elementAt(i);
- ArrayList row = new ArrayList();
- row.add(attendee.getValue());
- row.add(attendee.getEmail());
- row.add(attendee.getRelationship());
- row.add(attendee.getType());
- row.add(attendee.getStatus());
- rows.add(row);
- }
- }
-
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/events");
+ Uri.parse("content://" + AUTHORITY + "/events");
public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://calendar/deleted_events");
+ Uri.parse("content://" + AUTHORITY + "/deleted_events");
/**
* The default sort order for this table
@@ -733,12 +848,14 @@ public final class Calendar {
*/
public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
+
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end) {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
- return cr.query(builder.build(), projection, Calendars.SELECTED + "=1",
+ return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
null, DEFAULT_SORT_ORDER);
}
@@ -748,9 +865,9 @@ public final class Calendar {
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
if (TextUtils.isEmpty(where)) {
- where = Calendars.SELECTED + "=1";
+ where = WHERE_CALENDARS_SELECTED;
} else {
- where = "(" + where + ") AND " + Calendars.SELECTED + "=1";
+ where = "(" + where + ") AND " + WHERE_CALENDARS_SELECTED;
}
return cr.query(builder.build(), projection, where,
null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
@@ -759,9 +876,10 @@ public final class Calendar {
/**
* The content:// style URL for this table
*/
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/instances/when");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/when");
public static final Uri CONTENT_BY_DAY_URI =
- Uri.parse("content://calendar/instances/whenbyday");
+ Uri.parse("content://" + AUTHORITY + "/instances/whenbyday");
/**
* The default sort order for this table.
@@ -851,60 +969,42 @@ public final class Calendar {
public static final String MAX_INSTANCE = "maxInstance";
/**
- * The minimum Julian day in the BusyBits table.
+ * The minimum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MIN_BUSYBITS = "minBusyBits";
+ public static final String MIN_EVENTDAYS = "minEventDays";
/**
- * The maximum Julian day in the BusyBits table.
+ * The maximum Julian day in the EventDays table.
* <P>Type: INTEGER</P>
*/
- public static final String MAX_BUSYBITS = "maxBusyBits";
+ public static final String MAX_EVENTDAYS = "maxEventDays";
}
-
+
public static final class CalendarMetaData implements CalendarMetaDataColumns {
}
-
- public interface BusyBitsColumns {
- /**
- * The Julian day number.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String DAY = "day";
+ public interface EventDaysColumns {
/**
- * The 24 bits representing the 24 1-hour time slots in a day.
- * If an event in the Instances table overlaps part of a 1-hour
- * time slot then the corresponding bit is set. The first time slot
- * (12am to 1am) is bit 0. The last time slot (11pm to midnight)
- * is bit 23.
+ * The Julian starting day number.
* <P>Type: INTEGER (int)</P>
*/
- public static final String BUSYBITS = "busyBits";
+ public static final String STARTDAY = "startDay";
+ public static final String ENDDAY = "endDay";
- /**
- * The number of all-day events that occur on this day.
- * <P>Type: INTEGER (int)</P>
- */
- public static final String ALL_DAY_COUNT = "allDayCount";
}
-
- public static final class BusyBits implements BusyBitsColumns {
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/busybits/when");
- public static final String[] PROJECTION = { DAY, BUSYBITS, ALL_DAY_COUNT };
-
- // The number of minutes represented by one busy bit
- public static final int MINUTES_PER_BUSY_INTERVAL = 60;
-
- // The number of intervals in a day
- public static final int INTERVALS_PER_DAY = 24 * 60 / MINUTES_PER_BUSY_INTERVAL;
+ public static final class EventDays implements EventDaysColumns {
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/instances/groupbyday");
+
+ public static final String[] PROJECTION = { STARTDAY, ENDDAY };
+ public static final String SELECTION = "selected=1";
/**
- * Retrieves the busy bits for the Julian days starting at "startDay"
+ * Retrieves the days with events for the Julian days starting at "startDay"
* for "numDays".
- *
+ *
* @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)
@@ -918,8 +1018,8 @@ public final class Calendar {
Uri.Builder builder = CONTENT_URI.buildUpon();
ContentUris.appendId(builder, startDay);
ContentUris.appendId(builder, endDay);
- return cr.query(builder.build(), PROJECTION, null /* selection */,
- null /* selection args */, DAY);
+ return cr.query(builder.build(), PROJECTION, SELECTION,
+ null /* selection args */, STARTDAY);
}
}
@@ -955,7 +1055,7 @@ public final class Calendar {
public static final class Reminders implements BaseColumns, RemindersColumns, EventsColumns {
public static final String TABLE_NAME = "Reminders";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/reminders");
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
}
public interface CalendarAlertsColumns {
@@ -1025,22 +1125,38 @@ public final class Calendar {
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = "alarmTime ASC,begin ASC,title ASC";
+ public static final String DEFAULT_SORT_ORDER = "begin ASC,title ASC";
}
public static final class CalendarAlerts implements BaseColumns,
CalendarAlertsColumns, EventsColumns, CalendarsColumns {
+
public static final String TABLE_NAME = "CalendarAlerts";
- public static final Uri CONTENT_URI = Uri.parse("content://calendar/calendar_alerts");
-
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY +
+ "/calendar_alerts");
+
+ private static final String WHERE_ALARM_EXISTS = EVENT_ID + "=?"
+ + " AND " + BEGIN + "=?"
+ + " AND " + ALARM_TIME + "=?";
+
+ private static final String WHERE_FINDNEXTALARMTIME = ALARM_TIME + ">=?";
+ private static final String SORT_ORDER_ALARMTIME_ASC = ALARM_TIME + " ASC";
+
+ private static final String WHERE_RESCHEDULE_MISSED_ALARMS = STATE + "=" + SCHEDULED
+ + " AND " + ALARM_TIME + "<?"
+ + " AND " + ALARM_TIME + ">?"
+ + " AND " + END + ">=?";
+
/**
* This URI is for grouping the query results by event_id and begin
* time. This will return one result per instance of an event. So
* events with multiple alarms will appear just once, but multiple
* instances of a repeating event will show up multiple times.
*/
- public static final Uri CONTENT_URI_BY_INSTANCE =
- Uri.parse("content://calendar/calendar_alerts/by_instance");
+ public static final Uri CONTENT_URI_BY_INSTANCE =
+ Uri.parse("content://" + AUTHORITY + "/calendar_alerts/by_instance");
+
+ private static final boolean DEBUG = true;
public static final Uri insert(ContentResolver cr, long eventId,
long begin, long end, long alarmTime, int minutes) {
@@ -1059,15 +1175,15 @@ public final class Calendar {
}
public static final Cursor query(ContentResolver cr, String[] projection,
- String selection, String[] selectionArgs) {
+ String selection, String[] selectionArgs, String sortOrder) {
return cr.query(CONTENT_URI, projection, selection, selectionArgs,
- DEFAULT_SORT_ORDER);
+ sortOrder);
}
-
+
/**
* 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.
- *
+ *
* @param cr the ContentResolver
* @param millis the time in UTC milliseconds
* @return the next alarm time greater than or equal to "millis", or -1
@@ -1078,7 +1194,12 @@ public final class Calendar {
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
String[] projection = new String[] { ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ Cursor cursor = query(cr, projection,
+ WHERE_FINDNEXTALARMTIME,
+ new String[] {
+ Long.toString(millis)
+ },
+ SORT_ORDER_ALARMTIME_ASC);
long alarmTime = -1;
try {
if (cursor != null && cursor.moveToFirst()) {
@@ -1091,13 +1212,13 @@ public final class Calendar {
}
return alarmTime;
}
-
+
/**
* Searches the CalendarAlerts table for alarms that should have fired
* but have not and then reschedules them. This method can be called
* at boot time to restore alarms that may have been lost due to a
* phone reboot.
- *
+ *
* @param cr the ContentResolver
* @param context the Context
* @param manager the AlarmManager
@@ -1107,53 +1228,72 @@ public final class Calendar {
// Get all the alerts that have been scheduled but have not fired
// and should have fired by now and are not too old.
long now = System.currentTimeMillis();
- long ancient = now - 24 * DateUtils.HOUR_IN_MILLIS;
- String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.SCHEDULED
- + " AND " + CalendarAlerts.ALARM_TIME + "<" + now
- + " AND " + CalendarAlerts.ALARM_TIME + ">" + ancient
- + " AND " + CalendarAlerts.END + ">=" + now;
+ long ancient = now - DateUtils.DAY_IN_MILLIS;
String[] projection = new String[] {
- _ID,
- BEGIN,
- END,
ALARM_TIME,
};
- Cursor cursor = CalendarAlerts.query(cr, projection, selection, null);
+
+ // TODO: construct an explicit SQL query so that we can add
+ // "GROUPBY" instead of doing a sort and de-dup
+ Cursor cursor = CalendarAlerts.query(cr,
+ projection,
+ WHERE_RESCHEDULE_MISSED_ALARMS,
+ new String[] {
+ Long.toString(now),
+ Long.toString(ancient),
+ Long.toString(now)
+ },
+ SORT_ORDER_ALARMTIME_ASC);
if (cursor == null) {
return;
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+
+ if (DEBUG) {
Log.d(TAG, "missed alarms found: " + cursor.getCount());
}
-
+
try {
+ long alarmTime = -1;
+
while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- long begin = cursor.getLong(1);
- long end = cursor.getLong(2);
- long alarmTime = cursor.getLong(3);
- Uri uri = ContentUris.withAppendedId(CONTENT_URI, id);
- Intent intent = new Intent(android.provider.Calendar.EVENT_REMINDER_ACTION);
- intent.setData(uri);
- intent.putExtra(android.provider.Calendar.EVENT_BEGIN_TIME, begin);
- intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end);
- PendingIntent sender = PendingIntent.getBroadcast(context,
- 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- Log.w(TAG, "rescheduling missed alarm, id: " + id + " begin: " + begin
- + " end: " + end + " alarmTime: " + alarmTime);
- manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender);
+ long newAlarmTime = cursor.getLong(0);
+ if (alarmTime != newAlarmTime) {
+ if (DEBUG) {
+ Log.w(TAG, "rescheduling missed alarm. alarmTime: " + newAlarmTime);
+ }
+ scheduleAlarm(context, manager, newAlarmTime);
+ alarmTime = newAlarmTime;
+ }
}
} finally {
cursor.close();
}
-
}
-
+
+ public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
+ if (DEBUG) {
+ Time time = new Time();
+ time.set(alarmTime);
+ String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
+ Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
+ }
+
+ if (manager == null) {
+ manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ Intent intent = new Intent(EVENT_REMINDER_ACTION);
+ intent.setData(ContentUris.withAppendedId(Calendar.CONTENT_URI, alarmTime));
+ intent.putExtra(ALARM_TIME, alarmTime);
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
+ manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
+ }
+
/**
* Searches for an entry in the CalendarAlerts table that matches
* the given event id, begin time and alarm time. If one is found
* then this alarm already exists and this method returns true.
- *
+ *
* @param cr the ContentResolver
* @param eventId the event id to match
* @param begin the start time of the event in UTC millis
@@ -1163,13 +1303,18 @@ public final class Calendar {
*/
public static final boolean alarmExists(ContentResolver cr, long eventId,
long begin, long alarmTime) {
- String selection = CalendarAlerts.EVENT_ID + "=" + eventId
- + " AND " + CalendarAlerts.BEGIN + "=" + begin
- + " AND " + CalendarAlerts.ALARM_TIME + "=" + alarmTime;
// TODO: construct an explicit SQL query so that we can add
// "LIMIT 1" to the end and get just one result.
- String[] projection = new String[] { CalendarAlerts.ALARM_TIME };
- Cursor cursor = query(cr, projection, selection, null);
+ String[] projection = new String[] { ALARM_TIME };
+ Cursor cursor = query(cr,
+ projection,
+ WHERE_ALARM_EXISTS,
+ new String[] {
+ Long.toString(eventId),
+ Long.toString(begin),
+ Long.toString(alarmTime)
+ },
+ null);
boolean found = false;
try {
if (cursor != null && cursor.getCount() > 0) {
@@ -1208,9 +1353,30 @@ public final class Calendar {
public static final class ExtendedProperties implements BaseColumns,
ExtendedPropertiesColumns, EventsColumns {
public static final Uri CONTENT_URI =
- Uri.parse("content://calendar/extendedproperties");
+ Uri.parse("content://" + AUTHORITY + "/extendedproperties");
// TODO: fill out this class when we actually start utilizing extendedproperties
// in the calendar application.
}
+
+ /**
+ * A table provided for sync adapters to use for storing private sync state data.
+ *
+ * @see SyncStateContract
+ */
+ public static final class SyncState implements SyncStateContract.Columns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private SyncState() {}
+
+ public static final String CONTENT_DIRECTORY =
+ SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
+ }
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 7854423..d52632b 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -22,6 +22,7 @@ import com.android.internal.telephony.Connection;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
@@ -111,25 +112,25 @@ public class CallLog {
* <P>Type: TEXT</P>
*/
public static final String CACHED_NAME = "name";
-
+
/**
* The cached number type (Home, Work, etc) associated with the
* phone number, if it exists.
* This value is not guaranteed to be current, if the contact information
* associated with this number has changed.
- * <P>Type: INTEGER</P>
+ * <P>Type: INTEGER</P>
*/
public static final String CACHED_NUMBER_TYPE = "numbertype";
-
+
/**
* The cached number label, for a custom number type, associated with the
* phone number, if it exists.
* This value is not guaranteed to be current, if the contact information
* associated with this number has changed.
- * <P>Type: TEXT</P>
+ * <P>Type: TEXT</P>
*/
public static final String CACHED_NUMBER_LABEL = "numberlabel";
-
+
/**
* Adds a call to the call log.
*
@@ -137,15 +138,15 @@ public class CallLog {
* if the contact is unknown.
* @param context the context used to get the ContentResolver
* @param number the phone number to be added to the calls db
- * @param presentation the number presenting rules set by the network for
+ * @param presentation the number presenting rules set by the network for
* "allowed", "payphone", "restricted" or "unknown"
* @param callType enumerated values for "incoming", "outgoing", or "missed"
* @param start time stamp for the call in milliseconds
* @param duration call duration in seconds
- *
+ *
* {@hide}
*/
- public static Uri addCall(CallerInfo ci, Context context, String number,
+ public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, long start, int duration) {
final ContentResolver resolver = context.getContentResolver();
@@ -175,22 +176,47 @@ public class CallLog {
values.put(CACHED_NUMBER_TYPE, ci.numberType);
values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
}
-
+
if ((ci != null) && (ci.person_id > 0)) {
ContactsContract.Contacts.markAsContacted(resolver, ci.person_id);
}
-
+
Uri result = resolver.insert(CONTENT_URI, values);
-
+
removeExpiredEntries(context);
-
+
return result;
}
-
+
+ /**
+ * Query the call log database for the last dialed number.
+ * @param context Used to get the content resolver.
+ * @return The last phone number dialed (outgoing) or an empty
+ * string if none exist yet.
+ */
+ public static String getLastOutgoingCall(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ Cursor c = null;
+ try {
+ c = resolver.query(
+ CONTENT_URI,
+ new String[] {NUMBER},
+ TYPE + " = " + OUTGOING_TYPE,
+ null,
+ DEFAULT_SORT_ORDER + " LIMIT 1");
+ if (c == null || !c.moveToFirst()) {
+ return "";
+ }
+ return c.getString(0);
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+
private static void removeExpiredEntries(Context context) {
final ContentResolver resolver = context.getContentResolver();
resolver.delete(CONTENT_URI, "_id IN " +
- "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ " LIMIT -1 OFFSET 500)", null);
}
}
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
deleted file mode 100644
index 4134dc2..0000000
--- a/core/java/android/provider/Checkin.java
+++ /dev/null
@@ -1,326 +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.provider;
-
-import org.apache.commons.codec.binary.Base64;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.SQLException;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.server.data.CrashData;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
-
-/**
- * Contract class for the checkin provider, used to store events and
- * statistics that will be uploaded to a checkin server eventually.
- * Describes the exposed database schema, and offers methods to add
- * events and statistics to be uploaded.
- *
- * TODO: Move this to vendor/google when we have a home for it.
- *
- * @hide
- */
-public final class Checkin {
- public static final String AUTHORITY = "android.server.checkin";
-
- /**
- * The events table is a log of important timestamped occurrences.
- * Each event has a type tag and an optional string value.
- * If too many events are added before they can be reported, the
- * content provider will erase older events to limit the table size.
- */
- public interface Events extends BaseColumns {
- public static final String TABLE_NAME = "events";
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
-
- public static final String TAG = "tag"; // TEXT
- public static final String VALUE = "value"; // TEXT
- public static final String DATE = "date"; // INTEGER
-
- /** Valid tag values. Extend as necessary for your needs. */
- public enum Tag {
- APANIC_CONSOLE,
- APANIC_THREADS,
- AUTOTEST_FAILURE,
- AUTOTEST_SEQUENCE_BEGIN,
- AUTOTEST_SUITE_BEGIN,
- AUTOTEST_TCPDUMP_BEGIN,
- AUTOTEST_TCPDUMP_DATA,
- AUTOTEST_TCPDUMP_END,
- AUTOTEST_TEST_BEGIN,
- AUTOTEST_TEST_FAILURE,
- AUTOTEST_TEST_SUCCESS,
- BROWSER_BUG_REPORT,
- CARRIER_BUG_REPORT,
- CHECKIN_FAILURE,
- CHECKIN_SUCCESS,
- CPUFREQ_STATS,
- FOTA_BEGIN,
- FOTA_FAILURE,
- FOTA_INSTALL,
- FOTA_PROMPT,
- FOTA_PROMPT_ACCEPT,
- FOTA_PROMPT_REJECT,
- FOTA_PROMPT_SKIPPED,
- GSERVICES_ERROR,
- GSERVICES_UPDATE,
- LOGIN_SERVICE_ACCOUNT_TRIED,
- LOGIN_SERVICE_ACCOUNT_SAVED,
- LOGIN_SERVICE_AUTHENTICATE,
- LOGIN_SERVICE_CAPTCHA_ANSWERED,
- LOGIN_SERVICE_CAPTCHA_SHOWN,
- LOGIN_SERVICE_PASSWORD_ENTERED,
- LOGIN_SERVICE_SWITCH_GOOGLE_MAIL,
- NETWORK_DOWN,
- NETWORK_UP,
- PHONE_UI,
- RADIO_BUG_REPORT,
- SETUP_COMPLETED,
- SETUP_INITIATED,
- SETUP_IO_ERROR,
- SETUP_NETWORK_ERROR,
- SETUP_REQUIRED_CAPTCHA,
- SETUP_RETRIES_EXHAUSTED,
- SETUP_SERVER_ERROR,
- SETUP_SERVER_TIMEOUT,
- SETUP_NO_DATA_NETWORK,
- SYSTEM_BOOT,
- SYSTEM_LAST_KMSG,
- SYSTEM_RECOVERY_LOG,
- SYSTEM_RESTART,
- SYSTEM_SERVICE_LOOPING,
- SYSTEM_TOMBSTONE,
- TEST,
- BATTERY_DISCHARGE_INFO,
- MARKET_DOWNLOAD,
- MARKET_INSTALL,
- MARKET_REMOVE,
- MARKET_REFUND,
- MARKET_UNINSTALL,
- }
- }
-
- /**
- * The stats table is a list of counter values indexed by a tag name.
- * Each statistic has a count and sum fields, so it can track averages.
- * When multiple statistics are inserted with the same tag, the count
- * and sum fields are added together into a single entry in the database.
- */
- public interface Stats extends BaseColumns {
- public static final String TABLE_NAME = "stats";
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
-
- public static final String TAG = "tag"; // TEXT UNIQUE
- public static final String COUNT = "count"; // INTEGER
- public static final String SUM = "sum"; // REAL
-
- /** Valid tag values. Extend as necessary for your needs. */
- public enum Tag {
- BROWSER_SNAP_CENTER,
- BROWSER_TEXT_SIZE_CHANGE,
- BROWSER_ZOOM_OVERVIEW,
-
- CRASHES_REPORTED,
- CRASHES_TRUNCATED,
- ELAPSED_REALTIME_SEC,
- ELAPSED_UPTIME_SEC,
- HTTP_REQUEST,
- HTTP_REUSED,
- HTTP_STATUS,
- PHONE_GSM_REGISTERED,
- PHONE_GPRS_ATTEMPTED,
- PHONE_GPRS_CONNECTED,
- PHONE_RADIO_RESETS,
- TEST,
- NETWORK_RX_MOBILE,
- NETWORK_TX_MOBILE,
- PHONE_CDMA_REGISTERED,
- PHONE_CDMA_DATA_ATTEMPTED,
- PHONE_CDMA_DATA_CONNECTED,
- }
- }
-
- /**
- * The properties table is a set of tagged values sent with every checkin.
- * Unlike statistics or events, they are not cleared after being uploaded.
- * Multiple properties inserted with the same tag overwrite each other.
- */
- public interface Properties extends BaseColumns {
- public static final String TABLE_NAME = "properties";
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
-
- public static final String TAG = "tag"; // TEXT UNIQUE
- public static final String VALUE = "value"; // TEXT
-
- /** Valid tag values, to be extended as necessary. */
- public enum Tag {
- DESIRED_BUILD,
- MARKET_CHECKIN,
- }
- }
-
- /**
- * The crashes table is a log of crash reports, kept separate from the
- * general event log because crashes are large, important, and bursty.
- * Like the events table, the crashes table is pruned on insert.
- */
- public interface Crashes extends BaseColumns {
- public static final String TABLE_NAME = "crashes";
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME);
-
- // TODO: one or both of these should be a file attachment, not a column
- public static final String DATA = "data"; // TEXT
- public static final String LOGS = "logs"; // TEXT
- }
-
- /**
- * Intents with this action cause a checkin attempt. Normally triggered by
- * a periodic alarm, these may be sent directly to force immediate checkin.
- */
- public interface TriggerIntent {
- public static final String ACTION = "android.server.checkin.CHECKIN";
-
- // The category is used for GTalk service messages
- public static final String CATEGORY = "android.server.checkin.CHECKIN";
-
- // If true indicates that the checkin should only transfer market related data
- public static final String EXTRA_MARKET_ONLY = "market_only";
- }
-
- private static final String TAG = "Checkin";
-
- /**
- * Helper function to log an event to the database.
- *
- * @param resolver from {@link android.content.Context#getContentResolver}
- * @param tag identifying the type of event being recorded
- * @param value associated with event, if any
- * @return URI of the event that was added
- */
- static public Uri logEvent(ContentResolver resolver,
- Events.Tag tag, String value) {
- try {
- // Don't specify the date column; the content provider will add that.
- ContentValues values = new ContentValues();
- values.put(Events.TAG, tag.toString());
- if (value != null) values.put(Events.VALUE, value);
- return resolver.insert(Events.CONTENT_URI, values);
- } catch (IllegalArgumentException e) { // thrown when provider is unavailable.
- Log.w(TAG, "Can't log event " + tag + ": " + e);
- return null;
- } catch (SQLException e) {
- Log.e(TAG, "Can't log event " + tag, e); // Database errors are not fatal.
- return null;
- }
- }
-
- /**
- * Helper function to update statistics in the database.
- * Note that multiple updates to the same tag will be combined.
- *
- * @param tag identifying what is being observed
- * @param count of occurrences
- * @param sum of some value over these occurrences
- * @return URI of the statistic that was returned
- */
- static public Uri updateStats(ContentResolver resolver,
- Stats.Tag tag, int count, double sum) {
- try {
- ContentValues values = new ContentValues();
- values.put(Stats.TAG, tag.toString());
- if (count != 0) values.put(Stats.COUNT, count);
- if (sum != 0.0) values.put(Stats.SUM, sum);
- return resolver.insert(Stats.CONTENT_URI, values);
- } catch (IllegalArgumentException e) { // thrown when provider is unavailable.
- Log.w(TAG, "Can't update stat " + tag + ": " + e);
- return null;
- } catch (SQLException e) {
- Log.e(TAG, "Can't update stat " + tag, e); // Database errors are not fatal.
- return null;
- }
- }
-
- /** Minimum time to wait after a crash failure before trying again. */
- static private final long MIN_CRASH_FAILURE_RETRY = 10000; // 10 seconds
-
- /** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */
- static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY;
-
- /**
- * Helper function to report a crash.
- *
- * @param resolver from {@link android.content.Context#getContentResolver}
- * @param crash data from {@link android.server.data.CrashData}
- * @return URI of the crash report that was added
- */
- static public Uri reportCrash(ContentResolver resolver, byte[] crash) {
- try {
- // If we are in a situation where crash reports fail (such as a full disk),
- // it's important that we don't get into a loop trying to report failures.
- // So discard all crash reports for a few seconds after reporting fails.
- long realtime = SystemClock.elapsedRealtime();
- if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) {
- Log.e(TAG, "Crash logging skipped, too soon after logging failure");
- return null;
- }
-
- // HACK: we don't support BLOB values, so base64 encode it.
- byte[] encoded = Base64.encodeBase64(crash);
- ContentValues values = new ContentValues();
- values.put(Crashes.DATA, new String(encoded));
- Uri uri = resolver.insert(Crashes.CONTENT_URI, values);
- if (uri == null) {
- Log.e(TAG, "Error reporting crash");
- sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
- }
- return uri;
- } catch (Throwable t) {
- // To avoid an infinite crash-reporting loop, swallow all errors and exceptions.
- Log.e(TAG, "Error reporting crash: " + t);
- sLastCrashFailureRealtime = SystemClock.elapsedRealtime();
- return null;
- }
- }
-
- /**
- * Report a crash in CrashData format.
- *
- * @param resolver from {@link android.content.Context#getContentResolver}
- * @param crash data to report
- * @return URI of the crash report that was added
- */
- static public Uri reportCrash(ContentResolver resolver, CrashData crash) {
- try {
- ByteArrayOutputStream data = new ByteArrayOutputStream();
- crash.write(new DataOutputStream(data));
- return reportCrash(resolver, data.toByteArray());
- } catch (Throwable t) {
- // Swallow all errors and exceptions when writing crash report
- Log.e(TAG, "Error writing crash: " + t);
- return null;
- }
- }
-}
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index 1a38166..a29ecb5 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -1334,8 +1334,26 @@ public class Contacts {
}
/**
+ * TODO find a place to put the canonical version of these.
+ */
+ interface ProviderNames {
+ //
+ //NOTE: update Contacts.java with new providers when they're added.
+ //
+ String YAHOO = "Yahoo";
+ String GTALK = "GTalk";
+ String MSN = "MSN";
+ String ICQ = "ICQ";
+ String AIM = "AIM";
+ String XMPP = "XMPP";
+ String JABBER = "JABBER";
+ String SKYPE = "SKYPE";
+ String QQ = "QQ";
+ }
+
+ /**
* This looks up the provider name defined in
- * {@link android.provider.Im.ProviderNames} from the predefined IM protocol id.
+ * from the predefined IM protocol id.
* This is used for interacting with the IM application.
*
* @param protocol the protocol ID
@@ -1348,21 +1366,21 @@ public class Contacts {
public static String lookupProviderNameFromId(int protocol) {
switch (protocol) {
case PROTOCOL_GOOGLE_TALK:
- return Im.ProviderNames.GTALK;
+ return ProviderNames.GTALK;
case PROTOCOL_AIM:
- return Im.ProviderNames.AIM;
+ return ProviderNames.AIM;
case PROTOCOL_MSN:
- return Im.ProviderNames.MSN;
+ return ProviderNames.MSN;
case PROTOCOL_YAHOO:
- return Im.ProviderNames.YAHOO;
+ return ProviderNames.YAHOO;
case PROTOCOL_ICQ:
- return Im.ProviderNames.ICQ;
+ return ProviderNames.ICQ;
case PROTOCOL_JABBER:
- return Im.ProviderNames.JABBER;
+ return ProviderNames.JABBER;
case PROTOCOL_SKYPE:
- return Im.ProviderNames.SKYPE;
+ return ProviderNames.SKYPE;
case PROTOCOL_QQ:
- return Im.ProviderNames.QQ;
+ return ProviderNames.QQ;
}
return null;
}
@@ -1532,7 +1550,35 @@ public class Contacts {
* @deprecated see {@link android.provider.ContactsContract}
*/
@Deprecated
- public interface PresenceColumns extends Im.CommonPresenceColumns {
+ public interface PresenceColumns {
+ /**
+ * The priority, an integer, used by XMPP presence
+ * <P>Type: INTEGER</P>
+ */
+ String PRIORITY = "priority";
+
+ /**
+ * The server defined status.
+ * <P>Type: INTEGER (one of the values below)</P>
+ */
+ String PRESENCE_STATUS = ContactsContract.StatusUpdates.PRESENCE;
+
+ /**
+ * Presence Status definition
+ */
+ int OFFLINE = ContactsContract.StatusUpdates.OFFLINE;
+ int INVISIBLE = ContactsContract.StatusUpdates.INVISIBLE;
+ int AWAY = ContactsContract.StatusUpdates.AWAY;
+ int IDLE = ContactsContract.StatusUpdates.IDLE;
+ int DO_NOT_DISTURB = ContactsContract.StatusUpdates.DO_NOT_DISTURB;
+ int AVAILABLE = ContactsContract.StatusUpdates.AVAILABLE;
+
+ /**
+ * The user defined status line.
+ * <P>Type: TEXT</P>
+ */
+ String PRESENCE_CUSTOM_STATUS = ContactsContract.StatusUpdates.STATUS;
+
/**
* The IM service the presence is coming from. Formatted using either
* {@link Contacts.ContactMethods#encodePredefinedImProtocol} or
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a56bb45..40a408a 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -23,14 +23,19 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
+import android.content.CursorEntityIterator;
+import android.content.Entity;
+import android.content.EntityIterator;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
import android.graphics.Rect;
import android.net.Uri;
import android.os.RemoteException;
-import android.provider.ContactsContract.CommonDataKinds.Email;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
@@ -50,18 +55,21 @@ import java.io.InputStream;
* </p>
* <ul>
* <li>
- * The {@link Data} table contains all kinds of personal data: phone numbers,
- * email addresses etc. The list of data kinds that can be stored in this table
- * is open-ended. There is a predefined set of common kinds, but any application
- * can add its own data kinds.
+ * A row in the {@link Data} table can store any kind of personal data, such
+ * as a phone number or email addresses. The set of data kinds that can be
+ * stored in this table is open-ended. There is a predefined set of common
+ * kinds, but any application can add its own data kinds.
* </li>
* <li>
- * A row in the {@link RawContacts} table represents a set of Data describing a
- * person and associated with a single account.
+ * A row in the {@link RawContacts} table represents a set of data describing a
+ * person and associated with a single account (for example, one of the user's
+ * Gmail accounts).
* </li>
* <li>
* A row in the {@link Contacts} table represents an aggregate of one or more
- * RawContacts presumably describing the same person.
+ * RawContacts presumably describing the same person. When data in or associated with
+ * the RawContacts table is changed, the affected aggregate contacts are updated as
+ * necessary.
* </li>
* </ul>
* <p>
@@ -69,7 +77,8 @@ import java.io.InputStream;
* </p>
* <ul>
* <li>
- * {@link Groups}, which contains information about raw contact groups - the
+ * {@link Groups}, which contains information about raw contact groups
+ * such as Gmail contact groups. The
* current API does not support the notion of groups spanning multiple accounts.
* </li>
* <li>
@@ -100,11 +109,15 @@ public final class ContactsContract {
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
/**
- * An optional insert, update or delete URI parameter that allows the caller
+ * An optional URI parameter for insert, update, or delete queries
+ * 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)}.
+ * {@link RawContacts#DIRTY} is not automatically set and the
+ * "syncToNetwork" parameter is set to false when calling
+ * {@link
+ * ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ * This prevents an unnecessary extra synchronization, see the discussion of
+ * the delete operation in {@link RawContacts}.
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
@@ -117,6 +130,57 @@ public final class ContactsContract {
public static final String REQUESTING_PACKAGE_PARAM_KEY = "requesting_package";
/**
+ * @hide
+ */
+ public static final class Preferences {
+
+ /**
+ * A key in the {@link android.provider.Settings android.provider.Settings} provider
+ * that stores the preferred sorting order for contacts (by given name vs. by family name).
+ *
+ * @hide
+ */
+ public static final String SORT_ORDER = "android.contacts.SORT_ORDER";
+
+ /**
+ * The value for the SORT_ORDER key corresponding to sorting by given name first.
+ *
+ * @hide
+ */
+ public static final int SORT_ORDER_PRIMARY = 1;
+
+ /**
+ * The value for the SORT_ORDER key corresponding to sorting by family name first.
+ *
+ * @hide
+ */
+ public static final int SORT_ORDER_ALTERNATIVE = 2;
+
+ /**
+ * A key in the {@link android.provider.Settings android.provider.Settings} provider
+ * that stores the preferred display order for contacts (given name first vs. family
+ * name first).
+ *
+ * @hide
+ */
+ public static final String DISPLAY_ORDER = "android.contacts.DISPLAY_ORDER";
+
+ /**
+ * The value for the DISPLAY_ORDER key corresponding to showing the given name first.
+ *
+ * @hide
+ */
+ public static final int DISPLAY_ORDER_PRIMARY = 1;
+
+ /**
+ * The value for the DISPLAY_ORDER key corresponding to showing the family name first.
+ *
+ * @hide
+ */
+ public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
+ }
+
+ /**
* @hide should be removed when users are updated to refer to SyncState
* @deprecated use SyncState instead
*/
@@ -240,6 +304,9 @@ public final class ContactsContract {
}
/**
+ * Columns of {@link ContactsContract.Contacts} that track the user's
+ * preferences for, or interactions with, the contact.
+ *
* @see Contacts
* @see RawContacts
* @see ContactsContract.Data
@@ -266,20 +333,25 @@ public final class ContactsContract {
public static final String STARRED = "starred";
/**
- * A custom ringtone associated with a contact. Not always present.
+ * URI for a custom ringtone associated with the contact. If null or missing,
+ * the default ringtone is used.
* <P>Type: TEXT (URI to the ringtone)</P>
*/
public static final String CUSTOM_RINGTONE = "custom_ringtone";
/**
- * Whether the contact should always be sent to voicemail. Not always
- * present.
+ * Whether the contact should always be sent to voicemail. If missing,
+ * defaults to false.
* <P>Type: INTEGER (0 for false, 1 for true)</P>
*/
public static final String SEND_TO_VOICEMAIL = "send_to_voicemail";
}
/**
+ * Columns of {@link ContactsContract.Contacts} that refer to intrinsic
+ * properties of the contact, as opposed to the user-specified options
+ * found in {@link ContactOptionsColumns}.
+ *
* @see Contacts
* @see ContactsContract.Data
* @see PhoneLookup
@@ -290,7 +362,14 @@ public final class ContactsContract {
* The display name for the contact.
* <P>Type: TEXT</P>
*/
- public static final String DISPLAY_NAME = "display_name";
+ public static final String DISPLAY_NAME = ContactNameColumns.DISPLAY_NAME_PRIMARY;
+
+ /**
+ * Reference to the row in the RawContacts table holding the contact name.
+ * <P>Type: INTEGER REFERENCES raw_contacts(_id)</P>
+ * @hide
+ */
+ public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
/**
* Reference to the row in the data table holding the photo.
@@ -365,6 +444,209 @@ public final class ContactsContract {
}
/**
+ * Constants for various styles of combining given name, family name etc into
+ * a full name. For example, the western tradition follows the pattern
+ * 'given name' 'middle name' 'family name' with the alternative pattern being
+ * 'family name', 'given name' 'middle name'. The CJK tradition is
+ * 'family name' 'middle name' 'given name', with Japanese favoring a space between
+ * the names and Chinese omitting the space.
+ * @hide
+ */
+ public interface FullNameStyle {
+ public static final int UNDEFINED = 0;
+ public static final int WESTERN = 1;
+
+ /**
+ * Used if the name is written in Hanzi/Kanji/Hanja and we could not determine
+ * which specific language it belongs to: Chinese, Japanese or Korean.
+ */
+ public static final int CJK = 2;
+
+ public static final int CHINESE = 3;
+ public static final int JAPANESE = 4;
+ public static final int KOREAN = 5;
+ }
+
+ /**
+ * Constants for various styles of capturing the pronunciation of a person's name.
+ * @hide
+ */
+ public interface PhoneticNameStyle {
+ public static final int UNDEFINED = 0;
+
+ /**
+ * Pinyin is a phonetic method of entering Chinese characters. Typically not explicitly
+ * shown in UIs, but used for searches and sorting.
+ */
+ public static final int PINYIN = 3;
+
+ /**
+ * Hiragana and Katakana are two common styles of writing out the pronunciation
+ * of a Japanese names.
+ */
+ public static final int JAPANESE = 4;
+
+ /**
+ * Hangul is the Korean phonetic alphabet.
+ */
+ public static final int KOREAN = 5;
+ }
+
+ /**
+ * Types of data used to produce the display name for a contact. Listed in the order
+ * of increasing priority.
+ *
+ * @hide
+ */
+ public interface DisplayNameSources {
+ public static final int UNDEFINED = 0;
+ public static final int EMAIL = 10;
+ public static final int PHONE = 20;
+ public static final int ORGANIZATION = 30;
+ public static final int NICKNAME = 35;
+ public static final int STRUCTURED_NAME = 40;
+ }
+
+ /**
+ * Contact name and contact name metadata columns in the RawContacts table.
+ *
+ * @see Contacts
+ * @see RawContacts
+ * @hide
+ */
+ protected interface ContactNameColumns {
+
+ /**
+ * The kind of data that is used as the display name for the contact, such as
+ * structured name or email address. See DisplayNameSources.
+ *
+ * TODO: convert DisplayNameSources to a link after it is un-hidden
+ */
+ public static final String DISPLAY_NAME_SOURCE = "display_name_source";
+
+ /**
+ * <p>
+ * The standard text shown as the contact's display name, based on the best
+ * available information for the contact (for example, it might be the email address
+ * if the name is not available).
+ * The information actually used to compute the name is stored in
+ * {@link #DISPLAY_NAME_SOURCE}.
+ * </p>
+ * <p>
+ * A contacts provider is free to choose whatever representation makes most
+ * sense for its target market.
+ * For example in the default Android Open Source Project implementation,
+ * if the display name is
+ * based on the structured name and the structured name follows
+ * the Western full-name style, then this field contains the "given name first"
+ * version of the full name.
+ * <p>
+ *
+ * @see ContactsContract.ContactNameColumns#DISPLAY_NAME_ALTERNATIVE
+ */
+ public static final String DISPLAY_NAME_PRIMARY = "display_name";
+
+ /**
+ * <p>
+ * An alternative representation of the display name, such as "family name first"
+ * instead of "given name first" for Western names. If an alternative is not
+ * available, the values should be the same as {@link #DISPLAY_NAME_PRIMARY}.
+ * </p>
+ * <p>
+ * A contacts provider is free to provide alternatives as necessary for
+ * its target market.
+ * For example the default Android Open Source Project contacts provider
+ * currently provides an
+ * alternative in a single case: if the display name is
+ * based on the structured name and the structured name follows
+ * the Western full name style, then the field contains the "family name first"
+ * version of the full name.
+ * Other cases may be added later.
+ * </p>
+ */
+ public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt";
+
+ /**
+ * The phonetic alphabet used to represent the {@link #PHONETIC_NAME}. See
+ * PhoneticNameStyle.
+ *
+ * TODO: convert PhoneticNameStyle to a link after it is un-hidden
+ */
+ public static final String PHONETIC_NAME_STYLE = "phonetic_name_style";
+
+ /**
+ * <p>
+ * Pronunciation of the full name in the phonetic alphabet specified by
+ * {@link #PHONETIC_NAME_STYLE}.
+ * </p>
+ * <p>
+ * The value may be set manually by the user.
+ * This capability is is of interest only in countries
+ * with commonly used phonetic
+ * alphabets, such as Japan and Korea. See PhoneticNameStyle.
+ * </p>
+ *
+ * TODO: convert PhoneticNameStyle to a link after it is un-hidden
+ */
+ public static final String PHONETIC_NAME = "phonetic_name";
+
+ /**
+ * Sort key that takes into account locale-based traditions for sorting
+ * names in address books. The default
+ * sort key is {@link #DISPLAY_NAME_PRIMARY}. For Chinese names
+ * the sort key is the name's Pinyin spelling, and for Japanese names
+ * it is the Hiragana version of the phonetic name.
+ */
+ public static final String SORT_KEY_PRIMARY = "sort_key";
+
+ /**
+ * Sort key based on the alternative representation of the full name,
+ * {@link #DISPLAY_NAME_ALTERNATIVE}. Thus for Western names,
+ * it is the one using the "family name first" format.
+ */
+ public static final String SORT_KEY_ALTERNATIVE = "sort_key_alt";
+ }
+
+ /**
+ * URI parameter and cursor extras that return counts of rows grouped by the
+ * address book index, which is usually the first letter of the sort key.
+ * When this parameter is supplied, the row counts are returned in the
+ * cursor extras bundle.
+ *
+ * @hide
+ */
+ public final static class ContactCounts {
+
+ /**
+ * Add this query parameter to a URI to get back row counts grouped by
+ * the address book index as cursor extras. For most languages it is the
+ * first letter of the sort key. This parameter does not affect the main
+ * content of the cursor.
+ *
+ * @hide
+ */
+ public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
+
+ /**
+ * The array of address book index titles, which are returned in the
+ * same order as the data in the cursor.
+ * <p>TYPE: String[]</p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
+
+ /**
+ * The array of group counts for the corresponding group. Contains the same number
+ * of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
+ * <p>TYPE: int[]</p>
+ *
+ * @hide
+ */
+ public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
+ }
+
+ /**
* Constants for the contacts table, which contains a record per aggregate
* of raw contacts representing the same person.
* <h3>Operations</h3>
@@ -423,13 +705,21 @@ public final class ContactsContract {
* row id changed as a result of a sync or aggregation.</td>
* </tr>
* <tr>
+ * <td>long</td>
+ * <td>NAME_RAW_CONTACT_ID</td>
+ * <td>read-only</td>
+ * <td>The ID of the raw contact that contributes the display name
+ * to the aggregate contact. During aggregation one of the constituent
+ * raw contacts is chosen using a heuristic: a longer name or a name
+ * with more diacritic marks or more upper case characters is chosen.</td>
+ * </tr>
+ * <tr>
* <td>String</td>
- * <td>{@link #DISPLAY_NAME}</td>
+ * <td>DISPLAY_NAME_PRIMARY</td>
* <td>read-only</td>
- * <td>The display name for the contact. During aggregation display name is
- * computed from display names of constituent raw contacts using a
- * heuristic: a longer name or a name with more diacritic marks or more
- * upper case characters is chosen.</td>
+ * <td>The display name for the contact. It is the display name
+ * contributed by the raw contact referred to by the NAME_RAW_CONTACT_ID
+ * column.</td>
* </tr>
* <tr>
* <td>long</td>
@@ -555,7 +845,7 @@ public final class ContactsContract {
* </table>
*/
public static class Contacts implements BaseColumns, ContactsColumns,
- ContactOptionsColumns, ContactStatusColumns {
+ ContactOptionsColumns, ContactNameColumns, ContactStatusColumns {
/**
* This utility class cannot be instantiated
*/
@@ -597,6 +887,25 @@ public final class ContactsContract {
"as_vcard");
/**
+ * 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
+ * has to be encoded again. Provides
+ * {@link OpenableColumns} columns when queried, or returns the
+ * referenced contact formatted as a vCard when opened through
+ * {@link ContentResolver#openAssetFileDescriptor(Uri, String)}.
+ *
+ * This is private API because we do not have a well-defined way to
+ * specify several entities yet. The format of this Uri might change in the future
+ * or the Uri might be completely removed.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_MULTI_VCARD_URI = Uri.withAppendedPath(CONTENT_URI,
+ "as_multi_vcard");
+
+ /**
* Builds a {@link #CONTENT_LOOKUP_URI} style {@link Uri} describing the
* requested {@link Contacts} entry.
*
@@ -659,7 +968,10 @@ public final class ContactsContract {
}
/**
- * Mark a contact as having been contacted.
+ * Mark a contact as having been contacted. This updates the
+ * {@link #TIMES_CONTACTED} and {@link #LAST_TIME_CONTACTED} for the
+ * contact, plus the corresponding values of any associated raw
+ * contacts.
*
* @param resolver the ContentResolver to use
* @param contactId the person who was contacted
@@ -898,18 +1210,67 @@ public final class ContactsContract {
* <P>Type: INTEGER</P>
*/
public static final String DELETED = "deleted";
+
+ /**
+ * The "name_verified" flag: "1" means that the name fields on this raw
+ * contact can be trusted and therefore should be used for the entire
+ * aggregated contact.
+ * <p>
+ * If an aggregated contact contains more than one raw contact with a
+ * verified name, one of those verified names is chosen at random.
+ * If an aggregated contact contains no verified names, the
+ * name is chosen randomly from the constituent raw contacts.
+ * </p>
+ * <p>
+ * Updating this flag from "0" to "1" automatically resets it to "0" on
+ * all other raw contacts in the same aggregated contact.
+ * </p>
+ * <p>
+ * Sync adapters should only specify a value for this column when
+ * inserting a raw contact and leave it out when doing an update.
+ * </p>
+ * <p>
+ * The default value is "0"
+ * </p>
+ * <p>Type: INTEGER</p>
+ *
+ * @hide
+ */
+ public static final String NAME_VERIFIED = "name_verified";
}
/**
- * Constants for the raw contacts table, which contains the base contact
- * information per sync source. Sync adapters and contact management apps
+ * Constants for the raw contacts table, which contains one row of contact
+ * information for each person in each synced account. Sync adapters and
+ * contact management apps
* are the primary consumers of this API.
+ *
+ * <h3>Aggregation</h3>
+ * <p>
+ * As soon as a raw contact is inserted or whenever its constituent data
+ * changes, the provider will check if the raw contact matches other
+ * existing raw contacts and if so will aggregate it with those. The
+ * aggregation is reflected in the {@link RawContacts} table by the change of the
+ * {@link #CONTACT_ID} field, which is the reference to the aggregate contact.
+ * </p>
+ * <p>
+ * Changes to the structured name, organization, phone number, email address,
+ * or nickname trigger a re-aggregation.
+ * </p>
+ * <p>
+ * See also {@link AggregationExceptions} for a mechanism to control
+ * aggregation programmatically.
+ * </p>
+ *
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
- * <dd>There are two mechanisms that can be used to insert a raw contact: incremental and
- * batch. The incremental method is more traditional but less efficient. It should be used
- * only if the constituent data rows are unavailable at the time the raw contact is created:
+ * <dd>
+ * <p>
+ * Raw contacts can be inserted incrementally or in a batch.
+ * The incremental method is more traditional but less efficient.
+ * It should be used
+ * only if no {@link Data} values are available at the time the raw contact is created:
* <pre>
* ContentValues values = new ContentValues();
* values.put(RawContacts.ACCOUNT_TYPE, accountType);
@@ -917,9 +1278,10 @@ public final class ContactsContract {
* Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);
* long rawContactId = ContentUris.parseId(rawContactUri);
* </pre>
+ * </p>
* <p>
- * Once data rows are available, insert those. For example, here's how you would insert
- * a name:
+ * Once {@link Data} values become available, insert those.
+ * For example, here's how you would insert a name:
*
* <pre>
* values.clear();
@@ -935,6 +1297,7 @@ public final class ContactsContract {
* and causes at most one aggregation pass.
* <pre>
* ArrayList&lt;ContentProviderOperation&gt; ops = Lists.newArrayList();
+ * ...
* int rawContactInsertIndex = ops.size();
* ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
* .withValue(RawContacts.ACCOUNT_TYPE, accountType)
@@ -951,34 +1314,41 @@ public final class ContactsContract {
* </pre>
* </p>
* <p>
- * Please note the use of back reference in the construction of the
- * {@link ContentProviderOperation}. It allows an operation to use the result of
- * a previous operation by referring to it by its index in the batch.
+ * Note the use of {@link ContentProviderOperation.Builder#withValueBackReference(String, int)}
+ * to refer to the as-yet-unknown index value of the raw contact inserted in the
+ * first operation.
* </p>
+ *
* <dt><b>Update</b></dt>
- * <dd><p>Just as with insert, the update can be done incrementally or as a batch, the
- * batch mode being the preferred method.</p></dd>
+ * <dd><p>
+ * Raw contacts can be updated incrementally or in a batch.
+ * Batch mode should be used whenever possible.
+ * The procedures and considerations are analogous to those documented above for inserts.
+ * </p></dd>
* <dt><b>Delete</b></dt>
* <dd><p>When a raw contact is deleted, all of its Data rows as well as StatusUpdates,
* AggregationExceptions, PhoneLookup rows are deleted automatically. When all raw
- * contacts in a Contact are deleted, the Contact itself is also deleted automatically.
+ * contacts associated with a {@link Contacts} row are deleted, the {@link Contacts} row
+ * itself is also deleted automatically.
* </p>
* <p>
- * The invocation of {@code resolver.delete(...)}, does not physically delete
- * a raw contacts row. It sets the {@link #DELETED} flag on the raw contact and
+ * The invocation of {@code resolver.delete(...)}, does not immediately delete
+ * a raw contacts row.
+ * Instead, it sets the {@link #DELETED} flag on the raw contact and
* removes the raw contact from its aggregate contact.
* The sync adapter then deletes the raw contact from the server and
* finalizes phone-side deletion by calling {@code resolver.delete(...)}
- * again and passing the {@link #CALLER_IS_SYNCADAPTER} query parameter.<p>
+ * again and passing the {@link ContactsContract#CALLER_IS_SYNCADAPTER} query parameter.<p>
* <p>Some sync adapters are read-only, meaning that they only sync server-side
* changes to the phone, but not the reverse. If one of those raw contacts
* is marked for deletion, it will remain on the phone. However it will be
* effectively invisible, because it will not be part of any aggregate contact.
* </dd>
+ *
* <dt><b>Query</b></dt>
* <dd>
* <p>
- * Finding all raw contacts in a Contact is easy:
+ * It is easy to find all raw contacts in a Contact:
* <pre>
* Cursor c = getContentResolver().query(RawContacts.CONTENT_URI,
* new String[]{RawContacts._ID},
@@ -987,7 +1357,7 @@ public final class ContactsContract {
* </pre>
* </p>
* <p>
- * There are two ways to find raw contacts within a specific account,
+ * To find raw contacts within a specific account,
* you can either put the account name and type in the selection or pass them as query
* parameters. The latter approach is preferable, especially when you can reuse the
* URI:
@@ -1029,19 +1399,8 @@ public final class ContactsContract {
* </p>
* </dd>
* </dl>
- * <h3>Aggregation</h3>
- * <p>
- * As soon as a raw contact is inserted or whenever its constituent data
- * changes, the provider will check if the raw contact matches other
- * existing raw contacts and if so will aggregate it with those. From the
- * data standpoint, aggregation is reflected in the change of the
- * {@link #CONTACT_ID} field, which is the reference to the aggregate contact.
- * </p>
- * <p>
- * See also {@link AggregationExceptions} for a mechanism to control
- * aggregation programmatically.
- * </p>
* <h2>Columns</h2>
+ *
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>RawContacts</th>
@@ -1050,15 +1409,16 @@ public final class ContactsContract {
* <td>long</td>
* <td>{@link #_ID}</td>
* <td>read-only</td>
- * <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words,
- * it would be a really bad idea to delete and reinsert a raw contact. A sync adapter should
- * always do an update instead.</td>
+ * <td>Row ID. Sync adapters should try to preserve row IDs during updates. In other words,
+ * it is much better for a sync adapter to update a raw contact rather than to delete and
+ * re-insert it.</td>
* </tr>
* <tr>
* <td>long</td>
* <td>{@link #CONTACT_ID}</td>
* <td>read-only</td>
- * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this raw contact belongs
+ * <td>The ID of the row in the {@link ContactsContract.Contacts} table
+ * that this raw contact belongs
* to. Raw contacts are linked to contacts by the aggregation process, which can be controlled
* by the {@link #AGGREGATION_MODE} field and {@link AggregationExceptions}.</td>
* </tr>
@@ -1089,7 +1449,8 @@ public final class ContactsContract {
* <td>The number of times the contact has been contacted. To have an effect
* on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted.
- * See {@link ContactsContract.Contacts#markAsContacted}.</td>
+ * After that, this value is typically updated via
+ * {@link ContactsContract.Contacts#markAsContacted}.</td>
* </tr>
* <tr>
* <td>long</td>
@@ -1098,14 +1459,16 @@ public final class ContactsContract {
* <td>The timestamp of the last time the contact was contacted. To have an effect
* on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted.
- * See {@link ContactsContract.Contacts#markAsContacted}.</td>
+ * After that, this value is typically updated via
+ * {@link ContactsContract.Contacts#markAsContacted}.
+ * </td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #STARRED}</td>
* <td>read/write</td>
* <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise.
- * Changing this field immediately effects the corresponding aggregate contact:
+ * Changing this field immediately affects the corresponding aggregate contact:
* if any raw contacts in that aggregate contact are starred, then the contact
* itself is marked as starred.</td>
* </tr>
@@ -1118,7 +1481,8 @@ public final class ContactsContract {
* {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent.
* To have an effect on the corresponding value of the aggregate contact, this field
* should be set at the time the raw contact is inserted. To set a custom
- * ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE}
+ * ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE
+ * Contacts.CUSTOM_RINGTONE}
* instead.</td>
* </tr>
* <tr>
@@ -1135,16 +1499,27 @@ public final class ContactsContract {
* <td>{@link #ACCOUNT_NAME}</td>
* <td>read/write-once</td>
* <td>The name of the account instance to which this row belongs, which when paired with
- * {@link #ACCOUNT_TYPE} identifies a specific account. It should be set at the time
+ * {@link #ACCOUNT_TYPE} identifies a specific account.
+ * For example, this will be the Gmail address if it is a Google account.
+ * It should be set at the time
* the raw contact is inserted and never changed afterwards.</td>
* </tr>
* <tr>
* <td>String</td>
* <td>{@link #ACCOUNT_TYPE}</td>
* <td>read/write-once</td>
- * <td>The type of account to which this row belongs, which when paired with
- * {@link #ACCOUNT_NAME} identifies a specific account. It should be set at the time
- * the raw contact is inserted and never changed afterwards.</td>
+ * <td>
+ * <p>
+ * The type of account to which this row belongs, which when paired with
+ * {@link #ACCOUNT_NAME} identifies a specific account.
+ * It should be set at the time
+ * the raw contact is inserted and never changed afterwards.
+ * </p>
+ * <p>
+ * To ensure uniqueness, new account types should be chosen according to the
+ * Java package naming convention. Thus a Google account is of type "com.google".
+ * </p>
+ * </td>
* </tr>
* <tr>
* <td>String</td>
@@ -1153,8 +1528,8 @@ public final class ContactsContract {
* <td>String that uniquely identifies this row to its source account.
* Typically it is set at the time the raw contact is inserted and never
* changed afterwards. The one notable exception is a new raw contact: it
- * will have an account name and type, but no source id. This should
- * indicated to the sync adapter that a new contact needs to be created
+ * will have an account name and type, but no source id. This
+ * indicates to the sync adapter that a new contact needs to be created
* server-side and its ID stored in the corresponding SOURCE_ID field on
* the phone.
* </td>
@@ -1186,7 +1561,8 @@ public final class ContactsContract {
* <td>String</td>
* <td>{@link #SYNC1}</td>
* <td>read/write</td>
- * <td>Generic column for use by sync adapters. Content provider
+ * <td>Generic column provided for arbitrary use by sync adapters.
+ * The content provider
* stores this information on behalf of the sync adapter but does not
* interpret it in any way.
* </td>
@@ -1215,7 +1591,7 @@ public final class ContactsContract {
* </table>
*/
public static final class RawContacts implements BaseColumns, RawContactsColumns,
- ContactOptionsColumns, SyncColumns {
+ ContactOptionsColumns, ContactNameColumns, SyncColumns {
/**
* This utility class cannot be instantiated
*/
@@ -1223,46 +1599,69 @@ public final class ContactsContract {
}
/**
- * The content:// style URI for this table
+ * The content:// style URI for this table, which requests a directory of
+ * raw contact rows matching the selection criteria.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts");
/**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
+ * The MIME type of the results from {@link #CONTENT_URI} when a specific
+ * ID value is not provided, and multiple raw contacts may be returned.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/raw_contact";
/**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
+ * The MIME type of the results when a raw contact ID is appended to {@link #CONTENT_URI},
+ * yielding a subdirectory of a single person.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/raw_contact";
/**
- * Aggregation mode: aggregate asynchronously.
+ * Aggregation mode: aggregate immediately after insert or update operation(s) are complete.
*/
public static final int AGGREGATION_MODE_DEFAULT = 0;
/**
- * Aggregation mode: aggregate at the time the raw contact is inserted/updated.
- * TODO: deprecate. Aggregation is now synchronous, this value is a no-op
+ * Do not use.
+ *
+ * TODO: deprecate in favor of {@link #AGGREGATION_MODE_DEFAULT}
*/
public static final int AGGREGATION_MODE_IMMEDIATE = 1;
/**
- * If {@link #AGGREGATION_MODE} is {@link #AGGREGATION_MODE_SUSPENDED}, changes
- * to the raw contact do not cause its aggregation to be revisited. Note that changing
+ * <p>
+ * Aggregation mode: aggregation suspended temporarily, and is likely to be resumed later.
+ * Changes to the raw contact will update the associated aggregate contact but will not
+ * result in any change in how the contact is aggregated. Similar to
+ * {@link #AGGREGATION_MODE_DISABLED}, but maintains a link to the corresponding
+ * {@link Contacts} aggregate.
+ * </p>
+ * <p>
+ * This can be used to postpone aggregation until after a series of updates, for better
+ * performance and/or user experience.
+ * </p>
+ * <p>
+ * Note that changing
* {@link #AGGREGATION_MODE} from {@link #AGGREGATION_MODE_SUSPENDED} to
- * {@link #AGGREGATION_MODE_DEFAULT} does not trigger an aggregation pass. Any subsequent
+ * {@link #AGGREGATION_MODE_DEFAULT} does not trigger an aggregation pass, but any
+ * subsequent
* change to the raw contact's data will.
+ * </p>
*/
public static final int AGGREGATION_MODE_SUSPENDED = 2;
/**
- * Aggregation mode: never aggregate this raw contact (note that the raw contact will not
- * have a corresponding Aggregate and therefore will not be included in Aggregates
- * query results.)
+ * <p>
+ * Aggregation mode: never aggregate this raw contact. The raw contact will not
+ * have a corresponding {@link Contacts} aggregate and therefore will not be included in
+ * {@link Contacts} query results.
+ * </p>
+ * <p>
+ * For example, this mode can be used for a raw contact that is marked for deletion while
+ * waiting for the deletion to occur on the server side.
+ * </p>
+ *
+ * @see #AGGREGATION_MODE_SUSPENDED
*/
public static final int AGGREGATION_MODE_DISABLED = 3;
@@ -1292,9 +1691,11 @@ public final class ContactsContract {
}
/**
- * A sub-directory of a single raw contact that contains all of their
+ * A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.Data} rows. To access this directory
* append {@link Data#CONTENT_DIRECTORY} to the contact URI.
+ *
+ * TODO: deprecate in favor of {@link RawContacts.Entity}.
*/
public static final class Data implements BaseColumns, DataColumns {
/**
@@ -1311,26 +1712,24 @@ public final class ContactsContract {
/**
* <p>
- * A sub-directory of a single raw contact that contains all of their
+ * A sub-directory of a single raw contact that contains all of its
* {@link ContactsContract.Data} rows. To access this directory append
- * {@link Entity#CONTENT_DIRECTORY} to the contact URI. See
+ * {@link #CONTENT_DIRECTORY} to the contact URI. See
* {@link RawContactsEntity} for a stand-alone table containing the same
* data.
* </p>
* <p>
- * The Entity directory is similar to the {@link RawContacts.Data}
- * directory but with two important differences:
- * <ul>
- * <li>Entity has different ID fields: {@link #_ID} for the raw contact
- * and {@link #DATA_ID} for the data rows.</li>
- * <li>Entity always contains at least one row, even if there are no
+ * Entity has two ID fields: {@link #_ID} for the raw contact
+ * and {@link #DATA_ID} for the data rows.
+ * Entity always contains at least one row, even if there are no
* actual data rows. In this case the {@link #DATA_ID} field will be
- * null.</li>
- * </ul>
- * Using Entity should preferred to using two separate queries:
- * RawContacts followed by Data. The reason is that Entity reads all
- * data for a raw contact in one transaction, so there is no possibility
- * of the data changing between the two queries.
+ * null.
+ * </p>
+ * <p>
+ * Entity reads all
+ * data for a raw contact in one transaction, to guarantee
+ * consistency.
+ * </p>
*/
public static final class Entity implements BaseColumns, DataColumns {
/**
@@ -1351,6 +1750,118 @@ public final class ContactsContract {
*/
public static final String DATA_ID = "data_id";
}
+
+ /**
+ * TODO: javadoc
+ * @param cursor
+ * @return
+ */
+ public static EntityIterator newEntityIterator(Cursor cursor) {
+ return new EntityIteratorImpl(cursor);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ private static final String[] DATA_KEYS = new String[]{
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15,
+ Data.SYNC1,
+ Data.SYNC2,
+ Data.SYNC3,
+ Data.SYNC4};
+
+ public EntityIteratorImpl(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public android.content.Entity getEntityAndIncrementCursor(Cursor cursor)
+ throws RemoteException {
+ final int columnRawContactId = cursor.getColumnIndexOrThrow(RawContacts._ID);
+ final long rawContactId = cursor.getLong(columnRawContactId);
+
+ // we expect the cursor is already at the row we need to read from
+ ContentValues cv = new ContentValues();
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_NAME);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ACCOUNT_TYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC4);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, IS_RESTRICTED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED);
+ android.content.Entity contact = new android.content.Entity(cv);
+
+ // read data rows until the contact id changes
+ do {
+ if (rawContactId != cursor.getLong(columnRawContactId)) {
+ break;
+ }
+ // add the data to to the contact
+ cv = new ContentValues();
+ cv.put(Data._ID, cursor.getLong(cursor.getColumnIndexOrThrow(Entity.DATA_ID)));
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Data.RES_PACKAGE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Data.MIMETYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.IS_PRIMARY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv,
+ Data.IS_SUPER_PRIMARY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, Data.DATA_VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Data.DATA_VERSION);
+ for (String key : DATA_KEYS) {
+ final int columnIndex = cursor.getColumnIndexOrThrow(key);
+ if (cursor.isNull(columnIndex)) {
+ // don't put anything
+ } else {
+ try {
+ cv.put(key, cursor.getString(columnIndex));
+ } catch (SQLiteException e) {
+ cv.put(key, cursor.getBlob(columnIndex));
+ }
+ }
+ // TODO: go back to this version of the code when bug
+ // http://b/issue?id=2306370 is fixed.
+// if (cursor.isNull(columnIndex)) {
+// // don't put anything
+// } else if (cursor.isLong(columnIndex)) {
+// values.put(key, cursor.getLong(columnIndex));
+// } else if (cursor.isFloat(columnIndex)) {
+// values.put(key, cursor.getFloat(columnIndex));
+// } else if (cursor.isString(columnIndex)) {
+// values.put(key, cursor.getString(columnIndex));
+// } else if (cursor.isBlob(columnIndex)) {
+// values.put(key, cursor.getBlob(columnIndex));
+// }
+ }
+ contact.addSubValue(ContactsContract.Data.CONTENT_URI, cv);
+ } while (cursor.moveToNext());
+
+ return contact;
+ }
+
+ }
}
/**
@@ -1359,18 +1870,60 @@ public final class ContactsContract {
* @see StatusUpdates
* @see ContactsContract.Data
*/
- protected interface StatusColumns extends Im.CommonPresenceColumns {
+ protected interface StatusColumns {
/**
* Contact's latest presence level.
* <P>Type: INTEGER (one of the values below)</P>
*/
- public static final String PRESENCE = PRESENCE_STATUS;
+ public static final String PRESENCE = "mode";
+
+ /**
+ * @deprecated use {@link #PRESENCE}
+ */
+ @Deprecated
+ public static final String PRESENCE_STATUS = PRESENCE;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int OFFLINE = 0;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int INVISIBLE = 1;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int AWAY = 2;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int IDLE = 3;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int DO_NOT_DISTURB = 4;
+
+ /**
+ * An allowed value of {@link #PRESENCE}.
+ */
+ int AVAILABLE = 5;
/**
* Contact latest status update.
* <p>Type: TEXT</p>
*/
- public static final String STATUS = PRESENCE_CUSTOM_STATUS;
+ public static final String STATUS = "status";
+
+ /**
+ * @deprecated use {@link #STATUS}
+ */
+ @Deprecated
+ public static final String PRESENCE_CUSTOM_STATUS = STATUS;
/**
* The absolute time in milliseconds when the latest status was inserted/updated.
@@ -1426,7 +1979,7 @@ public final class ContactsContract {
public static final String RAW_CONTACT_ID = "raw_contact_id";
/**
- * Whether this is the primary entry of its kind for the raw contact it belongs to
+ * Whether this is the primary entry of its kind for the raw contact it belongs to.
* <P>Type: INTEGER (if set, non-0 means true)</P>
*/
public static final String IS_PRIMARY = "is_primary";
@@ -1475,7 +2028,10 @@ public final class ContactsContract {
public static final String DATA13 = "data13";
/** Generic data column, the meaning is {@link #MIMETYPE} specific */
public static final String DATA14 = "data14";
- /** Generic data column, the meaning is {@link #MIMETYPE} specific */
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific. By convention,
+ * this field is used to store BLOBs (binary data).
+ */
public static final String DATA15 = "data15";
/** Generic column for use by sync adapters. */
@@ -1494,30 +2050,35 @@ public final class ContactsContract {
* @see ContactsContract.Data
*/
protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns,
- RawContactsColumns, ContactsColumns, ContactOptionsColumns, ContactStatusColumns {
-
+ RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns,
+ ContactStatusColumns {
}
/**
* <p>
* Constants for the data table, which contains data points tied to a raw
- * contact. For example, a phone number or email address.
+ * contact. Each row of the data table is typically used to store a single
+ * piece of contact
+ * information (such as a phone number) and its
+ * associated metadata (such as whether it is a work or home number).
* </p>
* <h3>Data kinds</h3>
* <p>
- * Data is a generic table that can hold all kinds of data. Sync adapters
- * and applications can introduce their own data kinds. The kind of data
- * stored in a particular row is determined by the mime type in the row.
- * Fields from {@link #DATA1} through {@link #DATA15} are generic columns
- * whose specific use is determined by the kind of data stored in the row.
+ * Data is a generic table that can hold any kind of contact data.
+ * The kind of data stored in a given row is specified by the row's
+ * {@link #MIMETYPE} value, which determines the meaning of the
+ * generic columns {@link #DATA1} through
+ * {@link #DATA15}.
* For example, if the data kind is
- * {@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}, then DATA1 stores the
+ * {@link CommonDataKinds.Phone Phone.CONTENT_ITEM_TYPE}, then the column
+ * {@link #DATA1} stores the
* phone number, but if the data kind is
- * {@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}, then DATA1 stores the
- * email address.
+ * {@link CommonDataKinds.Email Email.CONTENT_ITEM_TYPE}, then {@link #DATA1}
+ * stores the email address.
+ * Sync adapters and applications can introduce their own data kinds.
* </p>
* <p>
- * ContactsContract defines a small number of common data kinds, e.g.
+ * ContactsContract defines a small number of pre-defined data kinds, e.g.
* {@link CommonDataKinds.Phone}, {@link CommonDataKinds.Email} etc. As a
* convenience, these classes define data kind specific aliases for DATA1 etc.
* For example, {@link CommonDataKinds.Phone Phone.NUMBER} is the same as
@@ -1534,8 +2095,13 @@ public final class ContactsContract {
* By convention, {@link #DATA15} is used for storing BLOBs (binary data).
* </p>
* <p>
- * Typically you should refrain from introducing new kinds of data for 3rd
- * party account types. For example, if you add a data row for
+ * The sync adapter for a given account type must correctly handle every data type
+ * used in the corresponding raw contacts. Otherwise it could result in lost or
+ * corrupted data.
+ * </p>
+ * <p>
+ * Similarly, you should refrain from introducing new kinds of data for an other
+ * party's account types. For example, if you add a data row for
* "favorite song" to a raw contact owned by a Google account, it will not
* get synced to the server, because the Google sync adapter does not know
* how to handle this data kind. Thus new data kinds are typically
@@ -1672,6 +2238,10 @@ public final class ContactsContract {
* </dd>
* </dl>
* <h2>Columns</h2>
+ * <p>
+ * Many columns are available via a {@link Data#CONTENT_URI} query. For best performance you
+ * should explicitly specify a projection to only those columns that you need.
+ * </p>
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>Data</th>
@@ -1681,7 +2251,7 @@ public final class ContactsContract {
* <td style="width: 20em;">{@link #_ID}</td>
* <td style="width: 5em;">read-only</td>
* <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words,
- * it would be a bad idea to delete and reinsert a data rows. A sync adapter should
+ * it would be a bad idea to delete and reinsert a data row. A sync adapter should
* always do an update instead.</td>
* </tr>
* <tr>
@@ -1713,21 +2283,15 @@ public final class ContactsContract {
* <td>long</td>
* <td>{@link #RAW_CONTACT_ID}</td>
* <td>read/write-once</td>
- * <td>A reference to the {@link RawContacts#_ID} that this data belongs to.</td>
- * </tr>
- * <tr>
- * <td>long</td>
- * <td>{@link #CONTACT_ID}</td>
- * <td>read-only</td>
- * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this data row belongs
- * to. It is obtained through a join with RawContacts.</td>
+ * <td>The id of the row in the {@link RawContacts} table that this data belongs to.</td>
* </tr>
* <tr>
* <td>int</td>
* <td>{@link #IS_PRIMARY}</td>
* <td>read/write</td>
* <td>Whether this is the primary entry of its kind for the raw contact it belongs to.
- * "1" if true, "0" if false.</td>
+ * "1" if true, "0" if false.
+ * </td>
* </tr>
* <tr>
* <td>int</td>
@@ -1735,7 +2299,9 @@ public final class ContactsContract {
* <td>read/write</td>
* <td>Whether this is the primary entry of its kind for the aggregate
* contact it belongs to. Any data record that is "super primary" must
- * also be "primary".</td>
+ * also be "primary". For example, the super-primary entry may be
+ * interpreted as the default contact value of its kind (for example,
+ * the default phone number to use for the contact).</td>
* </tr>
* <tr>
* <td>int</td>
@@ -1764,7 +2330,19 @@ public final class ContactsContract {
* {@link #DATA15}
* </td>
* <td>read/write</td>
- * <td>Generic data columns, the meaning is {@link #MIMETYPE} specific.</td>
+ * <td>
+ * <p>
+ * Generic data columns. The meaning of each column is determined by the
+ * {@link #MIMETYPE}. By convention, {@link #DATA15} is used for storing
+ * BLOBs (binary data).
+ * </p>
+ * <p>
+ * Data columns whose meaning is not explicitly defined for a given MIMETYPE
+ * should not be used. There is no guarantee that any sync adapter will
+ * preserve them. Sync adapters themselves should not use such columns either,
+ * but should instead use {@link #SYNC1}-{@link #SYNC4}.
+ * </p>
+ * </td>
* </tr>
* <tr>
* <td>Any type</td>
@@ -1781,6 +2359,10 @@ public final class ContactsContract {
* </tr>
* </table>
*
+ * <p>
+ * Some columns from the most recent associated status update are also available
+ * through an implicit join.
+ * </p>
* <table class="jd-sumtable">
* <tr>
* <th colspan='4'>Join with {@link StatusUpdates}</th>
@@ -1833,18 +2415,26 @@ public final class ContactsContract {
* </table>
*
* <p>
- * Columns from the associated raw contact are also available through an
- * implicit join.
+ * Some columns from the associated raw contact are also available through an
+ * implicit join. The other columns are excluded as uninteresting in this
+ * context.
* </p>
*
* <table class="jd-sumtable">
* <tr>
- * <th colspan='4'>Join with {@link RawContacts}</th>
+ * <th colspan='4'>Join with {@link ContactsContract.RawContacts}</th>
* </tr>
* <tr>
- * <td style="width: 7em;">int</td>
- * <td style="width: 20em;">{@link #AGGREGATION_MODE}</td>
+ * <td style="width: 7em;">long</td>
+ * <td style="width: 20em;">{@link #CONTACT_ID}</td>
* <td style="width: 5em;">read-only</td>
+ * <td>The id of the row in the {@link Contacts} table that this data belongs
+ * to.</td>
+ * </tr>
+ * <tr>
+ * <td>int</td>
+ * <td>{@link #AGGREGATION_MODE}</td>
+ * <td>read-only</td>
* <td>See {@link RawContacts}.</td>
* </tr>
* <tr>
@@ -1856,13 +2446,18 @@ public final class ContactsContract {
* </table>
*
* <p>
- * Columns from the associated aggregated contact are also available through an
- * implicit join.
+ * The ID column for the associated aggregated contact table
+ * {@link ContactsContract.Contacts} is available
+ * via the implicit join to the {@link RawContacts} table, see above.
+ * The remaining columns from this table are also
+ * available, through an implicit join. This
+ * facilitates lookup by
+ * the value of a single data element, such as the email address.
* </p>
*
* <table class="jd-sumtable">
* <tr>
- * <th colspan='4'>Join with {@link Contacts}</th>
+ * <th colspan='4'>Join with {@link ContactsContract.Contacts}</th>
* </tr>
* <tr>
* <td style="width: 7em;">String</td>
@@ -1969,24 +2564,30 @@ public final class ContactsContract {
private Data() {}
/**
- * The content:// style URI for this table
+ * The content:// style URI for this table, which requests a directory
+ * of data rows matching the selection criteria.
*/
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
/**
- * The MIME type of {@link #CONTENT_URI} providing a directory of data.
+ * The MIME type of the results from {@link #CONTENT_URI}.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/data";
/**
+ * <p>
* If {@link #FOR_EXPORT_ONLY} is explicitly set to "1", returned Cursor toward
* Data.CONTENT_URI contains only exportable data.
- *
+ * </p>
+ * <p>
* This flag is useful (currently) only for vCard exporter in Contacts app, which
* needs to exclude "un-exportable" data from available data to export, while
* Contacts app itself has priviledge to access all data including "un-exportable"
* ones and providers return all of them regardless of the callers' intention.
- * <P>Type: INTEGER</p>
+ * </p>
+ * <p>
+ * Type: INTEGER
+ * </p>
*
* @hide Maybe available only in Eclair and not really ready for public use.
* TODO: remove, or implement this feature completely. As of now (Eclair),
@@ -1995,9 +2596,17 @@ public final class ContactsContract {
public static final String FOR_EXPORT_ONLY = "for_export_only";
/**
+ * <p>
* Build a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}
* style {@link Uri} for the parent {@link android.provider.ContactsContract.Contacts}
* entry of the given {@link ContactsContract.Data} entry.
+ * </p>
+ * <p>
+ * Returns the Uri for the contact in the first entry returned by
+ * {@link ContentResolver#query(Uri, String[], String, String[], String)}
+ * for the provided {@code dataUri}. If the query returns null or empty
+ * results, silently returns null.
+ * </p>
*/
public static Uri getContactLookupUri(ContentResolver resolver, Uri dataUri) {
final Cursor cursor = resolver.query(dataUri, new String[] {
@@ -2020,7 +2629,7 @@ public final class ContactsContract {
/**
* <p>
- * Constants for the raw contacts entities table, which can be though of as
+ * Constants for the raw contacts entities table, which can be thought of as
* an outer join of the raw_contacts table with the data table. It is a strictly
* read-only table.
* </p>
@@ -2345,6 +2954,13 @@ public final class ContactsContract {
*/
public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(AUTHORITY_URI,
"phone_lookup");
+
+ /**
+ * The MIME type of {@link #CONTENT_FILTER_URI} providing a directory of phone lookup rows.
+ *
+ * @hide
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup";
}
/**
@@ -2586,6 +3202,61 @@ public final class ContactsContract {
}
/**
+ * Additional columns returned by the {@link Contacts#CONTENT_FILTER_URI} providing the
+ * explanation of why the filter matched the contact. Specifically, they contain the
+ * data type and element that was used for matching.
+ * <p>
+ * This is temporary API, it will need to change when we move to FTS.
+ *
+ * @hide
+ */
+ public static class SearchSnippetColumns {
+
+ /**
+ * The ID of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA_ID = "snippet_data_id";
+
+ /**
+ * The type of data that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_MIMETYPE = "snippet_mimetype";
+
+ /**
+ * The {@link Data#DATA1} field of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA1 = "snippet_data1";
+
+ /**
+ * The {@link Data#DATA2} field of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA2 = "snippet_data2";
+
+ /**
+ * The {@link Data#DATA3} field of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA3 = "snippet_data3";
+
+ /**
+ * The {@link Data#DATA4} field of the data row that was matched by the filter.
+ *
+ * @hide
+ */
+ public static final String SNIPPET_DATA4 = "snippet_data4";
+
+ }
+
+ /**
* Container for definitions of common data types stored in the {@link ContactsContract.Data}
* table.
*/
@@ -2765,6 +3436,21 @@ public final class ContactsContract {
* <P>Type: TEXT</P>
*/
public static final String PHONETIC_FAMILY_NAME = DATA9;
+
+ /**
+ * The style used for combining given/middle/family name into a full name.
+ * See {@link ContactsContract.FullNameStyle}.
+ *
+ * @hide
+ */
+ public static final String FULL_NAME_STYLE = DATA10;
+
+ /**
+ * The alphabet used for capturing the phonetic name.
+ * See ContactsContract.PhoneticNameStyle.
+ * @hide
+ */
+ public static final String PHONETIC_NAME_STYLE = DATA11;
}
/**
@@ -3644,6 +4330,12 @@ public final class ContactsContract {
* <td>{@link #DATA9}</td>
* <td></td>
* </tr>
+ * <tr>
+ * <td>String</td>
+ * <td>PHONETIC_NAME_STYLE</td>
+ * <td>{@link #DATA10}</td>
+ * <td></td>
+ * </tr>
* </table>
*/
public static final class Organization implements DataColumnsWithJoins, CommonColumns {
@@ -3701,6 +4393,13 @@ public final class ContactsContract {
public static final String OFFICE_LOCATION = DATA9;
/**
+ * The alphabet used for capturing the phonetic name.
+ * See {@link ContactsContract.PhoneticNameStyle}.
+ * @hide
+ */
+ public static final String PHONETIC_NAME_STYLE = DATA10;
+
+ /**
* Return the string resource that best describes the given
* {@link #TYPE}. Will always return a valid resource.
*/
@@ -4311,6 +5010,42 @@ public final class ContactsContract {
* The MIME type of a single group.
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/group";
+
+ public static EntityIterator newEntityIterator(Cursor cursor) {
+ return new EntityIteratorImpl(cursor);
+ }
+
+ private static class EntityIteratorImpl extends CursorEntityIterator {
+ public EntityIteratorImpl(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public Entity getEntityAndIncrementCursor(Cursor cursor) throws RemoteException {
+ // we expect the cursor is already at the row we need to read from
+ final ContentValues values = new ContentValues();
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, _ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, ACCOUNT_NAME);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, ACCOUNT_TYPE);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DIRTY);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, VERSION);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SOURCE_ID);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, RES_PACKAGE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, TITLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, TITLE_RES);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, GROUP_VISIBLE);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC1);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC2);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC3);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYNC4);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SYSTEM_ID);
+ DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, values, DELETED);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, NOTES);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, values, SHOULD_SYNC);
+ cursor.moveToNext();
+ return new Entity(values);
+ }
+ }
}
/**
@@ -4528,7 +5263,6 @@ public final class ContactsContract {
* </tr>
* </table>
*/
-
public static final class Settings implements SettingsColumns {
/**
* This utility class cannot be instantiated
@@ -4555,6 +5289,83 @@ public final class ContactsContract {
}
/**
+ * Private API for inquiring about the general status of the provider.
+ *
+ * @hide
+ */
+ public static final class ProviderStatus {
+
+ /**
+ * Not instantiable.
+ */
+ private ProviderStatus() {
+ }
+
+ /**
+ * The content:// style URI for this table. Requests to this URI can be
+ * performed on the UI thread because they are always unblocking.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, "provider_status");
+
+ /**
+ * The MIME-type of {@link #CONTENT_URI} providing a directory of
+ * settings.
+ *
+ * @hide
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
+
+ /**
+ * An integer representing the current status of the provider.
+ *
+ * @hide
+ */
+ public static final String STATUS = "status";
+
+ /**
+ * Default status of the provider.
+ *
+ * @hide
+ */
+ public static final int STATUS_NORMAL = 0;
+
+ /**
+ * The status used when the provider is in the process of upgrading. Contacts
+ * are temporarily unaccessible.
+ *
+ * @hide
+ */
+ public static final int STATUS_UPGRADING = 1;
+
+ /**
+ * The status used if the provider was in the process of upgrading but ran
+ * out of storage. The DATA1 column will contain the estimated amount of
+ * storage required (in bytes). Update status to STATUS_NORMAL to force
+ * the provider to retry the upgrade.
+ *
+ * @hide
+ */
+ public static final int STATUS_UPGRADE_OUT_OF_MEMORY = 2;
+
+ /**
+ * The status used during a locale change.
+ *
+ * @hide
+ */
+ public static final int STATUS_CHANGING_LOCALE = 3;
+
+ /**
+ * Additional data associated with the status.
+ *
+ * @hide
+ */
+ public static final String DATA1 = "data1";
+ }
+
+ /**
* Helper methods to display QuickContact dialogs that allow users to pivot on
* a specific {@link Contacts} entry.
*/
@@ -4568,8 +5379,10 @@ public final class ContactsContract {
/**
* Extra used to specify pivot dialog location in screen coordinates.
+ * @deprecated Use {@link Intent#setSourceBounds(Rect)} instead.
* @hide
*/
+ @Deprecated
public static final String EXTRA_TARGET_RECT = "target_rect";
/**
@@ -4629,15 +5442,17 @@ public final class ContactsContract {
*/
public static void showQuickContact(Context context, View target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // Find location and bounds of target view
- final int[] location = new int[2];
- target.getLocationOnScreen(location);
+ // Find location and bounds of target view, adjusting based on the
+ // assumed local density.
+ final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ target.getLocationOnScreen(pos);
final Rect rect = new Rect();
- rect.left = location[0];
- rect.top = location[1];
- rect.right = rect.left + target.getWidth();
- rect.bottom = rect.top + target.getHeight();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
// Trigger with obtained rectangle
showQuickContact(context, rect, lookupUri, mode, excludeMimes);
@@ -4654,8 +5469,11 @@ public final class ContactsContract {
* @param target Specific {@link Rect} that this dialog should be
* centered around, in screen coordinates. In particular, if
* the dialog has a "callout" arrow, it will be pointed and
- * centered around this {@link Rect}.
- * @param lookupUri A {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
+ * centered around this {@link Rect}. If you are running at a
+ * non-native density, you need to manually adjust using
+ * {@link DisplayMetrics#density} before calling.
+ * @param lookupUri A
+ * {@link ContactsContract.Contacts#CONTENT_LOOKUP_URI} style
* {@link Uri} that describes a specific contact to feature
* in this dialog.
* @param mode Any of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or
@@ -4674,7 +5492,7 @@ public final class ContactsContract {
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
intent.setData(lookupUri);
- intent.putExtra(EXTRA_TARGET_RECT, target);
+ intent.setSourceBounds(target);
intent.putExtra(EXTRA_MODE, mode);
intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
context.startActivity(intent);
@@ -5073,5 +5891,4 @@ public final class ContactsContract {
public static final String IM_ISPRIMARY = "im_isprimary";
}
}
-
}
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 790fe5c..348e9e8 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -17,43 +17,52 @@
package android.provider;
import android.net.Uri;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+
+import java.io.File;
/**
- * Exposes constants used to interact with the download manager's
- * content provider.
- * The constants URI ... STATUS are the names of columns in the downloads table.
+ * The Download Manager
*
- * @hide
+ * @pending
*/
-// For 1.0 the download manager can't deal with abuse from untrusted apps, so
-// this API is hidden.
-public final class Downloads implements BaseColumns {
+public final class Downloads {
+ /**
+ * @hide
+ */
private Downloads() {}
/**
* The permission to access the download manager
+ * @hide
*/
public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
/**
* The permission to access the download manager's advanced functions
+ * @hide
*/
public static final String PERMISSION_ACCESS_ADVANCED =
"android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
/**
* The permission to directly access the download manager's cache directory
+ * @hide
*/
public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
/**
* The permission to send broadcasts on download completion
+ * @hide
*/
public static final String PERMISSION_SEND_INTENTS =
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
* The content:// URI for the data table in the provider
+ * @hide
*/
public static final Uri CONTENT_URI =
Uri.parse("content://downloads/download");
@@ -62,6 +71,7 @@ public final class Downloads implements BaseColumns {
* Broadcast Action: this is sent by the download manager to the app
* that had initiated a download when that download completes. The
* download's content: uri is specified in the intent's data.
+ * @hide
*/
public static final String ACTION_DOWNLOAD_COMPLETED =
"android.intent.action.DOWNLOAD_COMPLETED";
@@ -75,6 +85,7 @@ public final class Downloads implements BaseColumns {
* multiple downloads.
* Note: this is not currently sent for downloads that have completed
* successfully.
+ * @hide
*/
public static final String ACTION_NOTIFICATION_CLICKED =
"android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
@@ -83,6 +94,7 @@ public final class Downloads implements BaseColumns {
* The name of the column containing the URI of the data being downloaded.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
+ * @hide
*/
public static final String COLUMN_URI = "uri";
@@ -90,6 +102,7 @@ public final class Downloads implements BaseColumns {
* The name of the column containing application-specific data.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
+ * @hide
*/
public static final String COLUMN_APP_DATA = "entity";
@@ -103,6 +116,7 @@ public final class Downloads implements BaseColumns {
* whether a download fully completed).
* <P>Type: BOOLEAN</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_NO_INTEGRITY = "no_integrity";
@@ -112,6 +126,7 @@ public final class Downloads implements BaseColumns {
* to use this filename, or a variation, as the actual name for the file.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_FILE_NAME_HINT = "hint";
@@ -120,6 +135,7 @@ public final class Downloads implements BaseColumns {
* was actually stored.
* <P>Type: TEXT</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String _DATA = "_data";
@@ -127,6 +143,7 @@ public final class Downloads implements BaseColumns {
* The name of the column containing the MIME type of the downloaded data.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
+ * @hide
*/
public static final String COLUMN_MIME_TYPE = "mimetype";
@@ -135,6 +152,7 @@ public final class Downloads implements BaseColumns {
* of the download. See the DESTINATION_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_DESTINATION = "destination";
@@ -144,6 +162,7 @@ public final class Downloads implements BaseColumns {
* a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Init/Read/Write</P>
+ * @hide
*/
public static final String COLUMN_VISIBILITY = "visibility";
@@ -153,6 +172,7 @@ public final class Downloads implements BaseColumns {
* the CONTROL_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String COLUMN_CONTROL = "control";
@@ -162,6 +182,7 @@ public final class Downloads implements BaseColumns {
* the STATUS_* constants for a list of legal values.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String COLUMN_STATUS = "status";
@@ -171,6 +192,7 @@ public final class Downloads implements BaseColumns {
* value.
* <P>Type: BIGINT</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String COLUMN_LAST_MODIFICATION = "lastmod";
@@ -180,6 +202,7 @@ public final class Downloads implements BaseColumns {
* notifications to a component in this package when the download completes.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
+ * @hide
*/
public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
@@ -190,6 +213,7 @@ public final class Downloads implements BaseColumns {
* Intent.setClassName(String,String).
* <P>Type: TEXT</P>
* <P>Owner can Init/Read</P>
+ * @hide
*/
public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
@@ -198,6 +222,7 @@ public final class Downloads implements BaseColumns {
* is sent to the specified class and package when a download has finished.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
@@ -207,6 +232,7 @@ public final class Downloads implements BaseColumns {
* header that gets sent with the request.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_COOKIE_DATA = "cookiedata";
@@ -215,6 +241,7 @@ public final class Downloads implements BaseColumns {
* application wants the download manager to use for this download.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_USER_AGENT = "useragent";
@@ -223,6 +250,7 @@ public final class Downloads implements BaseColumns {
* application wants the download manager to use for this download.
* <P>Type: TEXT</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_REFERER = "referer";
@@ -231,6 +259,7 @@ public final class Downloads implements BaseColumns {
* downloaded.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String COLUMN_TOTAL_BYTES = "total_bytes";
@@ -239,6 +268,7 @@ public final class Downloads implements BaseColumns {
* has been downloaded so far.
* <P>Type: INTEGER</P>
* <P>Owner can Read</P>
+ * @hide
*/
public static final String COLUMN_CURRENT_BYTES = "current_bytes";
@@ -251,6 +281,7 @@ public final class Downloads implements BaseColumns {
* android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
* <P>Type: INTEGER</P>
* <P>Owner can Init</P>
+ * @hide
*/
public static final String COLUMN_OTHER_UID = "otheruid";
@@ -260,6 +291,7 @@ public final class Downloads implements BaseColumns {
* list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
+ * @hide
*/
public static final String COLUMN_TITLE = "title";
@@ -269,6 +301,7 @@ public final class Downloads implements BaseColumns {
* user in the list of downloads.
* <P>Type: TEXT</P>
* <P>Owner can Init/Read/Write</P>
+ * @hide
*/
public static final String COLUMN_DESCRIPTION = "description";
@@ -284,6 +317,7 @@ public final class Downloads implements BaseColumns {
* Downloads to the external destination only write files for which
* there is a registered handler. The resulting files are accessible
* by filename to all applications.
+ * @hide
*/
public static final int DESTINATION_EXTERNAL = 0;
@@ -295,6 +329,7 @@ public final class Downloads implements BaseColumns {
* application can access the file (indirectly through a content
* provider). This requires the
* android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+ * @hide
*/
public static final int DESTINATION_CACHE_PARTITION = 1;
@@ -304,6 +339,7 @@ public final class Downloads implements BaseColumns {
* for private files (similar to CACHE_PARTITION) that aren't deleted
* immediately after they are used, and are kept around by the download
* manager as long as space is available.
+ * @hide
*/
public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
@@ -311,16 +347,19 @@ public final class Downloads implements BaseColumns {
* This download will be saved to the download manager's private
* partition, as with DESTINATION_CACHE_PARTITION, but the download
* will not proceed if the user is on a roaming data connection.
+ * @hide
*/
public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
/**
* This download is allowed to run.
+ * @hide
*/
public static final int CONTROL_RUN = 0;
/**
* This download must pause at the first opportunity.
+ * @hide
*/
public static final int CONTROL_PAUSED = 1;
@@ -337,6 +376,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the status is informational (i.e. 1xx).
+ * @hide
*/
public static boolean isStatusInformational(int status) {
return (status >= 100 && status < 200);
@@ -346,6 +386,7 @@ public final class Downloads implements BaseColumns {
* Returns whether the download is suspended. (i.e. whether the download
* won't complete without some action from outside the download
* manager).
+ * @hide
*/
public static boolean isStatusSuspended(int status) {
return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
@@ -353,6 +394,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the status is a success (i.e. 2xx).
+ * @hide
*/
public static boolean isStatusSuccess(int status) {
return (status >= 200 && status < 300);
@@ -360,6 +402,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the status is an error (i.e. 4xx or 5xx).
+ * @hide
*/
public static boolean isStatusError(int status) {
return (status >= 400 && status < 600);
@@ -367,6 +410,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the status is a client error (i.e. 4xx).
+ * @hide
*/
public static boolean isStatusClientError(int status) {
return (status >= 400 && status < 500);
@@ -374,6 +418,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the status is a server error (i.e. 5xx).
+ * @hide
*/
public static boolean isStatusServerError(int status) {
return (status >= 500 && status < 600);
@@ -382,6 +427,7 @@ public final class Downloads implements BaseColumns {
/**
* Returns whether the download has completed (either with success or
* error).
+ * @hide
*/
public static boolean isStatusCompleted(int status) {
return (status >= 200 && status < 300) || (status >= 400 && status < 600);
@@ -389,21 +435,25 @@ public final class Downloads implements BaseColumns {
/**
* This download hasn't stated yet
+ * @hide
*/
public static final int STATUS_PENDING = 190;
/**
* This download hasn't stated yet and is paused
+ * @hide
*/
public static final int STATUS_PENDING_PAUSED = 191;
/**
* This download has started
+ * @hide
*/
public static final int STATUS_RUNNING = 192;
/**
* This download has started and is paused
+ * @hide
*/
public static final int STATUS_RUNNING_PAUSED = 193;
@@ -412,18 +462,21 @@ public final class Downloads implements BaseColumns {
* Warning: there might be other status values that indicate success
* in the future.
* Use isSucccess() to capture the entire category.
+ * @hide
*/
public static final int STATUS_SUCCESS = 200;
/**
* This request couldn't be parsed. This is also used when processing
* requests with unknown/unsupported URI schemes.
+ * @hide
*/
public static final int STATUS_BAD_REQUEST = 400;
/**
* This download can't be performed because the content type cannot be
* handled.
+ * @hide
*/
public static final int STATUS_NOT_ACCEPTABLE = 406;
@@ -435,6 +488,7 @@ public final class Downloads implements BaseColumns {
* client when a response is received whose length cannot be determined
* accurately (therefore making it impossible to know when a download
* completes).
+ * @hide
*/
public static final int STATUS_LENGTH_REQUIRED = 411;
@@ -442,11 +496,13 @@ public final class Downloads implements BaseColumns {
* This download was interrupted and cannot be resumed.
* This is the code for the HTTP error "Precondition Failed", and it is
* also used in situations where the client doesn't have an ETag at all.
+ * @hide
*/
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
* This download was canceled
+ * @hide
*/
public static final int STATUS_CANCELED = 490;
@@ -454,12 +510,16 @@ public final class Downloads implements BaseColumns {
* This download has completed with an error.
* Warning: there will be other status values that indicate errors in
* the future. Use isStatusError() to capture the entire category.
+ * @hide
*/
public static final int STATUS_UNKNOWN_ERROR = 491;
/**
* This download couldn't be completed because of a storage issue.
* Typically, that's because the filesystem is missing or full.
+ * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+ * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
+ * @hide
*/
public static final int STATUS_FILE_ERROR = 492;
@@ -467,47 +527,587 @@ public final class Downloads implements BaseColumns {
* This download couldn't be completed because of an HTTP
* redirect response that the download manager couldn't
* handle.
+ * @hide
*/
public static final int STATUS_UNHANDLED_REDIRECT = 493;
/**
* This download couldn't be completed because of an
* unspecified unhandled HTTP code.
+ * @hide
*/
public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
/**
* This download couldn't be completed because of an
* error receiving or processing data at the HTTP level.
+ * @hide
*/
public static final int STATUS_HTTP_DATA_ERROR = 495;
/**
* This download couldn't be completed because of an
* HttpException while setting up the request.
+ * @hide
*/
public static final int STATUS_HTTP_EXCEPTION = 496;
/**
* This download couldn't be completed because there were
* too many redirects.
+ * @hide
*/
public static final int STATUS_TOO_MANY_REDIRECTS = 497;
/**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ * @hide
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ * @hide
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
* This download is visible but only shows in the notifications
* while it's in progress.
+ * @hide
*/
public static final int VISIBILITY_VISIBLE = 0;
/**
* This download is visible and shows in the notifications while
* in progress and after completion.
+ * @hide
*/
public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
/**
* This download doesn't show in the UI or in the notifications.
+ * @hide
*/
public static final int VISIBILITY_HIDDEN = 2;
+
+ /**
+ * Implementation details
+ *
+ * Exposes constants used to interact with the download manager's
+ * content provider.
+ * The constants URI ... STATUS are the names of columns in the downloads table.
+ *
+ * @hide
+ */
+ public static final class Impl implements BaseColumns {
+ private Impl() {}
+
+ /**
+ * The permission to access the download manager
+ */
+ public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER";
+
+ /**
+ * The permission to access the download manager's advanced functions
+ */
+ public static final String PERMISSION_ACCESS_ADVANCED =
+ "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED";
+
+ /**
+ * The permission to directly access the download manager's cache directory
+ */
+ public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM";
+
+ /**
+ * The permission to send broadcasts on download completion
+ */
+ public static final String PERMISSION_SEND_INTENTS =
+ "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
+
+ /**
+ * The permission to access downloads to {@link DESTINATION_EXTERNAL}
+ * which were downloaded by other applications.
+ * @hide
+ */
+ public static final String PERMISSION_SEE_ALL_EXTERNAL =
+ "android.permission.SEE_ALL_EXTERNAL";
+
+ /**
+ * The content:// URI for the data table in the provider
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://downloads/download");
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when that download completes. The
+ * download's content: uri is specified in the intent's data.
+ */
+ public static final String ACTION_DOWNLOAD_COMPLETED =
+ "android.intent.action.DOWNLOAD_COMPLETED";
+
+ /**
+ * Broadcast Action: this is sent by the download manager to the app
+ * that had initiated a download when the user selects the notification
+ * associated with that download. The download's content: uri is specified
+ * in the intent's data if the click is associated with a single download,
+ * or Downloads.CONTENT_URI if the notification is associated with
+ * multiple downloads.
+ * Note: this is not currently sent for downloads that have completed
+ * successfully.
+ */
+ public static final String ACTION_NOTIFICATION_CLICKED =
+ "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+
+ /**
+ * The name of the column containing the URI of the data being downloaded.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_URI = "uri";
+
+ /**
+ * The name of the column containing application-specific data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_APP_DATA = "entity";
+
+ /**
+ * The name of the column containing the flags that indicates whether
+ * the initiating application is capable of verifying the integrity of
+ * the downloaded file. When this flag is set, the download manager
+ * performs downloads and reports success even in some situations where
+ * it can't guarantee that the download has completed (e.g. when doing
+ * a byte-range request without an ETag, or when it can't determine
+ * whether a download fully completed).
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NO_INTEGRITY = "no_integrity";
+
+ /**
+ * The name of the column containing the filename that the initiating
+ * application recommends. When possible, the download manager will attempt
+ * to use this filename, or a variation, as the actual name for the file.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_FILE_NAME_HINT = "hint";
+
+ /**
+ * The name of the column containing the filename where the downloaded data
+ * was actually stored.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String _DATA = "_data";
+
+ /**
+ * The name of the column containing the MIME type of the downloaded data.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_MIME_TYPE = "mimetype";
+
+ /**
+ * The name of the column containing the flag that controls the destination
+ * of the download. See the DESTINATION_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_DESTINATION = "destination";
+
+ /**
+ * The name of the column containing the flags that controls whether the
+ * download is displayed by the UI. See the VISIBILITY_* constants for
+ * a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_VISIBILITY = "visibility";
+
+ /**
+ * The name of the column containing the current control state of the download.
+ * Applications can write to this to control (pause/resume) the download.
+ * the CONTROL_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CONTROL = "control";
+
+ /**
+ * The name of the column containing the current status of the download.
+ * Applications can read this to follow the progress of each download. See
+ * the STATUS_* constants for a list of legal values.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_STATUS = "status";
+
+ /**
+ * The name of the column containing the date at which some interesting
+ * status changed in the download. Stored as a System.currentTimeMillis()
+ * value.
+ * <P>Type: BIGINT</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_LAST_MODIFICATION = "lastmod";
+
+ /**
+ * The name of the column containing the package name of the application
+ * that initiating the download. The download manager will send
+ * notifications to a component in this package when the download completes.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage";
+
+ /**
+ * The name of the column containing the component name of the class that
+ * will receive notifications associated with the download. The
+ * package/class combination is passed to
+ * Intent.setClassName(String,String).
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass";
+
+ /**
+ * If extras are specified when requesting a download they will be provided in the intent that
+ * is sent to the specified class and package when a download has finished.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
+
+ /**
+ * The name of the column contain the values of the cookie to be used for
+ * the download. This is used directly as the value for the Cookie: HTTP
+ * header that gets sent with the request.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_COOKIE_DATA = "cookiedata";
+
+ /**
+ * The name of the column containing the user agent that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_USER_AGENT = "useragent";
+
+ /**
+ * The name of the column containing the referer (sic) that the initiating
+ * application wants the download manager to use for this download.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_REFERER = "referer";
+
+ /**
+ * The name of the column containing the total size of the file being
+ * downloaded.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_TOTAL_BYTES = "total_bytes";
+
+ /**
+ * The name of the column containing the size of the part of the file that
+ * has been downloaded so far.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Read</P>
+ */
+ public static final String COLUMN_CURRENT_BYTES = "current_bytes";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * UID of another application that is allowed to access this download. If
+ * multiple applications share the same UID, all those applications will be
+ * allowed to access this download. This column can be updated after the
+ * download is initiated. This requires the permission
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init</P>
+ */
+ public static final String COLUMN_OTHER_UID = "otheruid";
+
+ /**
+ * The name of the column where the initiating application can provided the
+ * title of this download. The title will be displayed ito the user in the
+ * list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The name of the column where the initiating application can provide the
+ * description of this download. The description will be displayed to the
+ * user in the list of downloads.
+ * <P>Type: TEXT</P>
+ * <P>Owner can Init/Read/Write</P>
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /*
+ * Lists the destinations that an application can specify for a download.
+ */
+
+ /**
+ * This download will be saved to the external storage. This is the
+ * default behavior, and should be used for any file that the user
+ * can freely access, copy, delete. Even with that destination,
+ * unencrypted DRM files are saved in secure internal storage.
+ * Downloads to the external destination only write files for which
+ * there is a registered handler. The resulting files are accessible
+ * by filename to all applications.
+ */
+ public static final int DESTINATION_EXTERNAL = 0;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition. This is the behavior used by applications that want to
+ * download private files that are used and deleted soon after they
+ * get downloaded. All file types are allowed, and only the initiating
+ * application can access the file (indirectly through a content
+ * provider). This requires the
+ * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
+ */
+ public static final int DESTINATION_CACHE_PARTITION = 1;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition and will be purged as necessary to make space. This is
+ * for private files (similar to CACHE_PARTITION) that aren't deleted
+ * immediately after they are used, and are kept around by the download
+ * manager as long as space is available.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2;
+
+ /**
+ * This download will be saved to the download manager's private
+ * partition, as with DESTINATION_CACHE_PARTITION, but the download
+ * will not proceed if the user is on a roaming data connection.
+ */
+ public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
+
+ /**
+ * This download is allowed to run.
+ */
+ public static final int CONTROL_RUN = 0;
+
+ /**
+ * This download must pause at the first opportunity.
+ */
+ public static final int CONTROL_PAUSED = 1;
+
+ /*
+ * Lists the states that the download manager can set on a download
+ * to notify applications of the download progress.
+ * The codes follow the HTTP families:<br>
+ * 1xx: informational<br>
+ * 2xx: success<br>
+ * 3xx: redirects (not used by the download manager)<br>
+ * 4xx: client errors<br>
+ * 5xx: server errors
+ */
+
+ /**
+ * Returns whether the status is informational (i.e. 1xx).
+ */
+ public static boolean isStatusInformational(int status) {
+ return (status >= 100 && status < 200);
+ }
+
+ /**
+ * Returns whether the download is suspended. (i.e. whether the download
+ * won't complete without some action from outside the download
+ * manager).
+ */
+ public static boolean isStatusSuspended(int status) {
+ return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED);
+ }
+
+ /**
+ * Returns whether the status is a success (i.e. 2xx).
+ */
+ public static boolean isStatusSuccess(int status) {
+ return (status >= 200 && status < 300);
+ }
+
+ /**
+ * Returns whether the status is an error (i.e. 4xx or 5xx).
+ */
+ public static boolean isStatusError(int status) {
+ return (status >= 400 && status < 600);
+ }
+
+ /**
+ * Returns whether the status is a client error (i.e. 4xx).
+ */
+ public static boolean isStatusClientError(int status) {
+ return (status >= 400 && status < 500);
+ }
+
+ /**
+ * Returns whether the status is a server error (i.e. 5xx).
+ */
+ public static boolean isStatusServerError(int status) {
+ return (status >= 500 && status < 600);
+ }
+
+ /**
+ * Returns whether the download has completed (either with success or
+ * error).
+ */
+ public static boolean isStatusCompleted(int status) {
+ return (status >= 200 && status < 300) || (status >= 400 && status < 600);
+ }
+
+ /**
+ * This download hasn't stated yet
+ */
+ public static final int STATUS_PENDING = 190;
+
+ /**
+ * This download hasn't stated yet and is paused
+ */
+ public static final int STATUS_PENDING_PAUSED = 191;
+
+ /**
+ * This download has started
+ */
+ public static final int STATUS_RUNNING = 192;
+
+ /**
+ * This download has started and is paused
+ */
+ public static final int STATUS_RUNNING_PAUSED = 193;
+
+ /**
+ * This download has successfully completed.
+ * Warning: there might be other status values that indicate success
+ * in the future.
+ * Use isSucccess() to capture the entire category.
+ */
+ public static final int STATUS_SUCCESS = 200;
+
+ /**
+ * This request couldn't be parsed. This is also used when processing
+ * requests with unknown/unsupported URI schemes.
+ */
+ public static final int STATUS_BAD_REQUEST = 400;
+
+ /**
+ * This download can't be performed because the content type cannot be
+ * handled.
+ */
+ public static final int STATUS_NOT_ACCEPTABLE = 406;
+
+ /**
+ * This download cannot be performed because the length cannot be
+ * determined accurately. This is the code for the HTTP error "Length
+ * Required", which is typically used when making requests that require
+ * a content length but don't have one, and it is also used in the
+ * client when a response is received whose length cannot be determined
+ * accurately (therefore making it impossible to know when a download
+ * completes).
+ */
+ public static final int STATUS_LENGTH_REQUIRED = 411;
+
+ /**
+ * This download was interrupted and cannot be resumed.
+ * This is the code for the HTTP error "Precondition Failed", and it is
+ * also used in situations where the client doesn't have an ETag at all.
+ */
+ public static final int STATUS_PRECONDITION_FAILED = 412;
+
+ /**
+ * This download was canceled
+ */
+ public static final int STATUS_CANCELED = 490;
+
+ /**
+ * This download has completed with an error.
+ * Warning: there will be other status values that indicate errors in
+ * the future. Use isStatusError() to capture the entire category.
+ */
+ public static final int STATUS_UNKNOWN_ERROR = 491;
+
+ /**
+ * This download couldn't be completed because of a storage issue.
+ * Typically, that's because the filesystem is missing or full.
+ * Use the more specific {@link #STATUS_INSUFFICIENT_SPACE_ERROR}
+ * and {@link #STATUS_DEVICE_NOT_FOUND_ERROR} when appropriate.
+ */
+ public static final int STATUS_FILE_ERROR = 492;
+
+ /**
+ * This download couldn't be completed because of an HTTP
+ * redirect response that the download manager couldn't
+ * handle.
+ */
+ public static final int STATUS_UNHANDLED_REDIRECT = 493;
+
+ /**
+ * This download couldn't be completed because of an
+ * unspecified unhandled HTTP code.
+ */
+ public static final int STATUS_UNHANDLED_HTTP_CODE = 494;
+
+ /**
+ * This download couldn't be completed because of an
+ * error receiving or processing data at the HTTP level.
+ */
+ public static final int STATUS_HTTP_DATA_ERROR = 495;
+
+ /**
+ * This download couldn't be completed because of an
+ * HttpException while setting up the request.
+ */
+ public static final int STATUS_HTTP_EXCEPTION = 496;
+
+ /**
+ * This download couldn't be completed because there were
+ * too many redirects.
+ */
+ public static final int STATUS_TOO_MANY_REDIRECTS = 497;
+
+ /**
+ * This download couldn't be completed due to insufficient storage
+ * space. Typically, this is because the SD card is full.
+ */
+ public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
+
+ /**
+ * This download couldn't be completed because no external storage
+ * device was found. Typically, this is because the SD card is not
+ * mounted.
+ */
+ public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
+
+ /**
+ * This download is visible but only shows in the notifications
+ * while it's in progress.
+ */
+ public static final int VISIBILITY_VISIBLE = 0;
+
+ /**
+ * This download is visible and shows in the notifications while
+ * in progress and after completion.
+ */
+ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
+
+ /**
+ * This download doesn't show in the UI or in the notifications.
+ */
+ public static final int VISIBILITY_HIDDEN = 2;
+ }
}
diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java
deleted file mode 100644
index 073ae6c..0000000
--- a/core/java/android/provider/Gmail.java
+++ /dev/null
@@ -1,2467 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.text.Html;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.text.style.CharacterStyle;
-import android.text.util.Regex;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A thin wrapper over the content resolver for accessing the gmail provider.
- *
- * @hide
- */
-public final class Gmail {
- // Set to true to enable extra debugging.
- private static final boolean DEBUG = false;
-
- public static final String GMAIL_AUTH_SERVICE = "mail";
- // These constants come from google3/java/com/google/caribou/backend/MailLabel.java.
- public static final String LABEL_SENT = "^f";
- public static final String LABEL_INBOX = "^i";
- public static final String LABEL_DRAFT = "^r";
- public static final String LABEL_UNREAD = "^u";
- public static final String LABEL_TRASH = "^k";
- public static final String LABEL_SPAM = "^s";
- public static final String LABEL_STARRED = "^t";
- public static final String LABEL_CHAT = "^b"; // 'b' for 'buzz'
- public static final String LABEL_VOICEMAIL = "^vm";
- public static final String LABEL_IGNORED = "^g";
- public static final String LABEL_ALL = "^all";
- // These constants (starting with "^^") are only used locally and are not understood by the
- // server.
- public static final String LABEL_VOICEMAIL_INBOX = "^^vmi";
- public static final String LABEL_CACHED = "^^cached";
- public static final String LABEL_OUTBOX = "^^out";
-
- public static final String AUTHORITY = "gmail-ls";
- private static final String TAG = "Gmail";
- private static final String AUTHORITY_PLUS_CONVERSATIONS =
- "content://" + AUTHORITY + "/conversations/";
- private static final String AUTHORITY_PLUS_LABELS =
- "content://" + AUTHORITY + "/labels/";
- private static final String AUTHORITY_PLUS_MESSAGES =
- "content://" + AUTHORITY + "/messages/";
- private static final String AUTHORITY_PLUS_SETTINGS =
- "content://" + AUTHORITY + "/settings/";
-
- public static final Uri BASE_URI = Uri.parse(
- "content://" + AUTHORITY);
- private static final Uri LABELS_URI =
- Uri.parse(AUTHORITY_PLUS_LABELS);
- private static final Uri CONVERSATIONS_URI =
- Uri.parse(AUTHORITY_PLUS_CONVERSATIONS);
- private static final Uri SETTINGS_URI =
- Uri.parse(AUTHORITY_PLUS_SETTINGS);
-
- /** Separates email addresses in strings in the database. */
- public static final String EMAIL_SEPARATOR = "\n";
- public static final Pattern EMAIL_SEPARATOR_PATTERN = Pattern.compile(EMAIL_SEPARATOR);
-
- /**
- * Space-separated lists have separators only between items.
- */
- private static final char SPACE_SEPARATOR = ' ';
- public static final Pattern SPACE_SEPARATOR_PATTERN = Pattern.compile(" ");
-
- /**
- * Comma-separated lists have separators between each item, before the first and after the last
- * item. The empty list is <tt>,</tt>.
- *
- * <p>This makes them easier to modify with SQL since it is not a special case to add or
- * remove the last item. Having a separator on each side of each value also makes it safe to use
- * SQL's REPLACE to remove an item from a string by using REPLACE(',value,', ',').
- *
- * <p>We could use the same separator for both lists but this makes it easier to remember which
- * kind of list one is dealing with.
- */
- private static final char COMMA_SEPARATOR = ',';
- public static final Pattern COMMA_SEPARATOR_PATTERN = Pattern.compile(",");
-
- /** Separates attachment info parts in strings in the database. */
- public static final String ATTACHMENT_INFO_SEPARATOR = "\n";
- public static final Pattern ATTACHMENT_INFO_SEPARATOR_PATTERN =
- Pattern.compile(ATTACHMENT_INFO_SEPARATOR);
-
- public static final Character SENDER_LIST_SEPARATOR = '\n';
- public static final String SENDER_LIST_TOKEN_ELIDED = "e";
- public static final String SENDER_LIST_TOKEN_NUM_MESSAGES = "n";
- public static final String SENDER_LIST_TOKEN_NUM_DRAFTS = "d";
- public static final String SENDER_LIST_TOKEN_LITERAL = "l";
- public static final String SENDER_LIST_TOKEN_SENDING = "s";
- public static final String SENDER_LIST_TOKEN_SEND_FAILED = "f";
-
- /** Used for finding status in a cursor's extras. */
- public static final String EXTRA_STATUS = "status";
-
- public static final String RESPOND_INPUT_COMMAND = "command";
- public static final String COMMAND_RETRY = "retry";
- public static final String COMMAND_ACTIVATE = "activate";
- public static final String COMMAND_SET_VISIBLE = "setVisible";
- public static final String SET_VISIBLE_PARAM_VISIBLE = "visible";
- public static final String RESPOND_OUTPUT_COMMAND_RESPONSE = "commandResponse";
- public static final String COMMAND_RESPONSE_OK = "ok";
- public static final String COMMAND_RESPONSE_UNKNOWN = "unknownCommand";
-
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN = "origin";
- public static final String INSERT_PARAM_ATTACHMENT_ORIGIN_EXTRAS = "originExtras";
-
- private static final Pattern NAME_ADDRESS_PATTERN = Pattern.compile("\"(.*)\"");
- private static final Pattern UNNAMED_ADDRESS_PATTERN = Pattern.compile("([^<]+)@");
-
- private static final Map<Integer, Integer> sPriorityToLength = Maps.newHashMap();
- public static final SimpleStringSplitter sSenderListSplitter =
- new SimpleStringSplitter(SENDER_LIST_SEPARATOR);
- public static String[] sSenderFragments = new String[8];
-
- /**
- * Returns the name in an address string
- * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
- * @return returns the quoted name in the addressString, otherwise the username from the email
- * address
- */
- public static String getNameFromAddressString(String addressString) {
- Matcher namedAddressMatch = NAME_ADDRESS_PATTERN.matcher(addressString);
- if (namedAddressMatch.find()) {
- String name = namedAddressMatch.group(1);
- if (name.length() > 0) return name;
- addressString =
- addressString.substring(namedAddressMatch.end(), addressString.length());
- }
-
- Matcher unnamedAddressMatch = UNNAMED_ADDRESS_PATTERN.matcher(addressString);
- if (unnamedAddressMatch.find()) {
- return unnamedAddressMatch.group(1);
- }
-
- return addressString;
- }
-
- /**
- * Returns the email address in an address string
- * @param addressString such as &quot;bobby&quot; &lt;bob@example.com&gt;
- * @return returns the email address, such as bob@example.com from the example above
- */
- public static String getEmailFromAddressString(String addressString) {
- String result = addressString;
- Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(addressString);
- if (match.find()) {
- result = addressString.substring(match.start(), match.end());
- }
-
- return result;
- }
-
- /**
- * Returns whether the label is user-defined (versus system-defined labels such as inbox, whose
- * names start with "^").
- */
- public static boolean isLabelUserDefined(String label) {
- // TODO: label should never be empty so we should be able to say [label.charAt(0) != '^'].
- // However, it's a release week and I'm too scared to make that change.
- return !label.startsWith("^");
- }
-
- private static final Set<String> USER_SETTABLE_BUILTIN_LABELS = Sets.newHashSet(
- Gmail.LABEL_INBOX,
- Gmail.LABEL_UNREAD,
- Gmail.LABEL_TRASH,
- Gmail.LABEL_SPAM,
- Gmail.LABEL_STARRED,
- Gmail.LABEL_IGNORED);
-
- /**
- * Returns whether the label is user-settable. For example, labels such as LABEL_DRAFT should
- * only be set internally.
- */
- public static boolean isLabelUserSettable(String label) {
- return USER_SETTABLE_BUILTIN_LABELS.contains(label) || isLabelUserDefined(label);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public static Set<Long> getLabelIdsFromLabelIdsString(
- TextUtils.StringSplitter splitter) {
- Set<Long> labelIds = Sets.newHashSet();
- for (String labelIdString : splitter) {
- labelIds.add(Long.valueOf(labelIdString));
- }
- return labelIds;
- }
-
- /**
- * @deprecated remove when the activities stop using canonical names to identify labels
- */
- public static Set<String> getCanonicalNamesFromLabelIdsString(
- LabelMap labelMap, TextUtils.StringSplitter splitter) {
- Set<String> canonicalNames = Sets.newHashSet();
- for (long labelId : getLabelIdsFromLabelIdsString(splitter)) {
- final String canonicalName = labelMap.getCanonicalName(labelId);
- // We will sometimes see labels that the label map does not yet know about or that
- // do not have names yet.
- if (!TextUtils.isEmpty(canonicalName)) {
- canonicalNames.add(canonicalName);
- } else {
- Log.w(TAG, "getCanonicalNamesFromLabelIdsString skipping label id: " + labelId);
- }
- }
- return canonicalNames;
- }
-
- /**
- * @return a StringSplitter that is configured to split message label id strings
- */
- public static TextUtils.StringSplitter newMessageLabelIdsSplitter() {
- return new TextUtils.SimpleStringSplitter(SPACE_SEPARATOR);
- }
-
- /**
- * @return a StringSplitter that is configured to split conversation label id strings
- */
- public static TextUtils.StringSplitter newConversationLabelIdsSplitter() {
- return new CommaStringSplitter();
- }
-
- /**
- * A splitter for strings of the form described in the docs for COMMA_SEPARATOR.
- */
- private static class CommaStringSplitter extends TextUtils.SimpleStringSplitter {
-
- public CommaStringSplitter() {
- super(COMMA_SEPARATOR);
- }
-
- @Override
- public void setString(String string) {
- // The string should always be at least a single comma.
- super.setString(string.substring(1));
- }
- }
-
- /**
- * Creates a single string of the form that getLabelIdsFromLabelIdsString can split.
- */
- public static String getLabelIdsStringFromLabelIds(Set<Long> labelIds) {
- StringBuilder sb = new StringBuilder();
- sb.append(COMMA_SEPARATOR);
- for (Long labelId : labelIds) {
- sb.append(labelId);
- sb.append(COMMA_SEPARATOR);
- }
- return sb.toString();
- }
-
- public static final class ConversationColumns {
- public static final String ID = "_id";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String DATE = "date";
- public static final String PERSONAL_LEVEL = "personalLevel";
- /** A list of label names with a space after each one (including the last one). This makes
- * it easier remove individual labels from this list using SQL. */
- public static final String LABEL_IDS = "labelIds";
- public static final String NUM_MESSAGES = "numMessages";
- public static final String MAX_MESSAGE_ID = "maxMessageId";
- public static final String HAS_ATTACHMENTS = "hasAttachments";
- public static final String HAS_MESSAGES_WITH_ERRORS = "hasMessagesWithErrors";
- public static final String FORCE_ALL_UNREAD = "forceAllUnread";
-
- private ConversationColumns() {}
- }
-
- public static final class MessageColumns {
-
- public static final String ID = "_id";
- public static final String MESSAGE_ID = "messageId";
- public static final String CONVERSATION_ID = "conversation";
- public static final String SUBJECT = "subject";
- public static final String SNIPPET = "snippet";
- public static final String FROM = "fromAddress";
- public static final String TO = "toAddresses";
- public static final String CC = "ccAddresses";
- public static final String BCC = "bccAddresses";
- public static final String REPLY_TO = "replyToAddresses";
- public static final String DATE_SENT_MS = "dateSentMs";
- public static final String DATE_RECEIVED_MS = "dateReceivedMs";
- public static final String LIST_INFO = "listInfo";
- public static final String PERSONAL_LEVEL = "personalLevel";
- public static final String BODY = "body";
- public static final String EMBEDS_EXTERNAL_RESOURCES = "bodyEmbedsExternalResources";
- public static final String LABEL_IDS = "labelIds";
- public static final String JOINED_ATTACHMENT_INFOS = "joinedAttachmentInfos";
- public static final String ERROR = "error";
- // TODO: add a method for accessing this
- public static final String REF_MESSAGE_ID = "refMessageId";
-
- // Fake columns used only for saving or sending messages.
- public static final String FAKE_SAVE = "save";
- public static final String FAKE_REF_MESSAGE_ID = "refMessageId";
-
- private MessageColumns() {}
- }
-
- public static final class LabelColumns {
- public static final String CANONICAL_NAME = "canonicalName";
- public static final String NAME = "name";
- public static final String NUM_CONVERSATIONS = "numConversations";
- public static final String NUM_UNREAD_CONVERSATIONS =
- "numUnreadConversations";
-
- private LabelColumns() {}
- }
-
- public static final class SettingsColumns {
- public static final String LABELS_INCLUDED = "labelsIncluded";
- public static final String LABELS_PARTIAL = "labelsPartial";
- public static final String CONVERSATION_AGE_DAYS =
- "conversationAgeDays";
- public static final String MAX_ATTACHMENET_SIZE_MB =
- "maxAttachmentSize";
- }
-
- /**
- * These flags can be included as Selection Arguments when
- * querying the provider.
- */
- public static class SelectionArguments {
- private SelectionArguments() {
- // forbid instantiation
- }
-
- /**
- * Specifies that you do NOT wish the returned cursor to
- * become the Active Network Cursor. If you do not include
- * this flag as a selectionArg, the new cursor will become the
- * Active Network Cursor by default.
- */
- public static final String DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR =
- "SELECTION_ARGUMENT_DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR";
- }
-
- // These are the projections that we need when getting cursors from the
- // content provider.
- private static String[] CONVERSATION_PROJECTION = {
- ConversationColumns.ID,
- ConversationColumns.SUBJECT,
- ConversationColumns.SNIPPET,
- ConversationColumns.FROM,
- ConversationColumns.DATE,
- ConversationColumns.PERSONAL_LEVEL,
- ConversationColumns.LABEL_IDS,
- ConversationColumns.NUM_MESSAGES,
- ConversationColumns.MAX_MESSAGE_ID,
- ConversationColumns.HAS_ATTACHMENTS,
- ConversationColumns.HAS_MESSAGES_WITH_ERRORS,
- ConversationColumns.FORCE_ALL_UNREAD};
- private static String[] MESSAGE_PROJECTION = {
- MessageColumns.ID,
- MessageColumns.MESSAGE_ID,
- MessageColumns.CONVERSATION_ID,
- MessageColumns.SUBJECT,
- MessageColumns.SNIPPET,
- MessageColumns.FROM,
- MessageColumns.TO,
- MessageColumns.CC,
- MessageColumns.BCC,
- MessageColumns.REPLY_TO,
- MessageColumns.DATE_SENT_MS,
- MessageColumns.DATE_RECEIVED_MS,
- MessageColumns.LIST_INFO,
- MessageColumns.PERSONAL_LEVEL,
- MessageColumns.BODY,
- MessageColumns.EMBEDS_EXTERNAL_RESOURCES,
- MessageColumns.LABEL_IDS,
- MessageColumns.JOINED_ATTACHMENT_INFOS,
- MessageColumns.ERROR};
- private static String[] LABEL_PROJECTION = {
- BaseColumns._ID,
- LabelColumns.CANONICAL_NAME,
- LabelColumns.NAME,
- LabelColumns.NUM_CONVERSATIONS,
- LabelColumns.NUM_UNREAD_CONVERSATIONS};
- private static String[] SETTINGS_PROJECTION = {
- SettingsColumns.LABELS_INCLUDED,
- SettingsColumns.LABELS_PARTIAL,
- SettingsColumns.CONVERSATION_AGE_DAYS,
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- };
-
- private ContentResolver mContentResolver;
-
- public Gmail(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
- }
-
- /**
- * Returns source if source is non-null. Returns the empty string otherwise.
- */
- private static String toNonnullString(String source) {
- if (source == null) {
- return "";
- } else {
- return source;
- }
- }
-
- /**
- * Behavior for a new cursor: should it become the Active Network
- * Cursor? This could potentially lead to bad behavior if someone
- * else is using the Active Network Cursor, since theirs will stop
- * being the Active Network Cursor.
- */
- public static enum BecomeActiveNetworkCursor {
- /**
- * The new cursor should become the one and only Active
- * Network Cursor. Any other cursor that might already be the
- * Active Network Cursor will cease to be so.
- */
- YES,
-
- /**
- * The new cursor should not become the Active Network
- * Cursor. Any other cursor that might already be the Active
- * Network Cursor will continue to be so.
- */
- NO
- }
-
- /**
- * Wraps a Cursor in a ConversationCursor
- *
- * @param account the account the cursor is associated with
- * @param cursor The Cursor to wrap
- * @return a new ConversationCursor
- */
- public ConversationCursor getConversationCursorForCursor(String account, Cursor cursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Creates an array of SelectionArguments suitable for passing to the provider's query.
- * Currently this only handles one flag, but it could be expanded in the future.
- */
- private static String[] getSelectionArguments(
- BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (BecomeActiveNetworkCursor.NO == becomeActiveNetworkCursor) {
- return new String[] {SelectionArguments.DO_NOT_BECOME_ACTIVE_NETWORK_CURSOR};
- } else {
- // Default behavior; no args required.
- return null;
- }
- }
-
- /**
- * Asynchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax. When the operation is complete the handler's
- * onQueryComplete() method is called with the resulting Cursor.
- *
- * @param account run the query on this account
- * @param handler An AsyncQueryHanlder that will be used to run the query
- * @param token The token to pass to startQuery, which will be passed back to onQueryComplete
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public void runQueryForConversations(String account, AsyncQueryHandler handler, int token,
- String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- handler.startQuery(token, null, Uri.withAppendedPath(CONVERSATIONS_URI, account),
- CONVERSATION_PROJECTION, query, selectionArgs, null);
- }
-
- /**
- * Synchronously gets a cursor over all conversations matching a query. The
- * query is in Gmail's query syntax.
- *
- * @param account run the query on this account
- * @param query a query in Gmail's query syntax
- * @param becomeActiveNetworkCursor whether or not the returned
- * cursor should become the Active Network Cursor
- */
- public ConversationCursor getConversationCursorForQuery(
- String account, String query, BecomeActiveNetworkCursor becomeActiveNetworkCursor) {
- String[] selectionArgs = getSelectionArguments(becomeActiveNetworkCursor);
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(CONVERSATIONS_URI, account), CONVERSATION_PROJECTION,
- query, selectionArgs, null);
- return new ConversationCursor(this, account, cursor);
- }
-
- /**
- * Gets a message cursor over the single message with the given id.
- *
- * @param account get the cursor for messages in this account
- * @param messageId the id of the message
- * @return a cursor over the message
- */
- public MessageCursor getMessageCursorForMessageId(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a message cursor over the messages that match the query. Note that
- * this simply finds all of the messages that match and returns them. It
- * does not return all messages in conversations where any message matches.
- *
- * @param account get the cursor for messages in this account
- * @param query a query in GMail's query syntax. Currently only queries of
- * the form [label:<label>] are supported
- * @return a cursor over the messages
- */
- public MessageCursor getLocalMessageCursorForQuery(String account, String query) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Cursor cursor = mContentResolver.query(uri, MESSAGE_PROJECTION, query, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Gets a cursor over all of the messages in a conversation.
- *
- * @param account get the cursor for messages in this account
- * @param conversationId the id of the converstion to fetch messages for
- * @return a cursor over messages in the conversation
- */
- public MessageCursor getMessageCursorForConversationId(String account, long conversationId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/messages");
- Cursor cursor = mContentResolver.query(
- uri, MESSAGE_PROJECTION, null, null, null);
- return new MessageCursor(this, mContentResolver, account, cursor);
- }
-
- /**
- * Expunge the indicated message. One use of this is to discard drafts.
- *
- * @param account the account of the message id
- * @param messageId the id of the message to expunge
- */
- public void expungeMessage(String account, long messageId) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- mContentResolver.delete(uri, null, null);
- }
-
- /**
- * Adds or removes the label on the conversation.
- *
- * @param account the account of the conversation
- * @param conversationId the conversation
- * @param maxServerMessageId the highest message id to whose labels should be changed. Note that
- * everywhere else in this file messageId means local message id but here you need to use a
- * server message id.
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public void addOrRemoveLabelOnConversation(
- String account, long conversationId, long maxServerMessageId, String label,
- boolean add) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId);
- mContentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_CONVERSATIONS + account + "/"
- + conversationId + "/labels/" + encodedLabel);
- mContentResolver.delete(
- uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId});
- }
- }
-
- /**
- * Adds or removes the label on the message.
- *
- * @param contentResolver the content resolver.
- * @param account the account of the message
- * @param conversationId the conversation containing the message
- * @param messageId the id of the message to whose labels should be changed
- * @param label the label to add or remove
- * @param add true to add the label, false to remove it
- */
- public static void addOrRemoveLabelOnMessage(ContentResolver contentResolver, String account,
- long conversationId, long messageId, String label, boolean add) {
-
- // conversationId is unused but we want to start passing it whereever we pass a message id.
- if (add) {
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId + "/labels");
- ContentValues values = new ContentValues();
- values.put(LabelColumns.CANONICAL_NAME, label);
- contentResolver.insert(uri, values);
- } else {
- String encodedLabel;
- try {
- encodedLabel = URLEncoder.encode(label, "utf-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId
- + "/labels/" + encodedLabel);
- contentResolver.delete(uri, null, null);
- }
- }
-
- /**
- * The mail provider will send an intent when certain changes happen in certain labels.
- * Currently those labels are inbox and voicemail.
- *
- * <p>The intent will have the action ACTION_PROVIDER_CHANGED and the extras mentioned below.
- * The data for the intent will be content://gmail-ls/unread/<name of label>.
- *
- * <p>The goal is to support the following user experience:<ul>
- * <li>When present the new mail indicator reports the number of unread conversations in the
- * inbox (or some other label).</li>
- * <li>When the user views the inbox the indicator is removed immediately. They do not have to
- * read all of the conversations.</li>
- * <li>If more mail arrives the indicator reappears and shows the total number of unread
- * conversations in the inbox.</li>
- * <li>If the user reads the new conversations on the web the indicator disappears on the
- * phone since there is no unread mail in the inbox that the user hasn't seen.</li>
- * <li>The phone should vibrate/etc when it transitions from having no unseen unread inbox
- * mail to having some.</li>
- */
-
- /** The account in which the change occurred. */
- static public final String PROVIDER_CHANGED_EXTRA_ACCOUNT = "account";
-
- /** The number of unread conversations matching the label. */
- static public final String PROVIDER_CHANGED_EXTRA_COUNT = "count";
-
- /** Whether to get the user's attention, perhaps by vibrating. */
- static public final String PROVIDER_CHANGED_EXTRA_GET_ATTENTION = "getAttention";
-
- /**
- * A label that is attached to all of the conversations being notified about. This enables the
- * receiver of a notification to get a list of matching conversations.
- */
- static public final String PROVIDER_CHANGED_EXTRA_TAG_LABEL = "tagLabel";
-
- /**
- * Settings for which conversations should be synced to the phone.
- * Conversations are synced if any message matches any of the following
- * criteria:
- *
- * <ul>
- * <li>the message has a label in the include set</li>
- * <li>the message is no older than conversationAgeDays and has a label in the partial set.
- * </li>
- * <li>also, pending changes on the server: the message has no user-controllable labels.</li>
- * </ul>
- *
- * <p>A user-controllable label is a user-defined label or star, inbox,
- * trash, spam, etc. LABEL_UNREAD is not considered user-controllable.
- */
- public static class Settings {
- public long conversationAgeDays;
- public long maxAttachmentSizeMb;
- public String[] labelsIncluded;
- public String[] labelsPartial;
- }
-
- /**
- * Returns the settings.
- * @param account the account whose setting should be retrieved
- */
- public Settings getSettings(String account) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Settings settings = new Settings();
- Cursor cursor = mContentResolver.query(
- Uri.withAppendedPath(SETTINGS_URI, account), SETTINGS_PROJECTION, null, null, null);
- cursor.moveToNext();
- settings.labelsIncluded = TextUtils.split(cursor.getString(0), SPACE_SEPARATOR_PATTERN);
- settings.labelsPartial = TextUtils.split(cursor.getString(1), SPACE_SEPARATOR_PATTERN);
- settings.conversationAgeDays = Long.parseLong(cursor.getString(2));
- settings.maxAttachmentSizeMb = Long.parseLong(cursor.getString(3));
- cursor.close();
- return settings;
- }
-
- /**
- * Sets the settings. A sync will be scheduled automatically.
- */
- public void setSettings(String account, Settings settings) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- ContentValues values = new ContentValues();
- values.put(
- SettingsColumns.LABELS_INCLUDED,
- TextUtils.join(" ", settings.labelsIncluded));
- values.put(
- SettingsColumns.LABELS_PARTIAL,
- TextUtils.join(" ", settings.labelsPartial));
- values.put(
- SettingsColumns.CONVERSATION_AGE_DAYS,
- settings.conversationAgeDays);
- values.put(
- SettingsColumns.MAX_ATTACHMENET_SIZE_MB,
- settings.maxAttachmentSizeMb);
- mContentResolver.update(Uri.withAppendedPath(SETTINGS_URI, account), values, null, null);
- }
-
- /**
- * Uses sender instructions to build a formatted string.
- *
- * <p>Sender list instructions contain compact information about the sender list. Most work that
- * can be done without knowing how much room will be availble for the sender list is done when
- * creating the instructions.
- *
- * <p>The instructions string consists of tokens separated by SENDER_LIST_SEPARATOR. Here are
- * the tokens, one per line:<ul>
- * <li><tt>n</tt></li>
- * <li><em>int</em>, the number of non-draft messages in the conversation</li>
- * <li><tt>d</tt</li>
- * <li><em>int</em>, the number of drafts in the conversation</li>
- * <li><tt>l</tt></li>
- * <li><em>literal html to be included in the output</em></li>
- * <li><tt>s</tt> indicates that the message is sending (in the outbox without errors)</li>
- * <li><tt>f</tt> indicates that the message failed to send (in the outbox with errors)</li>
- * <li><em>for each message</em><ul>
- * <li><em>int</em>, 0 for read, 1 for unread</li>
- * <li><em>int</em>, the priority of the message. Zero is the most important</li>
- * <li><em>text</em>, the sender text or blank for messages from 'me'</li>
- * </ul></li>
- * <li><tt>e</tt> to indicate that one or more messages have been elided</li>
- *
- * <p>The instructions indicate how many messages and drafts are in the conversation and then
- * describe the most important messages in order, indicating the priority of each message and
- * whether the message is unread.
- *
- * @param instructions instructions as described above
- * @param sb the SpannableStringBuilder to append to
- * @param maxChars the number of characters available to display the text
- * @param unreadStyle the CharacterStyle for unread messages, or null
- * @param draftsStyle the CharacterStyle for draft messages, or null
- * @param sendingString the string to use when there are messages scheduled to be sent
- * @param sendFailedString the string to use when there are messages that mailed to send
- * @param meString the string to use for messages sent by this user
- * @param draftString the string to use for "Draft"
- * @param draftPluralString the string to use for "Drafts"
- */
- public static void getSenderSnippet(
- String instructions, SpannableStringBuilder sb, int maxChars,
- CharacterStyle unreadStyle,
- CharacterStyle draftsStyle,
- CharSequence meString, CharSequence draftString, CharSequence draftPluralString,
- CharSequence sendingString, CharSequence sendFailedString,
- boolean forceAllUnread, boolean forceAllRead) {
- assert !(forceAllUnread && forceAllRead);
- boolean unreadStatusIsForced = forceAllUnread || forceAllRead;
- boolean forcedUnreadStatus = forceAllUnread;
-
- // Measure each fragment. It's ok to iterate over the entire set of fragments because it is
- // never a long list, even if there are many senders.
- final Map<Integer, Integer> priorityToLength = sPriorityToLength;
- priorityToLength.clear();
-
- int maxFoundPriority = Integer.MIN_VALUE;
- int numMessages = 0;
- int numDrafts = 0;
- CharSequence draftsFragment = "";
- CharSequence sendingFragment = "";
- CharSequence sendFailedFragment = "";
-
- sSenderListSplitter.setString(instructions);
- int numFragments = 0;
- String[] fragments = sSenderFragments;
- int currentSize = fragments.length;
- while (sSenderListSplitter.hasNext()) {
- fragments[numFragments++] = sSenderListSplitter.next();
- if (numFragments == currentSize) {
- sSenderFragments = new String[2 * currentSize];
- System.arraycopy(fragments, 0, sSenderFragments, 0, currentSize);
- currentSize *= 2;
- fragments = sSenderFragments;
- }
- }
-
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (Gmail.SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- // ignore
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- numMessages = Integer.valueOf(fragments[i++]);
- } else if (Gmail.SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- String numDraftsString = fragments[i++];
- numDrafts = Integer.parseInt(numDraftsString);
- draftsFragment = numDrafts == 1 ? draftString :
- draftPluralString + " (" + numDraftsString + ")";
- } else if (Gmail.SENDER_LIST_TOKEN_LITERAL.equals(fragment0)) {
- sb.append(Html.fromHtml(fragments[i++]));
- return;
- } else if (Gmail.SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- sendingFragment = sendingString;
- } else if (Gmail.SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- sendFailedFragment = sendFailedString;
- } else {
- String priorityString = fragments[i++];
- CharSequence nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString;
- int priority = Integer.parseInt(priorityString);
- priorityToLength.put(priority, nameString.length());
- maxFoundPriority = Math.max(maxFoundPriority, priority);
- }
- }
- String numMessagesFragment =
- (numMessages != 0) ? " (" + Integer.toString(numMessages + numDrafts) + ")" : "";
-
- // Don't allocate fixedFragment unless we need it
- SpannableStringBuilder fixedFragment = null;
- int fixedFragmentLength = 0;
- if (draftsFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- fixedFragment.append(draftsFragment);
- if (draftsStyle != null) {
- fixedFragment.setSpan(
- CharacterStyle.wrap(draftsStyle),
- 0, fixedFragment.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- if (sendingFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendingFragment);
- }
- if (sendFailedFragment.length() != 0) {
- if (fixedFragment == null) {
- fixedFragment = new SpannableStringBuilder();
- }
- if (fixedFragment.length() != 0) fixedFragment.append(", ");
- fixedFragment.append(sendFailedFragment);
- }
-
- if (fixedFragment != null) {
- fixedFragmentLength = fixedFragment.length();
- }
-
- final boolean normalMessagesExist =
- numMessagesFragment.length() != 0 || maxFoundPriority != Integer.MIN_VALUE;
- String preFixedFragement = "";
- if (normalMessagesExist && fixedFragmentLength != 0) {
- preFixedFragement = ", ";
- }
- int maxPriorityToInclude = -1; // inclusive
- int numCharsUsed =
- numMessagesFragment.length() + preFixedFragement.length() + fixedFragmentLength;
- int numSendersUsed = 0;
- while (maxPriorityToInclude < maxFoundPriority) {
- if (priorityToLength.containsKey(maxPriorityToInclude + 1)) {
- int length = numCharsUsed + priorityToLength.get(maxPriorityToInclude + 1);
- if (numCharsUsed > 0) length += 2;
- // We must show at least two senders if they exist. If we don't have space for both
- // then we will truncate names.
- if (length > maxChars && numSendersUsed >= 2) {
- break;
- }
- numCharsUsed = length;
- numSendersUsed++;
- }
- maxPriorityToInclude++;
- }
-
- int numCharsToRemovePerWord = 0;
- if (numCharsUsed > maxChars) {
- numCharsToRemovePerWord = (numCharsUsed - maxChars) / numSendersUsed;
- }
-
- boolean elided = false;
- for (int i = 0; i < numFragments;) {
- String fragment0 = fragments[i++];
- if ("".equals(fragment0)) {
- // This should be the final fragment.
- } else if (SENDER_LIST_TOKEN_ELIDED.equals(fragment0)) {
- elided = true;
- } else if (SENDER_LIST_TOKEN_NUM_MESSAGES.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_NUM_DRAFTS.equals(fragment0)) {
- i++;
- } else if (SENDER_LIST_TOKEN_SENDING.equals(fragment0)) {
- } else if (SENDER_LIST_TOKEN_SEND_FAILED.equals(fragment0)) {
- } else {
- final String unreadString = fragment0;
- final String priorityString = fragments[i++];
- String nameString = fragments[i++];
- if (nameString.length() == 0) nameString = meString.toString();
- if (numCharsToRemovePerWord != 0) {
- nameString = nameString.substring(
- 0, Math.max(nameString.length() - numCharsToRemovePerWord, 0));
- }
- final boolean unread = unreadStatusIsForced
- ? forcedUnreadStatus : Integer.parseInt(unreadString) != 0;
- final int priority = Integer.parseInt(priorityString);
- if (priority <= maxPriorityToInclude) {
- if (sb.length() != 0) {
- sb.append(elided ? " .. " : ", ");
- }
- elided = false;
- int pos = sb.length();
- sb.append(nameString);
- if (unread && unreadStyle != null) {
- sb.setSpan(CharacterStyle.wrap(unreadStyle),
- pos, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- elided = true;
- }
- }
- }
- sb.append(numMessagesFragment);
- if (fixedFragmentLength != 0) {
- sb.append(preFixedFragement);
- sb.append(fixedFragment);
- }
- }
-
- /**
- * This is a cursor that only defines methods to move throught the results
- * and register to hear about changes. All access to the data is left to
- * subinterfaces.
- */
- public static class MailCursor extends ContentObserver {
-
- // A list of observers of this cursor.
- private Set<MailCursorObserver> mObservers;
-
- // Updated values are accumulated here before being written out if the
- // cursor is asked to persist the changes.
- private ContentValues mUpdateValues;
-
- protected Cursor mCursor;
- protected String mAccount;
-
- public Cursor getCursor() {
- return mCursor;
- }
-
- /**
- * Constructs the MailCursor given a regular cursor, registering as a
- * change observer of the cursor.
- * @param account the account the cursor is associated with
- * @param cursor the underlying cursor
- */
- protected MailCursor(String account, Cursor cursor) {
- super(new Handler());
- mObservers = new HashSet<MailCursorObserver>();
- mCursor = cursor;
- mAccount = account;
- if (mCursor != null) mCursor.registerContentObserver(this);
- }
-
- /**
- * Gets the account associated with this cursor.
- * @return the account.
- */
- public String getAccount() {
- return mAccount;
- }
-
- protected void checkThread() {
- // Turn this on when activity code no longer runs in the sync thread
- // after notifications of changes.
-// Thread currentThread = Thread.currentThread();
-// if (currentThread != mThread) {
-// throw new RuntimeException("Accessed from the wrong thread");
-// }
- }
-
- /**
- * Lazily constructs a map of update values to apply to the database
- * if requested. This map is cleared out when we move to a different
- * item in the result set.
- *
- * @return a map of values to be applied by an update.
- */
- protected ContentValues getUpdateValues() {
- if (mUpdateValues == null) {
- mUpdateValues = new ContentValues();
- }
- return mUpdateValues;
- }
-
- /**
- * Called whenever mCursor is changed to point to a different row.
- * Subclasses should override this if they need to clear out state
- * when this happens.
- *
- * Subclasses must call the inherited version if they override this.
- */
- protected void onCursorPositionChanged() {
- mUpdateValues = null;
- }
-
- // ********* MailCursor
-
- /**
- * Returns the numbers of rows in the cursor.
- *
- * @return the number of rows in the cursor.
- */
- final public int count() {
- if (mCursor != null) {
- return mCursor.getCount();
- } else {
- return 0;
- }
- }
-
- /**
- * @return the current position of this cursor, or -1 if this cursor
- * has not been initialized.
- */
- final public int position() {
- if (mCursor != null) {
- return mCursor.getPosition();
- } else {
- return -1;
- }
- }
-
- /**
- * Move the cursor to an absolute position. The valid
- * range of vaues is -1 &lt;= position &lt;= count.
- *
- * <p>This method will return true if the request destination was
- * reachable, otherwise it returns false.
- *
- * @param position the zero-based position to move to.
- * @return whether the requested move fully succeeded.
- */
- final public boolean moveTo(int position) {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToPosition(position);
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Move the cursor to the next row.
- *
- * <p>This method will return false if the cursor is already past the
- * last entry in the result set.
- *
- * @return whether the move succeeded.
- */
- final public boolean next() {
- checkCursor();
- checkThread();
- boolean moved = mCursor.moveToNext();
- if (moved) onCursorPositionChanged();
- return moved;
- }
-
- /**
- * Release all resources and locks associated with the cursor. The
- * cursor will not be valid after this function is called.
- */
- final public void release() {
- if (mCursor != null) {
- mCursor.unregisterContentObserver(this);
- mCursor.deactivate();
- }
- }
-
- final public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
- }
-
- final public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
- }
-
- final public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
- }
-
- final public void unregisterDataSetObserver(DataSetObserver observer) {
- mCursor.unregisterDataSetObserver(observer);
- }
-
- /**
- * Register an observer to hear about changes to the cursor.
- *
- * @param observer the observer to register
- */
- final public void registerObserver(MailCursorObserver observer) {
- mObservers.add(observer);
- }
-
- /**
- * Unregister an observer.
- *
- * @param observer the observer to unregister
- */
- final public void unregisterObserver(MailCursorObserver observer) {
- mObservers.remove(observer);
- }
-
- // ********* ContentObserver
-
- @Override
- final public boolean deliverSelfNotifications() {
- return false;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- if (DEBUG) {
- Log.d(TAG, "MailCursor is notifying " + mObservers.size() + " observers");
- }
- for (MailCursorObserver o: mObservers) {
- o.onCursorChanged(this);
- }
- }
-
- protected void checkCursor() {
- if (mCursor == null) {
- throw new IllegalStateException(
- "cannot read from an insertion cursor");
- }
- }
-
- /**
- * Returns the string value of the column, or "" if the value is null.
- */
- protected String getStringInColumn(int columnIndex) {
- checkCursor();
- return toNonnullString(mCursor.getString(columnIndex));
- }
- }
-
- /**
- * A MailCursor observer is notified of changes to the result set of a
- * cursor.
- */
- public interface MailCursorObserver {
-
- /**
- * Called when the result set of a cursor has changed.
- *
- * @param cursor the cursor whose result set has changed.
- */
- void onCursorChanged(MailCursor cursor);
- }
-
- /**
- * A cursor over labels.
- */
- public final class LabelCursor extends MailCursor {
-
- private int mNameIndex;
- private int mNumConversationsIndex;
- private int mNumUnreadConversationsIndex;
-
- private LabelCursor(String account, Cursor cursor) {
- super(account, cursor);
-
- mNameIndex = mCursor.getColumnIndexOrThrow(LabelColumns.CANONICAL_NAME);
- mNumConversationsIndex =
- mCursor.getColumnIndexOrThrow(LabelColumns.NUM_CONVERSATIONS);
- mNumUnreadConversationsIndex = mCursor.getColumnIndexOrThrow(
- LabelColumns.NUM_UNREAD_CONVERSATIONS);
- }
-
- /**
- * Gets the canonical name of the current label.
- *
- * @return the current label's name.
- */
- public String getName() {
- return getStringInColumn(mNameIndex);
- }
-
- /**
- * Gets the number of conversations with this label.
- *
- * @return the number of conversations with this label.
- */
- public int getNumConversations() {
- return mCursor.getInt(mNumConversationsIndex);
- }
-
- /**
- * Gets the number of unread conversations with this label.
- *
- * @return the number of unread conversations with this label.
- */
- public int getNumUnreadConversations() {
- return mCursor.getInt(mNumUnreadConversationsIndex);
- }
- }
-
- /**
- * This is a map of labels. TODO: make it observable.
- */
- public static final class LabelMap extends Observable {
- private final static ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
-
- private ContentQueryMap mQueryMap;
- private SortedSet<String> mSortedUserLabels;
- private Map<String, Long> mCanonicalNameToId;
-
- private long mLabelIdSent;
- private long mLabelIdInbox;
- private long mLabelIdDraft;
- private long mLabelIdUnread;
- private long mLabelIdTrash;
- private long mLabelIdSpam;
- private long mLabelIdStarred;
- private long mLabelIdChat;
- private long mLabelIdVoicemail;
- private long mLabelIdIgnored;
- private long mLabelIdVoicemailInbox;
- private long mLabelIdCached;
- private long mLabelIdOutbox;
-
- private boolean mLabelsSynced = false;
-
- public LabelMap(ContentResolver contentResolver, String account, boolean keepUpdated) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- Cursor cursor = contentResolver.query(
- Uri.withAppendedPath(LABELS_URI, account), LABEL_PROJECTION, null, null, null);
- init(cursor, keepUpdated);
- }
-
- public LabelMap(Cursor cursor, boolean keepUpdated) {
- init(cursor, keepUpdated);
- }
-
- private void init(Cursor cursor, boolean keepUpdated) {
- mQueryMap = new ContentQueryMap(cursor, BaseColumns._ID, keepUpdated, null);
- mSortedUserLabels = new TreeSet<String>(java.text.Collator.getInstance());
- mCanonicalNameToId = Maps.newHashMap();
- updateDataStructures();
- mQueryMap.addObserver(new Observer() {
- public void update(Observable observable, Object data) {
- updateDataStructures();
- setChanged();
- notifyObservers();
- }
- });
- }
-
- /**
- * @return whether at least some labels have been synced.
- */
- public boolean labelsSynced() {
- return mLabelsSynced;
- }
-
- /**
- * Updates the data structures that are maintained separately from mQueryMap after the query
- * map has changed.
- */
- private void updateDataStructures() {
- mSortedUserLabels.clear();
- mCanonicalNameToId.clear();
- for (Map.Entry<String, ContentValues> row : mQueryMap.getRows().entrySet()) {
- long labelId = Long.valueOf(row.getKey());
- String canonicalName = row.getValue().getAsString(LabelColumns.CANONICAL_NAME);
- if (isLabelUserDefined(canonicalName)) {
- mSortedUserLabels.add(canonicalName);
- }
- mCanonicalNameToId.put(canonicalName, labelId);
-
- if (LABEL_SENT.equals(canonicalName)) {
- mLabelIdSent = labelId;
- } else if (LABEL_INBOX.equals(canonicalName)) {
- mLabelIdInbox = labelId;
- } else if (LABEL_DRAFT.equals(canonicalName)) {
- mLabelIdDraft = labelId;
- } else if (LABEL_UNREAD.equals(canonicalName)) {
- mLabelIdUnread = labelId;
- } else if (LABEL_TRASH.equals(canonicalName)) {
- mLabelIdTrash = labelId;
- } else if (LABEL_SPAM.equals(canonicalName)) {
- mLabelIdSpam = labelId;
- } else if (LABEL_STARRED.equals(canonicalName)) {
- mLabelIdStarred = labelId;
- } else if (LABEL_CHAT.equals(canonicalName)) {
- mLabelIdChat = labelId;
- } else if (LABEL_IGNORED.equals(canonicalName)) {
- mLabelIdIgnored = labelId;
- } else if (LABEL_VOICEMAIL.equals(canonicalName)) {
- mLabelIdVoicemail = labelId;
- } else if (LABEL_VOICEMAIL_INBOX.equals(canonicalName)) {
- mLabelIdVoicemailInbox = labelId;
- } else if (LABEL_CACHED.equals(canonicalName)) {
- mLabelIdCached = labelId;
- } else if (LABEL_OUTBOX.equals(canonicalName)) {
- mLabelIdOutbox = labelId;
- }
- mLabelsSynced = mLabelIdSent != 0
- && mLabelIdInbox != 0
- && mLabelIdDraft != 0
- && mLabelIdUnread != 0
- && mLabelIdTrash != 0
- && mLabelIdSpam != 0
- && mLabelIdStarred != 0
- && mLabelIdChat != 0
- && mLabelIdIgnored != 0
- && mLabelIdVoicemail != 0;
- }
- }
-
- public long getLabelIdSent() {
- checkLabelsSynced();
- return mLabelIdSent;
- }
-
- public long getLabelIdInbox() {
- checkLabelsSynced();
- return mLabelIdInbox;
- }
-
- public long getLabelIdDraft() {
- checkLabelsSynced();
- return mLabelIdDraft;
- }
-
- public long getLabelIdUnread() {
- checkLabelsSynced();
- return mLabelIdUnread;
- }
-
- public long getLabelIdTrash() {
- checkLabelsSynced();
- return mLabelIdTrash;
- }
-
- public long getLabelIdSpam() {
- checkLabelsSynced();
- return mLabelIdSpam;
- }
-
- public long getLabelIdStarred() {
- checkLabelsSynced();
- return mLabelIdStarred;
- }
-
- public long getLabelIdChat() {
- checkLabelsSynced();
- return mLabelIdChat;
- }
-
- public long getLabelIdIgnored() {
- checkLabelsSynced();
- return mLabelIdIgnored;
- }
-
- public long getLabelIdVoicemail() {
- checkLabelsSynced();
- return mLabelIdVoicemail;
- }
-
- public long getLabelIdVoicemailInbox() {
- checkLabelsSynced();
- return mLabelIdVoicemailInbox;
- }
-
- public long getLabelIdCached() {
- checkLabelsSynced();
- return mLabelIdCached;
- }
-
- public long getLabelIdOutbox() {
- checkLabelsSynced();
- return mLabelIdOutbox;
- }
-
- private void checkLabelsSynced() {
- if (!labelsSynced()) {
- throw new IllegalStateException("LabelMap not initalized");
- }
- }
-
- /** Returns the list of user-defined labels in alphabetical order. */
- public SortedSet<String> getSortedUserLabels() {
- return mSortedUserLabels;
- }
-
- private static final List<String> SORTED_USER_MEANINGFUL_SYSTEM_LABELS =
- Lists.newArrayList(
- LABEL_INBOX, LABEL_STARRED, LABEL_CHAT, LABEL_SENT,
- LABEL_OUTBOX, LABEL_DRAFT, LABEL_ALL,
- LABEL_SPAM, LABEL_TRASH);
-
-
- private static final Set<String> USER_MEANINGFUL_SYSTEM_LABELS_SET =
- Sets.newHashSet(
- SORTED_USER_MEANINGFUL_SYSTEM_LABELS.toArray(
- new String[]{}));
-
- public static List<String> getSortedUserMeaningfulSystemLabels() {
- return SORTED_USER_MEANINGFUL_SYSTEM_LABELS;
- }
-
- public static Set<String> getUserMeaningfulSystemLabelsSet() {
- return USER_MEANINGFUL_SYSTEM_LABELS_SET;
- }
-
- /**
- * If you are ever tempted to remove outbox or draft from this set make sure you have a
- * way to stop draft and outbox messages from getting purged before they are sent to the
- * server.
- */
- private static final Set<String> FORCED_INCLUDED_LABELS =
- Sets.newHashSet(LABEL_OUTBOX, LABEL_DRAFT);
-
- public static Set<String> getForcedIncludedLabels() {
- return FORCED_INCLUDED_LABELS;
- }
-
- private static final Set<String> FORCED_INCLUDED_OR_PARTIAL_LABELS =
- Sets.newHashSet(LABEL_INBOX);
-
- public static Set<String> getForcedIncludedOrPartialLabels() {
- return FORCED_INCLUDED_OR_PARTIAL_LABELS;
- }
-
- private static final Set<String> FORCED_UNSYNCED_LABELS =
- Sets.newHashSet(LABEL_ALL, LABEL_CHAT, LABEL_SPAM, LABEL_TRASH);
-
- public static Set<String> getForcedUnsyncedLabels() {
- return FORCED_UNSYNCED_LABELS;
- }
-
- /**
- * Returns the number of conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumConversations(String label) {
- return getNumConversations(getLabelId(label));
- }
-
- /** Returns the number of conversation with a given label. */
- public int getNumConversations(long labelId) {
- return getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_CONVERSATIONS);
- }
-
- /**
- * Returns the number of unread conversation with a given label.
- * @deprecated Use {@link #getLabelId} instead.
- */
- @Deprecated
- public int getNumUnreadConversations(String label) {
- return getNumUnreadConversations(getLabelId(label));
- }
-
- /** Returns the number of unread conversation with a given label. */
- public int getNumUnreadConversations(long labelId) {
- Integer unreadConversations =
- getLabelIdValues(labelId).getAsInteger(LabelColumns.NUM_UNREAD_CONVERSATIONS);
- // There seems to be a race condition here that can get the label maps into a bad
- // state and lose state on a particular label.
- int result = 0;
- if (unreadConversations != null) {
- result = unreadConversations < 0 ? 0 : unreadConversations;
- }
-
- return result;
- }
-
- /**
- * @return the canonical name for a label
- */
- public String getCanonicalName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.CANONICAL_NAME);
- }
-
- /**
- * @return the human name for a label
- */
- public String getName(long labelId) {
- return getLabelIdValues(labelId).getAsString(LabelColumns.NAME);
- }
-
- /**
- * @return whether a given label is known
- */
- public boolean hasLabel(long labelId) {
- return mQueryMap.getRows().containsKey(Long.toString(labelId));
- }
-
- /**
- * @return returns the id of a label given the canonical name
- * @deprecated this is only needed because most of the UI uses label names instead of ids
- */
- public long getLabelId(String canonicalName) {
- if (mCanonicalNameToId.containsKey(canonicalName)) {
- return mCanonicalNameToId.get(canonicalName);
- } else {
- throw new IllegalArgumentException("Unknown canonical name: " + canonicalName);
- }
- }
-
- private ContentValues getLabelIdValues(long labelId) {
- final ContentValues values = mQueryMap.getValues(Long.toString(labelId));
- if (values != null) {
- return values;
- } else {
- return EMPTY_CONTENT_VALUES;
- }
- }
-
- /** Force the map to requery. This should not be necessary outside tests. */
- public void requery() {
- mQueryMap.requery();
- }
-
- public void close() {
- mQueryMap.close();
- }
- }
-
- private Map<String, Gmail.LabelMap> mLabelMaps = Maps.newHashMap();
-
- public LabelMap getLabelMap(String account) {
- Gmail.LabelMap labelMap = mLabelMaps.get(account);
- if (labelMap == null) {
- labelMap = new Gmail.LabelMap(mContentResolver, account, true /* keepUpdated */);
- mLabelMaps.put(account, labelMap);
- }
- return labelMap;
- }
-
- public enum PersonalLevel {
- NOT_TO_ME(0),
- TO_ME_AND_OTHERS(1),
- ONLY_TO_ME(2);
-
- private int mLevel;
-
- PersonalLevel(int level) {
- mLevel = level;
- }
-
- public int toInt() {
- return mLevel;
- }
-
- public static PersonalLevel fromInt(int level) {
- switch (level) {
- case 0: return NOT_TO_ME;
- case 1: return TO_ME_AND_OTHERS;
- case 2: return ONLY_TO_ME;
- default:
- throw new IllegalArgumentException(
- level + " is not a personal level");
- }
- }
- }
-
- /**
- * Indicates a version of an attachment.
- */
- public enum AttachmentRendition {
- /**
- * The full version of an attachment if it can be handled on the device, otherwise the
- * preview.
- */
- BEST,
-
- /** A smaller or simpler version of the attachment, such as a scaled-down image or an HTML
- * version of a document. Not always available.
- */
- SIMPLE,
- }
-
- /**
- * The columns that can be requested when querying an attachment's download URI. See
- * getAttachmentDownloadUri.
- */
- public static final class AttachmentColumns implements BaseColumns {
-
- /** Contains a STATUS value from {@link android.provider.Downloads} */
- public static final String STATUS = "status";
-
- /**
- * The name of the file to open (with ContentProvider.open). If this is empty then continue
- * to use the attachment's URI.
- *
- * TODO: I'm not sure that we need this. See the note in CL 66853-p9.
- */
- public static final String FILENAME = "filename";
- }
-
- /**
- * We track where an attachment came from so that we know how to download it and include it
- * in new messages.
- */
- public enum AttachmentOrigin {
- /** Extras are "<conversationId>-<messageId>-<partId>". */
- SERVER_ATTACHMENT,
- /** Extras are "<path>". */
- LOCAL_FILE;
-
- private static final String SERVER_EXTRAS_SEPARATOR = "_";
-
- public static String serverExtras(
- long conversationId, long messageId, String partId) {
- return conversationId + SERVER_EXTRAS_SEPARATOR
- + messageId + SERVER_EXTRAS_SEPARATOR + partId;
- }
-
- /**
- * @param extras extras as returned by serverExtras
- * @return an array of conversationId, messageId, partId (all as strings)
- */
- public static String[] splitServerExtras(String extras) {
- return TextUtils.split(extras, SERVER_EXTRAS_SEPARATOR);
- }
-
- public static String localFileExtras(Uri path) {
- return path.toString();
- }
- }
-
- public static final class Attachment {
- /** Identifies the attachment uniquely when combined wih a message id.*/
- public String partId;
-
- /** The intended filename of the attachment.*/
- public String name;
-
- /** The native content type.*/
- public String contentType;
-
- /** The size of the attachment in its native form.*/
- public int size;
-
- /**
- * The content type of the simple version of the attachment. Blank if no simple version is
- * available.
- */
- public String simpleContentType;
-
- public AttachmentOrigin origin;
-
- public String originExtras;
-
- public String toJoinedString() {
- return TextUtils.join(
- "|", Lists.newArrayList(partId == null ? "" : partId,
- name.replace("|", ""), contentType,
- size, simpleContentType,
- origin.toString(), originExtras));
- }
-
- public static Attachment parseJoinedString(String joinedString) {
- String[] fragments = TextUtils.split(joinedString, "\\|");
- int i = 0;
- Attachment attachment = new Attachment();
- attachment.partId = fragments[i++];
- if (TextUtils.isEmpty(attachment.partId)) {
- attachment.partId = null;
- }
- attachment.name = fragments[i++];
- attachment.contentType = fragments[i++];
- attachment.size = Integer.parseInt(fragments[i++]);
- attachment.simpleContentType = fragments[i++];
- attachment.origin = AttachmentOrigin.valueOf(fragments[i++]);
- attachment.originExtras = fragments[i++];
- return attachment;
- }
- }
-
- /**
- * Any given attachment can come in two different renditions (see
- * {@link android.provider.Gmail.AttachmentRendition}) and can be saved to the sd card or to a
- * cache. The gmail provider automatically syncs some attachments to the cache. Other
- * attachments can be downloaded on demand. Attachments in the cache will be purged as needed to
- * save space. Attachments on the SD card must be managed by the user or other software.
- *
- * @param account which account to use
- * @param messageId the id of the mesage with the attachment
- * @param attachment the attachment
- * @param rendition the desired rendition
- * @param saveToSd whether the attachment should be saved to (or loaded from) the sd card or
- * @return the URI to ask the content provider to open in order to open an attachment.
- */
- public static Uri getAttachmentUri(
- String account, long messageId, Attachment attachment,
- AttachmentRendition rendition, boolean saveToSd) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("account is empty");
- }
- if (attachment.origin == AttachmentOrigin.LOCAL_FILE) {
- return Uri.parse(attachment.originExtras);
- } else {
- return Uri.parse(
- AUTHORITY_PLUS_MESSAGES).buildUpon()
- .appendPath(account).appendPath(Long.toString(messageId))
- .appendPath("attachments").appendPath(attachment.partId)
- .appendPath(rendition.toString())
- .appendPath(Boolean.toString(saveToSd))
- .build();
- }
- }
-
- /**
- * Return the URI to query in order to find out whether an attachment is downloaded.
- *
- * <p>Querying this will also start a download if necessary. The cursor returned by querying
- * this URI can contain the columns in {@link android.provider.Gmail.AttachmentColumns}.
- *
- * <p>Deleting this URI will cancel the download if it was not started automatically by the
- * provider. It will also remove bookkeeping for saveToSd downloads.
- *
- * @param attachmentUri the attachment URI as returned by getAttachmentUri. The URI's authority
- * Gmail.AUTHORITY. If it is not then you should open the file directly.
- */
- public static Uri getAttachmentDownloadUri(Uri attachmentUri) {
- if (!"content".equals(attachmentUri.getScheme())) {
- throw new IllegalArgumentException("Uri's scheme must be 'content': " + attachmentUri);
- }
- return attachmentUri.buildUpon().appendPath("download").build();
- }
-
- public enum CursorStatus {
- LOADED,
- LOADING,
- ERROR, // A network error occurred.
- }
-
- /**
- * A cursor over messages.
- */
- public static final class MessageCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private ContentResolver mContentResolver;
-
- /**
- * Only valid if mCursor == null, in which case we are inserting a new
- * message.
- */
- long mInReplyToLocalMessageId;
- boolean mPreserveAttachments;
-
- private int mIdIndex;
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mToIndex;
- private int mCcIndex;
- private int mBccIndex;
- private int mReplyToIndex;
- private int mDateSentMsIndex;
- private int mDateReceivedMsIndex;
- private int mListInfoIndex;
- private int mPersonalLevelIndex;
- private int mBodyIndex;
- private int mBodyEmbedsExternalResourcesIndex;
- private int mLabelIdsIndex;
- private int mJoinedAttachmentInfosIndex;
- private int mErrorIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newMessageLabelIdsSplitter();
-
- public MessageCursor(Gmail gmail, ContentResolver cr, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
- if (cursor == null) {
- throw new IllegalArgumentException(
- "null cursor passed to MessageCursor()");
- }
-
- mContentResolver = cr;
-
- mIdIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ID);
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.CONVERSATION_ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(MessageColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(MessageColumns.FROM);
- mToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.TO);
- mCcIndex = mCursor.getColumnIndexOrThrow(MessageColumns.CC);
- mBccIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BCC);
- mReplyToIndex = mCursor.getColumnIndexOrThrow(MessageColumns.REPLY_TO);
- mDateSentMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_SENT_MS);
- mDateReceivedMsIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.DATE_RECEIVED_MS);
- mListInfoIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LIST_INFO);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.PERSONAL_LEVEL);
- mBodyIndex = mCursor.getColumnIndexOrThrow(MessageColumns.BODY);
- mBodyEmbedsExternalResourcesIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.EMBEDS_EXTERNAL_RESOURCES);
- mLabelIdsIndex = mCursor.getColumnIndexOrThrow(MessageColumns.LABEL_IDS);
- mJoinedAttachmentInfosIndex =
- mCursor.getColumnIndexOrThrow(MessageColumns.JOINED_ATTACHMENT_INFOS);
- mErrorIndex = mCursor.getColumnIndexOrThrow(MessageColumns.ERROR);
-
- mInReplyToLocalMessageId = 0;
- mPreserveAttachments = false;
- }
-
- protected MessageCursor(ContentResolver cr, String account, long inReplyToMessageId,
- boolean preserveAttachments) {
- super(account, null);
- mContentResolver = cr;
- mInReplyToLocalMessageId = inReplyToMessageId;
- mPreserveAttachments = preserveAttachments;
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the message id of the current message. Note that this is an
- * immutable local message (not, for example, GMail's message id, which
- * is immutable).
- *
- * @return the message's id
- */
- public long getMessageId() {
- checkCursor();
- return mCursor.getLong(mIdIndex);
- }
-
- /**
- * Gets the message's conversation id. This must be immutable. (For
- * example, with GMail this should be the original conversation id
- * rather than the default notion of converation id.)
- *
- * @return the message's conversation id
- */
- public long getConversationId() {
- checkCursor();
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Gets the message's subject.
- *
- * @return the message's subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the message's snippet (the short piece of the body). The snippet
- * is generated from the body and cannot be set directly.
- *
- * @return the message's snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Gets the message's from address.
- *
- * @return the message's from address
- */
- public String getFromAddress() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Returns the addresses for the key, if it has been updated, or index otherwise.
- */
- private String[] getAddresses(String key, int index) {
- ContentValues updated = getUpdateValues();
- String addresses;
- if (updated.containsKey(key)) {
- addresses = (String)getUpdateValues().get(key);
- } else {
- addresses = getStringInColumn(index);
- }
-
- return TextUtils.split(addresses, EMAIL_SEPARATOR_PATTERN);
- }
-
- /**
- * Gets the message's to addresses.
- * @return the message's to addresses
- */
- public String[] getToAddresses() {
- return getAddresses(MessageColumns.TO, mToIndex);
- }
-
- /**
- * Gets the message's cc addresses.
- * @return the message's cc addresses
- */
- public String[] getCcAddresses() {
- return getAddresses(MessageColumns.CC, mCcIndex);
- }
-
- /**
- * Gets the message's bcc addresses.
- * @return the message's bcc addresses
- */
- public String[] getBccAddresses() {
- return getAddresses(MessageColumns.BCC, mBccIndex);
- }
-
- /**
- * Gets the message's replyTo address.
- *
- * @return the message's replyTo address
- */
- public String[] getReplyToAddress() {
- return TextUtils.split(getStringInColumn(mReplyToIndex), EMAIL_SEPARATOR_PATTERN);
- }
-
- public long getDateSentMs() {
- checkCursor();
- return mCursor.getLong(mDateSentMsIndex);
- }
-
- public long getDateReceivedMs() {
- checkCursor();
- return mCursor.getLong(mDateReceivedMsIndex);
- }
-
- public String getListInfo() {
- return getStringInColumn(mListInfoIndex);
- }
-
- public PersonalLevel getPersonalLevel() {
- checkCursor();
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @deprecated Always returns true.
- */
- @Deprecated
- public boolean getExpanded() {
- return true;
- }
-
- /**
- * Gets the message's body.
- *
- * @return the message's body
- */
- public String getBody() {
- return getStringInColumn(mBodyIndex);
- }
-
- /**
- * @return whether the message's body contains embedded references to external resources. In
- * that case the resources should only be displayed if the user explicitly asks for them to
- * be
- */
- public boolean getBodyEmbedsExternalResources() {
- checkCursor();
- return mCursor.getInt(mBodyEmbedsExternalResourcesIndex) != 0;
- }
-
- /**
- * @return a copy of the set of label ids
- */
- public Set<Long> getLabelIds() {
- String labelNames = mCursor.getString(mLabelIdsIndex);
- mLabelIdsSplitter.setString(labelNames);
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * Adds a label to a message (if add is true) or removes it (if add is
- * false).
- *
- * @param label the label to add or remove
- * @param add whether to add or remove the label
- */
- public void addOrRemoveLabel(String label, boolean add) {
- addOrRemoveLabelOnMessage(mContentResolver, mAccount, getConversationId(),
- getMessageId(), label, add);
- }
-
- public ArrayList<Attachment> getAttachmentInfos() {
- ArrayList<Attachment> attachments = Lists.newArrayList();
-
- String joinedAttachmentInfos = mCursor.getString(mJoinedAttachmentInfosIndex);
- if (joinedAttachmentInfos != null) {
- for (String joinedAttachmentInfo :
- TextUtils.split(joinedAttachmentInfos, ATTACHMENT_INFO_SEPARATOR_PATTERN)) {
-
- Attachment attachment = Attachment.parseJoinedString(joinedAttachmentInfo);
- attachments.add(attachment);
- }
- }
- return attachments;
- }
-
- /**
- * @return the error text for the message. Error text gets set if the server rejects a
- * message that we try to save or send. If there is error text then the message is no longer
- * scheduled to be saved or sent. Calling save() or send() will clear any error as well as
- * scheduling another atempt to save or send the message.
- */
- public String getErrorText() {
- return mCursor.getString(mErrorIndex);
- }
- }
-
- /**
- * A helper class for creating or updating messags. Use the putXxx methods to provide initial or
- * new values for the message. Then save or send the message. To save or send an existing
- * message without making other changes to it simply provide an emty ContentValues.
- */
- public static class MessageModification {
-
- /**
- * Sets the message's subject. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param subject the new subject
- */
- public static void putSubject(ContentValues values, String subject) {
- values.put(MessageColumns.SUBJECT, subject);
- }
-
- /**
- * Sets the message's to address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param toAddresses the new to addresses
- */
- public static void putToAddresses(ContentValues values, String[] toAddresses) {
- values.put(MessageColumns.TO, TextUtils.join(EMAIL_SEPARATOR, toAddresses));
- }
-
- /**
- * Sets the message's cc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param ccAddresses the new cc addresses
- */
- public static void putCcAddresses(ContentValues values, String[] ccAddresses) {
- values.put(MessageColumns.CC, TextUtils.join(EMAIL_SEPARATOR, ccAddresses));
- }
-
- /**
- * Sets the message's bcc address. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param bccAddresses the new bcc addresses
- */
- public static void putBccAddresses(ContentValues values, String[] bccAddresses) {
- values.put(MessageColumns.BCC, TextUtils.join(EMAIL_SEPARATOR, bccAddresses));
- }
-
- /**
- * Saves a new body for the message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param body the new body of the message
- */
- public static void putBody(ContentValues values, String body) {
- values.put(MessageColumns.BODY, body);
- }
-
- /**
- * Sets the attachments on a message. Only valid for drafts.
- *
- * @param values the ContentValues that will be used to create or update the message
- * @param attachments
- */
- public static void putAttachments(ContentValues values, List<Attachment> attachments) {
- values.put(
- MessageColumns.JOINED_ATTACHMENT_INFOS, joinedAttachmentsString(attachments));
- }
-
- /**
- * Create a new message and save it as a draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param values the values for the new message
- * @param refMessageId the message that is being replied to or forwarded
- * @param save whether to save or send the message
- * @return the id of the new message
- */
- public static long sendOrSaveNewMessage(
- ContentResolver contentResolver, String account,
- ContentValues values, long refMessageId, boolean save) {
- values.put(MessageColumns.FAKE_SAVE, save);
- values.put(MessageColumns.FAKE_REF_MESSAGE_ID, refMessageId);
- Uri uri = Uri.parse(AUTHORITY_PLUS_MESSAGES + account + "/");
- Uri result = contentResolver.insert(uri, values);
- return ContentUris.parseId(result);
- }
-
- /**
- * Update an existing draft and save it as a new draft or send it.
- *
- * @param contentResolver the content resolver to use
- * @param account the account to use
- * @param messageId the id of the message to update
- * @param updateValues the values to change. Unspecified fields will not be altered
- * @param save whether to resave the message as a draft or send it
- */
- public static void sendOrSaveExistingMessage(
- ContentResolver contentResolver, String account, long messageId,
- ContentValues updateValues, boolean save) {
- updateValues.put(MessageColumns.FAKE_SAVE, save);
- updateValues.put(MessageColumns.FAKE_REF_MESSAGE_ID, 0);
- Uri uri = Uri.parse(
- AUTHORITY_PLUS_MESSAGES + account + "/" + messageId);
- contentResolver.update(uri, updateValues, null, null);
- }
-
- /**
- * The string produced here is parsed by Gmail.MessageCursor#getAttachmentInfos.
- */
- public static String joinedAttachmentsString(List<Gmail.Attachment> attachments) {
- StringBuilder attachmentsSb = new StringBuilder();
- for (Gmail.Attachment attachment : attachments) {
- if (attachmentsSb.length() != 0) {
- attachmentsSb.append(Gmail.ATTACHMENT_INFO_SEPARATOR);
- }
- attachmentsSb.append(attachment.toJoinedString());
- }
- return attachmentsSb.toString();
- }
-
- }
-
- /**
- * A cursor over conversations.
- *
- * "Conversation" refers to the information needed to populate a list of
- * conversations, not all of the messages in a conversation.
- */
- public static final class ConversationCursor extends MailCursor {
-
- private LabelMap mLabelMap;
-
- private int mConversationIdIndex;
- private int mSubjectIndex;
- private int mSnippetIndex;
- private int mFromIndex;
- private int mDateIndex;
- private int mPersonalLevelIndex;
- private int mLabelIdsIndex;
- private int mNumMessagesIndex;
- private int mMaxMessageIdIndex;
- private int mHasAttachmentsIndex;
- private int mHasMessagesWithErrorsIndex;
- private int mForceAllUnreadIndex;
-
- private TextUtils.StringSplitter mLabelIdsSplitter = newConversationLabelIdsSplitter();
-
- private ConversationCursor(Gmail gmail, String account, Cursor cursor) {
- super(account, cursor);
- mLabelMap = gmail.getLabelMap(account);
-
- mConversationIdIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.ID);
- mSubjectIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SUBJECT);
- mSnippetIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.SNIPPET);
- mFromIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.FROM);
- mDateIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.DATE);
- mPersonalLevelIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.PERSONAL_LEVEL);
- mLabelIdsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.LABEL_IDS);
- mNumMessagesIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.NUM_MESSAGES);
- mMaxMessageIdIndex = mCursor.getColumnIndexOrThrow(ConversationColumns.MAX_MESSAGE_ID);
- mHasAttachmentsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_ATTACHMENTS);
- mHasMessagesWithErrorsIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.HAS_MESSAGES_WITH_ERRORS);
- mForceAllUnreadIndex =
- mCursor.getColumnIndexOrThrow(ConversationColumns.FORCE_ALL_UNREAD);
- }
-
- @Override
- protected void onCursorPositionChanged() {
- super.onCursorPositionChanged();
- }
-
- public CursorStatus getStatus() {
- Bundle extras = mCursor.getExtras();
- String stringStatus = extras.getString(EXTRA_STATUS);
- return CursorStatus.valueOf(stringStatus);
- }
-
- /** Retry a network request after errors. */
- public void retry() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_RETRY);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * When a conversation cursor is created it becomes the active network cursor, which means
- * that it will fetch results from the network if it needs to in order to show all mail that
- * matches its query. If you later want to requery an older cursor and would like that
- * cursor to be the active cursor you need to call this method before requerying.
- */
- public void becomeActiveNetworkCursor() {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_ACTIVATE);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Tells the cursor whether its contents are visible to the user. The cursor will
- * automatically broadcast intents to remove any matching new-mail notifications when this
- * cursor's results become visible and, if they are visible, when the cursor is requeried.
- *
- * Note that contents shown in an activity that is resumed but not focused
- * (onWindowFocusChanged/hasWindowFocus) then results shown in that activity do not count
- * as visible. (This happens when the activity is behind the lock screen or a dialog.)
- *
- * @param visible whether the contents of this cursor are visible to the user.
- */
- public void setContentsVisibleToUser(boolean visible) {
- Bundle input = new Bundle();
- input.putString(RESPOND_INPUT_COMMAND, COMMAND_SET_VISIBLE);
- input.putBoolean(SET_VISIBLE_PARAM_VISIBLE, visible);
- Bundle output = mCursor.respond(input);
- String response = output.getString(RESPOND_OUTPUT_COMMAND_RESPONSE);
- assert COMMAND_RESPONSE_OK.equals(response);
- }
-
- /**
- * Gets the conversation id. This is immutable. (The server calls it the original
- * conversation id.)
- *
- * @return the conversation id
- */
- public long getConversationId() {
- return mCursor.getLong(mConversationIdIndex);
- }
-
- /**
- * Returns the instructions for building from snippets. Pass this to getFromSnippetHtml
- * in order to actually build the snippets.
- * @return snippet instructions for use by getFromSnippetHtml()
- */
- public String getFromSnippetInstructions() {
- return getStringInColumn(mFromIndex);
- }
-
- /**
- * Gets the conversation's subject.
- *
- * @return the subject
- */
- public String getSubject() {
- return getStringInColumn(mSubjectIndex);
- }
-
- /**
- * Gets the conversation's snippet.
- *
- * @return the snippet
- */
- public String getSnippet() {
- return getStringInColumn(mSnippetIndex);
- }
-
- /**
- * Get's the conversation's personal level.
- *
- * @return the personal level.
- */
- public PersonalLevel getPersonalLevel() {
- int personalLevelInt = mCursor.getInt(mPersonalLevelIndex);
- return PersonalLevel.fromInt(personalLevelInt);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- * @deprecated use getLabelIds
- */
- public Set<String> getLabels() {
- return getLabels(getRawLabelIds(), mLabelMap);
- }
-
- /**
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<Long> getLabelIds() {
- mLabelIdsSplitter.setString(getRawLabelIds());
- return getLabelIdsFromLabelIdsString(mLabelIdsSplitter);
- }
-
- /**
- * Returns the set of labels using the raw labels from a previous getRawLabels()
- * as input.
- * @return a copy of the set of labels. To add or remove labels call
- * MessageCursor.addOrRemoveLabel on each message in the conversation.
- */
- public Set<String> getLabels(String rawLabelIds, LabelMap labelMap) {
- mLabelIdsSplitter.setString(rawLabelIds);
- return getCanonicalNamesFromLabelIdsString(labelMap, mLabelIdsSplitter);
- }
-
- /**
- * @return a joined string of labels separated by spaces. Use
- * getLabels(rawLabels) to convert this to a Set of labels.
- */
- public String getRawLabelIds() {
- return mCursor.getString(mLabelIdsIndex);
- }
-
- /**
- * @return the number of messages in the conversation
- */
- public int getNumMessages() {
- return mCursor.getInt(mNumMessagesIndex);
- }
-
- /**
- * @return the max message id in the conversation
- */
- public long getMaxServerMessageId() {
- return mCursor.getLong(mMaxMessageIdIndex);
- }
-
- public long getDateMs() {
- return mCursor.getLong(mDateIndex);
- }
-
- public boolean hasAttachments() {
- return mCursor.getInt(mHasAttachmentsIndex) != 0;
- }
-
- public boolean hasMessagesWithErrors() {
- return mCursor.getInt(mHasMessagesWithErrorsIndex) != 0;
- }
-
- public boolean getForceAllUnread() {
- return !mCursor.isNull(mForceAllUnreadIndex)
- && mCursor.getInt(mForceAllUnreadIndex) != 0;
- }
- }
-}
diff --git a/core/java/android/provider/Im.java b/core/java/android/provider/Im.java
deleted file mode 100644
index 025d5c2..0000000
--- a/core/java/android/provider/Im.java
+++ /dev/null
@@ -1,2352 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider;
-
-import android.content.ContentQueryMap;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-
-import java.util.HashMap;
-
-/**
- * The GTalk provider stores all information about roster contacts, chat messages, presence, etc.
- *
- * @hide
- */
-public class Im {
- /**
- * no public constructor since this is a utility class
- */
- private Im() {}
-
- /**
- * The Columns for IM providers
- */
- public interface ProviderColumns {
- /**
- * The name of the IM provider
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The full name of the provider
- * <P>Type: TEXT</P>
- */
- String FULLNAME = "fullname";
-
- /**
- * The category for the provider, used to form intent.
- * <P>Type: TEXT</P>
- */
- String CATEGORY = "category";
-
- /**
- * The url users should visit to create a new account for this provider
- * <P>Type: TEXT</P>
- */
- String SIGNUP_URL = "signup_url";
- }
-
- /**
- * Known names corresponding to the {@link ProviderColumns#NAME} column
- */
- public interface ProviderNames {
- //
- //NOTE: update Contacts.java with new providers when they're added.
- //
- String YAHOO = "Yahoo";
- String GTALK = "GTalk";
- String MSN = "MSN";
- String ICQ = "ICQ";
- String AIM = "AIM";
- String XMPP = "XMPP";
- String JABBER = "JABBER";
- String SKYPE = "SKYPE";
- String QQ = "QQ";
- }
-
- /**
- * This table contains the IM providers
- */
- public static final class Provider implements BaseColumns, ProviderColumns {
- private Provider() {}
-
- public static final long getProviderIdForName(ContentResolver cr, String providerName) {
- String[] selectionArgs = new String[1];
- selectionArgs[0] = providerName;
-
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- NAME+"=?",
- selectionArgs, null);
-
- long retVal = 0;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- public static final String getProviderNameForId(ContentResolver cr, long providerId) {
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- _ID + "=" + providerId,
- null, null);
-
- String retVal = null;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getString(cursor.getColumnIndexOrThrow(NAME));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- private static final String[] PROVIDER_PROJECTION = new String[] {
- _ID,
- NAME
- };
-
- public static final String ACTIVE_ACCOUNT_ID = "account_id";
- public static final String ACTIVE_ACCOUNT_USERNAME = "account_username";
- public static final String ACTIVE_ACCOUNT_PW = "account_pw";
- public static final String ACTIVE_ACCOUNT_LOCKED = "account_locked";
- public static final String ACTIVE_ACCOUNT_KEEP_SIGNED_IN = "account_keepSignedIn";
- public static final String ACCOUNT_PRESENCE_STATUS = "account_presenceStatus";
- public static final String ACCOUNT_CONNECTION_STATUS = "account_connStatus";
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/providers");
-
- public static final Uri CONTENT_URI_WITH_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/providers/account");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-providers";
-
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-providers";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
- }
-
- /**
- * The columns for IM accounts. There can be more than one account for each IM provider.
- */
- public interface AccountColumns {
- /**
- * The name of the account
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The IM provider for this account
- * <P>Type: INTEGER</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The username for this account
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The password for this account
- * <P>Type: TEXT</P>
- */
- String PASSWORD = "pw";
-
- /**
- * A boolean value indicates if the account is active.
- * <P>Type: INTEGER</P>
- */
- String ACTIVE = "active";
-
- /**
- * A boolean value indicates if the account is locked (not editable)
- * <P>Type: INTEGER</P>
- */
- String LOCKED = "locked";
-
- /**
- * A boolean value to indicate whether this account is kept signed in.
- * <P>Type: INTEGER</P>
- */
- String KEEP_SIGNED_IN = "keep_signed_in";
-
- /**
- * A boolean value indiciating the last login state for this account
- * <P>Type: INTEGER</P>
- */
- String LAST_LOGIN_STATE = "last_login_state";
- }
-
- /**
- * This table contains the IM accounts.
- */
- public static final class Account implements BaseColumns, AccountColumns {
- private Account() {}
-
- public static final long getProviderIdForAccount(ContentResolver cr, long accountId) {
- Cursor cursor = cr.query(CONTENT_URI,
- PROVIDER_PROJECTION,
- _ID + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- long providerId = 0;
-
- try {
- if (cursor.moveToFirst()) {
- providerId = cursor.getLong(PROVIDER_COLUMN);
- }
- } finally {
- cursor.close();
- }
-
- return providerId;
- }
-
- private static final String[] PROVIDER_PROJECTION = new String[] { PROVIDER };
- private static final int PROVIDER_COLUMN = 0;
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/accounts");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * account.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-accounts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * account.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-accounts";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
-
- }
-
- /**
- * Connection status
- */
- public interface ConnectionStatus {
- /**
- * The connection is offline, not logged in.
- */
- int OFFLINE = 0;
-
- /**
- * The connection is attempting to connect.
- */
- int CONNECTING = 1;
-
- /**
- * The connection is suspended due to network not available.
- */
- int SUSPENDED = 2;
-
- /**
- * The connection is logged in and online.
- */
- int ONLINE = 3;
- }
-
- public interface AccountStatusColumns {
- /**
- * account id
- * <P>Type: INTEGER</P>
- */
- String ACCOUNT = "account";
-
- /**
- * User's presence status, see definitions in {#link CommonPresenceColumn}
- * <P>Type: INTEGER</P>
- */
- String PRESENCE_STATUS = "presenceStatus";
-
- /**
- * The connection status of this account, see {#link ConnectionStatus}
- * <P>Type: INTEGER</P>
- */
- String CONNECTION_STATUS = "connStatus";
- }
-
- public static final class AccountStatus implements BaseColumns, AccountStatusColumns {
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/accountStatus");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of account status.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-account-status";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single account status.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-account-status";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name ASC";
- }
-
- /**
- * Columns from the Contacts table.
- */
- public interface ContactsColumns {
- /**
- * The username
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The nickname or display name
- * <P>Type: TEXT</P>
- */
- String NICKNAME = "nickname";
-
- /**
- * The IM provider for this contact
- * <P>Type: INTEGER</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The account (within a IM provider) for this contact
- * <P>Type: INTEGER</P>
- */
- String ACCOUNT = "account";
-
- /**
- * The contactList this contact belongs to
- * <P>Type: INTEGER</P>
- */
- String CONTACTLIST = "contactList";
-
- /**
- * Contact type
- * <P>Type: INTEGER</P>
- */
- String TYPE = "type";
-
- /**
- * normal IM contact
- */
- int TYPE_NORMAL = 0;
- /**
- * temporary contact, someone not in the list of contacts that we
- * subscribe presence for. Usually created because of the user is
- * having a chat session with this contact.
- */
- int TYPE_TEMPORARY = 1;
- /**
- * temporary contact created for group chat.
- */
- int TYPE_GROUP = 2;
- /**
- * blocked contact.
- */
- int TYPE_BLOCKED = 3;
- /**
- * the contact is hidden. The client should always display this contact to the user.
- */
- int TYPE_HIDDEN = 4;
- /**
- * the contact is pinned. The client should always display this contact to the user.
- */
- int TYPE_PINNED = 5;
-
- /**
- * Contact subscription status
- * <P>Type: INTEGER</P>
- */
- String SUBSCRIPTION_STATUS = "subscriptionStatus";
-
- /**
- * no pending subscription
- */
- int SUBSCRIPTION_STATUS_NONE = 0;
- /**
- * requested to subscribe
- */
- int SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING = 1;
- /**
- * requested to unsubscribe
- */
- int SUBSCRIPTION_STATUS_UNSUBSCRIBE_PENDING = 2;
-
- /**
- * Contact subscription type
- * <P>Type: INTEGER </P>
- */
- String SUBSCRIPTION_TYPE = "subscriptionType";
-
- /**
- * The user and contact have no interest in each other's presence.
- */
- int SUBSCRIPTION_TYPE_NONE = 0;
- /**
- * The user wishes to stop receiving presence updates from the contact.
- */
- int SUBSCRIPTION_TYPE_REMOVE = 1;
- /**
- * The user is interested in receiving presence updates from the contact.
- */
- int SUBSCRIPTION_TYPE_TO = 2;
- /**
- * The contact is interested in receiving presence updates from the user.
- */
- int SUBSCRIPTION_TYPE_FROM = 3;
- /**
- * The user and contact have a mutual interest in each other's presence.
- */
- int SUBSCRIPTION_TYPE_BOTH = 4;
- /**
- * This is a special type reserved for pending subscription requests
- */
- int SUBSCRIPTION_TYPE_INVITATIONS = 5;
-
- /**
- * Quick Contact: derived from Google Contact Extension's "message_count" attribute.
- * <P>Type: INTEGER</P>
- */
- String QUICK_CONTACT = "qc";
-
- /**
- * Google Contact Extension attribute
- *
- * Rejected: a boolean value indicating whether a subscription request from
- * this client was ever rejected by the user. "true" indicates that it has.
- * This is provided so that a client can block repeated subscription requests.
- * <P>Type: INTEGER</P>
- */
- String REJECTED = "rejected";
-
- /**
- * Off The Record status: 0 for disabled, 1 for enabled
- * <P>Type: INTEGER </P>
- */
- String OTR = "otr";
- }
-
- /**
- * This defines the different type of values of {@link ContactsColumns#OTR}
- */
- public interface OffTheRecordType {
- /*
- * Off the record not turned on
- */
- int DISABLED = 0;
- /**
- * Off the record turned on, but we don't know who turned it on
- */
- int ENABLED = 1;
- /**
- * Off the record turned on by the user
- */
- int ENABLED_BY_USER = 2;
- /**
- * Off the record turned on by the buddy
- */
- int ENABLED_BY_BUDDY = 3;
- };
-
- /**
- * This table contains contacts.
- */
- public static final class Contacts implements BaseColumns,
- ContactsColumns, PresenceColumns, ChatsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Contacts() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contacts");
-
- /**
- * The content:// style URL for contacts joined with presence
- */
- public static final Uri CONTENT_URI_WITH_PRESENCE =
- Uri.parse("content://com.google.android.providers.talk/contactsWithPresence");
-
- /**
- * The content:// style URL for barebone contacts, not joined with any other table
- */
- public static final Uri CONTENT_URI_CONTACTS_BAREBONE =
- Uri.parse("content://com.google.android.providers.talk/contactsBarebone");
-
- /**
- * The content:// style URL for contacts who have an open chat session
- */
- public static final Uri CONTENT_URI_CHAT_CONTACTS =
- Uri.parse("content://com.google.android.providers.talk/contacts_chatting");
-
- /**
- * The content:// style URL for contacts who have been blocked
- */
- public static final Uri CONTENT_URI_BLOCKED_CONTACTS =
- Uri.parse("content://com.google.android.providers.talk/contacts/blocked");
-
- /**
- * The content:// style URL for contacts by provider and account
- */
- public static final Uri CONTENT_URI_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who have an open chat session
- */
- public static final Uri CONTENT_URI_CHAT_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/chatting");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who are online
- */
- public static final Uri CONTENT_URI_ONLINE_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/online");
-
- /**
- * The content:// style URL for contacts by provider and account,
- * and who are offline
- */
- public static final Uri CONTENT_URI_OFFLINE_CONTACTS_BY =
- Uri.parse("content://com.google.android.providers.talk/contacts/offline");
-
- /**
- * The content:// style URL for operations on bulk contacts
- */
- public static final Uri BULK_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/bulk_contacts");
-
- /**
- * The content:// style URL for the count of online contacts in each
- * contact list by provider and account.
- */
- public static final Uri CONTENT_URI_ONLINE_COUNT =
- Uri.parse("content://com.google.android.providers.talk/contacts/onlineCount");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-contacts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-contacts";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER =
- "subscriptionType DESC, last_message_date DESC," +
- " mode DESC, nickname COLLATE UNICODE ASC";
-
- public static final String CHATS_CONTACT = "chats_contact";
-
- public static final String AVATAR_HASH = "avatars_hash";
-
- public static final String AVATAR_DATA = "avatars_data";
- }
-
- /**
- * Columns from the ContactList table.
- */
- public interface ContactListColumns {
- String NAME = "name";
- String PROVIDER = "provider";
- String ACCOUNT = "account";
- }
-
- /**
- * This table contains the contact lists.
- */
- public static final class ContactList implements BaseColumns,
- ContactListColumns {
- private ContactList() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contactLists");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-contactLists";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-contactLists";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "name COLLATE UNICODE ASC";
-
- public static final String PROVIDER_NAME = "provider_name";
-
- public static final String ACCOUNT_NAME = "account_name";
- }
-
- /**
- * Columns from the BlockedList table.
- */
- public interface BlockedListColumns {
- /**
- * The username of the blocked contact.
- * <P>Type: TEXT</P>
- */
- String USERNAME = "username";
-
- /**
- * The nickname of the blocked contact.
- * <P>Type: TEXT</P>
- */
- String NICKNAME = "nickname";
-
- /**
- * The provider id of the blocked contact.
- * <P>Type: INT</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The account id of the blocked contact.
- * <P>Type: INT</P>
- */
- String ACCOUNT = "account";
- }
-
- /**
- * This table contains blocked lists
- */
- public static final class BlockedList implements BaseColumns, BlockedListColumns {
- private BlockedList() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/blockedList");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-blockedList";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-blockedList";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "nickname ASC";
-
- public static final String PROVIDER_NAME = "provider_name";
-
- public static final String ACCOUNT_NAME = "account_name";
-
- public static final String AVATAR_DATA = "avatars_data";
- }
-
- /**
- * Columns from the contactsEtag table
- */
- public interface ContactsEtagColumns {
- /**
- * The roster etag, computed by the server, stored on the client. There is one etag
- * per account roster.
- * <P>Type: TEXT</P>
- */
- String ETAG = "etag";
-
- /**
- * The OTR etag, computed by the server, stored on the client. There is one OTR etag
- * per account roster.
- * <P>Type: TEXT</P>
- */
- String OTR_ETAG = "otr_etag";
-
- /**
- * The account id for the etag.
- * <P> Type: INTEGER </P>
- */
- String ACCOUNT = "account";
- }
-
- public static final class ContactsEtag implements BaseColumns, ContactsEtagColumns {
- private ContactsEtag() {}
-
- public static final Cursor query(ContentResolver cr,
- String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, null);
- }
-
- public static final Cursor query(ContentResolver cr,
- String[] projection, String where, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- null, orderBy == null ? null : orderBy);
- }
-
- public static final String getRosterEtag(ContentResolver resolver, long accountId) {
- String retVal = null;
-
- Cursor c = resolver.query(CONTENT_URI,
- CONTACT_ETAG_PROJECTION,
- ACCOUNT + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- try {
- if (c.moveToFirst()) {
- retVal = c.getString(COLUMN_ETAG);
- }
- } finally {
- c.close();
- }
-
- return retVal;
- }
-
- public static final String getOtrEtag(ContentResolver resolver, long accountId) {
- String retVal = null;
-
- Cursor c = resolver.query(CONTENT_URI,
- CONTACT_OTR_ETAG_PROJECTION,
- ACCOUNT + "=" + accountId,
- null /* selection args */,
- null /* sort order */);
-
- try {
- if (c.moveToFirst()) {
- retVal = c.getString(COLUMN_OTR_ETAG);
- }
- } finally {
- c.close();
- }
-
- return retVal;
- }
-
- private static final String[] CONTACT_ETAG_PROJECTION = new String[] {
- Im.ContactsEtag.ETAG // 0
- };
-
- private static int COLUMN_ETAG = 0;
-
- private static final String[] CONTACT_OTR_ETAG_PROJECTION = new String[] {
- Im.ContactsEtag.OTR_ETAG // 0
- };
-
- private static int COLUMN_OTR_ETAG = 0;
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/contactsEtag");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-contactsEtag";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-contactsEtag";
- }
-
- /**
- * Message type definition
- */
- public interface MessageType {
- /* sent message */
- int OUTGOING = 0;
- /* received message */
- int INCOMING = 1;
- /* presence became available */
- int PRESENCE_AVAILABLE = 2;
- /* presence became away */
- int PRESENCE_AWAY = 3;
- /* presence became DND (busy) */
- int PRESENCE_DND = 4;
- /* presence became unavailable */
- int PRESENCE_UNAVAILABLE = 5;
- /* the message is converted to a group chat */
- int CONVERT_TO_GROUPCHAT = 6;
- /* generic status */
- int STATUS = 7;
- /* the message cannot be sent now, but will be sent later */
- int POSTPONED = 8;
- /* off The Record status is turned off */
- int OTR_IS_TURNED_OFF = 9;
- /* off the record status is turned on */
- int OTR_IS_TURNED_ON = 10;
- /* off the record status turned on by user */
- int OTR_TURNED_ON_BY_USER = 11;
- /* off the record status turned on by buddy */
- int OTR_TURNED_ON_BY_BUDDY = 12;
- }
-
- /**
- * The common columns for messages table
- */
- public interface MessageColumns {
- /**
- * The thread_id column stores the contact id of the contact the message belongs to.
- * For groupchat messages, the thread_id stores the group id, which is the contact id
- * of the temporary group contact created for the groupchat. So there should be no
- * collision between groupchat message thread id and regular message thread id.
- */
- String THREAD_ID = "thread_id";
-
- /**
- * The nickname. This is used for groupchat messages to indicate the participant's
- * nickname. For non groupchat messages, this field should be left empty.
- */
- String NICKNAME = "nickname";
-
- /**
- * The body
- * <P>Type: TEXT</P>
- */
- String BODY = "body";
-
- /**
- * The date this message is sent or received. This represents the display date for
- * the message.
- * <P>Type: INTEGER</P>
- */
- String DATE = "date";
-
- /**
- * The real date for this message. While 'date' can be modified by the client
- * to account for server time skew, the real_date is the original timestamp set
- * by the server for incoming messages.
- * <P>Type: INTEGER</P>
- */
- String REAL_DATE = "real_date";
-
- /**
- * Message Type, see {@link MessageType}
- * <P>Type: INTEGER</P>
- */
- String TYPE = "type";
-
- /**
- * Error Code: 0 means no error.
- * <P>Type: INTEGER </P>
- */
- String ERROR_CODE = "err_code";
-
- /**
- * Error Message
- * <P>Type: TEXT</P>
- */
- String ERROR_MESSAGE = "err_msg";
-
- /**
- * Packet ID, auto assigned by the GTalkService for outgoing messages or the
- * GTalk server for incoming messages. The packet id field is optional for messages,
- * so it could be null.
- * <P>Type: STRING</P>
- */
- String PACKET_ID = "packet_id";
-
- /**
- * Is groupchat message or not
- * <P>Type: INTEGER</P>
- */
- String IS_GROUP_CHAT = "is_muc";
-
- /**
- * A hint that the UI should show the sent time of this message
- * <P>Type: INTEGER</P>
- */
- String DISPLAY_SENT_TIME = "show_ts";
- }
-
- /**
- * This table contains messages.
- */
- public static final class Messages implements BaseColumns, MessageColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Messages() {}
-
- /**
- * Gets the Uri to query messages by thread id.
- *
- * @param threadId the thread id of the message.
- * @return the Uri
- */
- public static final Uri getContentUriByThreadId(long threadId) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_THREAD_ID.buildUpon();
- ContentUris.appendId(builder, threadId);
- return builder.build();
- }
-
- /**
- * @deprecated
- *
- * Gets the Uri to query messages by account and contact.
- *
- * @param accountId the account id of the contact.
- * @param username the user name of the contact.
- * @return the Uri
- */
- public static final Uri getContentUriByContact(long accountId, String username) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT.buildUpon();
- ContentUris.appendId(builder, accountId);
- builder.appendPath(username);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query messages by provider.
- *
- * @param providerId the service provider id.
- * @return the Uri
- */
- public static final Uri getContentUriByProvider(long providerId) {
- Uri.Builder builder = CONTENT_URI_MESSAGES_BY_PROVIDER.buildUpon();
- ContentUris.appendId(builder, providerId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by account.
- *
- * @param accountId the account id.
- * @return the Uri
- */
- public static final Uri getContentUriByAccount(long accountId) {
- Uri.Builder builder = CONTENT_URI_BY_ACCOUNT.buildUpon();
- ContentUris.appendId(builder, accountId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by thread id.
- *
- * @param threadId the thread id of the message.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByThreadId(long threadId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID.buildUpon();
- ContentUris.appendId(builder, threadId);
- return builder.build();
- }
-
- /**
- * @deprecated
- *
- * Gets the Uri to query off the record messages by account and contact.
- *
- * @param accountId the account id of the contact.
- * @param username the user name of the contact.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByContact(long accountId, String username) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT.buildUpon();
- ContentUris.appendId(builder, accountId);
- builder.appendPath(username);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by provider.
- *
- * @param providerId the service provider id.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByProvider(long providerId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_PROVIDER.buildUpon();
- ContentUris.appendId(builder, providerId);
- return builder.build();
- }
-
- /**
- * Gets the Uri to query off the record messages by account.
- *
- * @param accountId the account id.
- * @return the Uri
- */
- public static final Uri getOtrMessagesContentUriByAccount(long accountId) {
- Uri.Builder builder = OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT.buildUpon();
- ContentUris.appendId(builder, accountId);
- return builder.build();
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/messages");
-
- /**
- * The content:// style URL for messages by thread id
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_THREAD_ID =
- Uri.parse("content://com.google.android.providers.talk/messagesByThreadId");
-
- /**
- * The content:// style URL for messages by account and contact
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_ACCOUNT_AND_CONTACT =
- Uri.parse("content://com.google.android.providers.talk/messagesByAcctAndContact");
-
- /**
- * The content:// style URL for messages by provider
- */
- public static final Uri CONTENT_URI_MESSAGES_BY_PROVIDER =
- Uri.parse("content://com.google.android.providers.talk/messagesByProvider");
-
- /**
- * The content:// style URL for messages by account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/messagesByAccount");
-
- /**
- * The content:// style url for off the record messages
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/otrMessages");
-
- /**
- * The content:// style url for off the record messages by thread id
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_THREAD_ID =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByThreadId");
-
- /**
- * The content:// style url for off the record messages by account and contact
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT_AND_CONTACT =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByAcctAndContact");
-
- /**
- * The content:// style URL for off the record messages by provider
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_PROVIDER =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByProvider");
-
- /**
- * The content:// style URL for off the record messages by account
- */
- public static final Uri OTR_MESSAGES_CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/otrMessagesByAccount");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-messages";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * person.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-messages";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "date ASC";
-
- /**
- * The "contact" column. This is not a real column in the messages table, but a
- * temoprary column created when querying for messages (joined with the contacts table)
- */
- public static final String CONTACT = "contact";
- }
-
- /**
- * Columns for the GroupMember table.
- */
- public interface GroupMemberColumns {
- /**
- * The id of the group this member belongs to.
- * <p>Type: INTEGER</p>
- */
- String GROUP = "groupId";
-
- /**
- * The full name of this member.
- * <p>Type: TEXT</p>
- */
- String USERNAME = "username";
-
- /**
- * The nick name of this member.
- * <p>Type: TEXT</p>
- */
- String NICKNAME = "nickname";
- }
-
- public final static class GroupMembers implements GroupMemberColumns {
- private GroupMembers(){}
-
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/groupMembers");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * group members.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-groupMembers";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * group member.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-groupMembers";
- }
-
- /**
- * Columns from the Invitation table.
- */
- public interface InvitationColumns {
- /**
- * The provider id.
- * <p>Type: INTEGER</p>
- */
- String PROVIDER = "providerId";
-
- /**
- * The account id.
- * <p>Type: INTEGER</p>
- */
- String ACCOUNT = "accountId";
-
- /**
- * The invitation id.
- * <p>Type: TEXT</p>
- */
- String INVITE_ID = "inviteId";
-
- /**
- * The name of the sender of the invitation.
- * <p>Type: TEXT</p>
- */
- String SENDER = "sender";
-
- /**
- * The name of the group which the sender invite you to join.
- * <p>Type: TEXT</p>
- */
- String GROUP_NAME = "groupName";
-
- /**
- * A note
- * <p>Type: TEXT</p>
- */
- String NOTE = "note";
-
- /**
- * The current status of the invitation.
- * <p>Type: TEXT</p>
- */
- String STATUS = "status";
-
- int STATUS_PENDING = 0;
- int STATUS_ACCEPTED = 1;
- int STATUS_REJECTED = 2;
- }
-
- /**
- * This table contains the invitations received from others.
- */
- public final static class Invitation implements InvitationColumns,
- BaseColumns {
- private Invitation() {
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/invitations");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * invitations.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/gtalk-invitations";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * invitation.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-invitations";
- }
-
- /**
- * Columns from the Avatars table
- */
- public interface AvatarsColumns {
- /**
- * The contact this avatar belongs to
- * <P>Type: TEXT</P>
- */
- String CONTACT = "contact";
-
- String PROVIDER = "provider_id";
-
- String ACCOUNT = "account_id";
-
- /**
- * The hash of the image data
- * <P>Type: TEXT</P>
- */
- String HASH = "hash";
-
- /**
- * raw image data
- * <P>Type: BLOB</P>
- */
- String DATA = "data";
- }
-
- /**
- * This table contains avatars.
- */
- public static final class Avatars implements BaseColumns, AvatarsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Avatars() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/avatars");
-
- /**
- * The content:// style URL for avatars by provider, account and contact
- */
- public static final Uri CONTENT_URI_AVATARS_BY =
- Uri.parse("content://com.google.android.providers.talk/avatarsBy");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing the avatars
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-avatars";
-
- /**
- * The MIME type of a {@link #CONTENT_URI}
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/gtalk-avatars";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "contact ASC";
-
- }
-
- /**
- * Common presence columns shared between the IM and contacts presence tables
- */
- public interface CommonPresenceColumns {
- /**
- * The priority, an integer, used by XMPP presence
- * <P>Type: INTEGER</P>
- */
- String PRIORITY = "priority";
-
- /**
- * The server defined status.
- * <P>Type: INTEGER (one of the values below)</P>
- */
- String PRESENCE_STATUS = "mode";
-
- /**
- * Presence Status definition
- */
- int OFFLINE = 0;
- int INVISIBLE = 1;
- int AWAY = 2;
- int IDLE = 3;
- int DO_NOT_DISTURB = 4;
- int AVAILABLE = 5;
-
- /**
- * The user defined status line.
- * <P>Type: TEXT</P>
- */
- String PRESENCE_CUSTOM_STATUS = "status";
- }
-
- /**
- * Columns from the Presence table.
- */
- public interface PresenceColumns extends CommonPresenceColumns {
- /**
- * The contact id
- * <P>Type: INTEGER</P>
- */
- String CONTACT_ID = "contact_id";
-
- /**
- * The contact's JID resource, only relevant for XMPP contact
- * <P>Type: TEXT</P>
- */
- String JID_RESOURCE = "jid_resource";
-
- /**
- * The contact's client type
- */
- String CLIENT_TYPE = "client_type";
-
- /**
- * client type definitions
- */
- int CLIENT_TYPE_DEFAULT = 0;
- int CLIENT_TYPE_MOBILE = 1;
- int CLIENT_TYPE_ANDROID = 2;
- }
-
- /**
- * Contains presence infomation for contacts.
- */
- public static final class Presence implements BaseColumns, PresenceColumns {
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/presence");
-
- /**
- * The content URL for Talk presences for an account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/presence/account");
-
- /**
- * The content:// style URL for operations on bulk contacts
- */
- public static final Uri BULK_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/bulk_presence");
-
- /**
- * The content:// style URL for seeding presences for a given account id.
- */
- public static final Uri SEED_PRESENCE_BY_ACCOUNT_CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/seed_presence/account");
-
- /**
- * The MIME type of a {@link #CONTENT_URI} providing a directory of presence
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-presence";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "mode DESC";
- }
-
- /**
- * Columns from the Chats table.
- */
- public interface ChatsColumns {
- /**
- * The contact ID this chat belongs to. The value is a long.
- * <P>Type: INT</P>
- */
- String CONTACT_ID = "contact_id";
-
- /**
- * The GTalk JID resource. The value is a string.
- * <P>Type: TEXT</P>
- */
- String JID_RESOURCE = "jid_resource";
-
- /**
- * Whether this is a groupchat or not.
- * <P>Type: INT</P>
- */
- String GROUP_CHAT = "groupchat";
-
- /**
- * The last unread message. This both indicates that there is an
- * unread message, and what the message is.
- * <P>Type: TEXT</P>
- */
- String LAST_UNREAD_MESSAGE = "last_unread_message";
-
- /**
- * The last message timestamp
- * <P>Type: INT</P>
- */
- String LAST_MESSAGE_DATE = "last_message_date";
-
- /**
- * A message that is being composed. This indicates that there was a
- * message being composed when the chat screen was shutdown, and what the
- * message is.
- * <P>Type: TEXT</P>
- */
- String UNSENT_COMPOSED_MESSAGE = "unsent_composed_message";
-
- /**
- * A value from 0-9 indicating which quick-switch chat screen slot this
- * chat is occupying. If none (for instance, this is the 12th active chat)
- * then the value is -1.
- * <P>Type: INT</P>
- */
- String SHORTCUT = "shortcut";
- }
-
- /**
- * Contains ongoing chat sessions.
- */
- public static final class Chats implements BaseColumns, ChatsColumns {
- /**
- * no public constructor since this is a utility class
- */
- private Chats() {}
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/chats");
-
- /**
- * The content URL for all chats that belong to the account
- */
- public static final Uri CONTENT_URI_BY_ACCOUNT =
- Uri.parse("content://com.google.android.providers.talk/chats/account");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of chats.
- */
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/gtalk-chats";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single chat.
- */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/gtalk-chats";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "last_message_date ASC";
- }
-
- /**
- * Columns from session cookies table. Used for IMPS.
- */
- public static interface SessionCookiesColumns {
- String NAME = "name";
- String VALUE = "value";
- String PROVIDER = "provider";
- String ACCOUNT = "account";
- }
-
- /**
- * Contains IMPS session cookies.
- */
- public static class SessionCookies implements SessionCookiesColumns, BaseColumns {
- private SessionCookies() {
- }
-
- /**
- * The content:// style URI for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/sessionCookies");
-
- /**
- * The content:// style URL for session cookies by provider and account
- */
- public static final Uri CONTENT_URI_SESSION_COOKIES_BY =
- Uri.parse("content://com.google.android.providers.talk/sessionCookiesBy");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * people.
- */
- public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-sessionCookies";
- }
-
- /**
- * Columns from ProviderSettings table
- */
- public static interface ProviderSettingsColumns {
- /**
- * The id in database of the related provider
- *
- * <P>Type: INT</P>
- */
- String PROVIDER = "provider";
-
- /**
- * The name of the setting
- * <P>Type: TEXT</P>
- */
- String NAME = "name";
-
- /**
- * The value of the setting
- * <P>Type: TEXT</P>
- */
- String VALUE = "value";
- }
-
- public static class ProviderSettings implements ProviderSettingsColumns {
- private ProviderSettings() {
- }
-
- /**
- * The content:// style URI for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/providerSettings");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing provider settings
- */
- public static final String CONTENT_TYPE = "vnd.android-dir/gtalk-providerSettings";
-
- /**
- * A boolean value to indicate whether this provider should show the offline contacts
- */
- public static final String SHOW_OFFLINE_CONTACTS = "show_offline_contacts";
-
- /** controls whether or not the GTalk service automatically connect to server. */
- public static final String SETTING_AUTOMATICALLY_CONNECT_GTALK = "gtalk_auto_connect";
-
- /** controls whether or not the GTalk service will be automatically started after boot */
- public static final String SETTING_AUTOMATICALLY_START_SERVICE = "auto_start_service";
-
- /** controls whether or not the offline contacts will be hided */
- public static final String SETTING_HIDE_OFFLINE_CONTACTS = "hide_offline_contacts";
-
- /** controls whether or not enable the GTalk notification */
- public static final String SETTING_ENABLE_NOTIFICATION = "enable_notification";
-
- /** specifies whether or not to vibrate */
- public static final String SETTING_VIBRATE = "vibrate";
-
- /** specifies the Uri string of the ringtone */
- public static final String SETTING_RINGTONE = "ringtone";
-
- /** specifies the Uri of the default ringtone */
- public static final String SETTING_RINGTONE_DEFAULT =
- "content://settings/system/notification_sound";
-
- /** specifies whether or not to show mobile indicator to friends */
- public static final String SETTING_SHOW_MOBILE_INDICATOR = "mobile_indicator";
-
- /** specifies whether or not to show as away when device is idle */
- public static final String SETTING_SHOW_AWAY_ON_IDLE = "show_away_on_idle";
-
- /** specifies whether or not to upload heartbeat stat upon login */
- public static final String SETTING_UPLOAD_HEARTBEAT_STAT = "upload_heartbeat_stat";
-
- /** specifies the last heartbeat interval received from the server */
- public static final String SETTING_HEARTBEAT_INTERVAL = "heartbeat_interval";
-
- /** specifiy the JID resource used for Google Talk connection */
- public static final String SETTING_JID_RESOURCE = "jid_resource";
-
- /**
- * Used for reliable message queue (RMQ). This is for storing the last rmq id received
- * from the GTalk server
- */
- public static final String LAST_RMQ_RECEIVED = "last_rmq_rec";
-
- /**
- * Query the settings of the provider specified by id
- *
- * @param cr
- * the relative content resolver
- * @param providerId
- * the specified id of provider
- * @return a HashMap which contains all the settings for the specified
- * provider
- */
- public static HashMap<String, String> queryProviderSettings(ContentResolver cr,
- long providerId) {
- HashMap<String, String> settings = new HashMap<String, String>();
-
- String[] projection = { NAME, VALUE };
- Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), projection, null, null, null);
- if (c == null) {
- return null;
- }
-
- while(c.moveToNext()) {
- settings.put(c.getString(0), c.getString(1));
- }
-
- c.close();
-
- return settings;
- }
-
- /**
- * Get the string value of setting which is specified by provider id and the setting name.
- *
- * @param cr The ContentResolver to use to access the settings table.
- * @param providerId The id of the provider.
- * @param settingName The name of the setting.
- * @return The value of the setting if the setting exist, otherwise return null.
- */
- public static String getStringValue(ContentResolver cr, long providerId, String settingName) {
- String ret = null;
- Cursor c = getSettingValue(cr, providerId, settingName);
- if (c != null) {
- ret = c.getString(0);
- c.close();
- }
-
- return ret;
- }
-
- /**
- * Get the boolean value of setting which is specified by provider id and the setting name.
- *
- * @param cr The ContentResolver to use to access the settings table.
- * @param providerId The id of the provider.
- * @param settingName The name of the setting.
- * @return The value of the setting if the setting exist, otherwise return false.
- */
- public static boolean getBooleanValue(ContentResolver cr, long providerId, String settingName) {
- boolean ret = false;
- Cursor c = getSettingValue(cr, providerId, settingName);
- if (c != null) {
- ret = c.getInt(0) != 0;
- c.close();
- }
- return ret;
- }
-
- private static Cursor getSettingValue(ContentResolver cr, long providerId, String settingName) {
- Cursor c = cr.query(ContentUris.withAppendedId(CONTENT_URI, providerId), new String[]{VALUE}, NAME + "=?",
- new String[]{settingName}, null);
- if (c != null) {
- if (!c.moveToFirst()) {
- c.close();
- return null;
- }
- }
- return c;
- }
-
- /**
- * Save a long value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putLongValue(ContentResolver cr, long providerId, String name,
- long value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, value);
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * Save a boolean value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putBooleanValue(ContentResolver cr, long providerId, String name,
- boolean value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, Boolean.toString(value));
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * Save a string value of setting in the table providerSetting.
- *
- * @param cr The ContentProvider used to access the providerSetting table.
- * @param providerId The id of the provider.
- * @param name The name of the setting.
- * @param value The value of the setting.
- */
- public static void putStringValue(ContentResolver cr, long providerId, String name,
- String value) {
- ContentValues v = new ContentValues(3);
- v.put(PROVIDER, providerId);
- v.put(NAME, name);
- v.put(VALUE, value);
-
- cr.insert(CONTENT_URI, v);
- }
-
- /**
- * A convenience method to set whether or not the GTalk service should be started
- * automatically.
- *
- * @param contentResolver The ContentResolver to use to access the settings table
- * @param autoConnect Whether the GTalk service should be started automatically.
- */
- public static void setAutomaticallyConnectGTalk(ContentResolver contentResolver,
- long providerId, boolean autoConnect) {
- putBooleanValue(contentResolver, providerId, SETTING_AUTOMATICALLY_CONNECT_GTALK,
- autoConnect);
- }
-
- /**
- * A convenience method to set whether or not the offline contacts should be hided
- *
- * @param contentResolver The ContentResolver to use to access the setting table
- * @param hideOfflineContacts Whether the offline contacts should be hided
- */
- public static void setHideOfflineContacts(ContentResolver contentResolver,
- long providerId, boolean hideOfflineContacts) {
- putBooleanValue(contentResolver, providerId, SETTING_HIDE_OFFLINE_CONTACTS,
- hideOfflineContacts);
- }
-
- /**
- * A convenience method to set whether or not enable the GTalk notification.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param enable Whether enable the GTalk notification
- */
- public static void setEnableNotification(ContentResolver contentResolver, long providerId,
- boolean enable) {
- putBooleanValue(contentResolver, providerId, SETTING_ENABLE_NOTIFICATION, enable);
- }
-
- /**
- * A convenience method to set whether or not to vibrate.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param vibrate Whether or not to vibrate
- */
- public static void setVibrate(ContentResolver contentResolver, long providerId,
- boolean vibrate) {
- putBooleanValue(contentResolver, providerId, SETTING_VIBRATE, vibrate);
- }
-
- /**
- * A convenience method to set the Uri String of the ringtone.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param ringtoneUri The Uri String of the ringtone to be set.
- */
- public static void setRingtoneURI(ContentResolver contentResolver, long providerId,
- String ringtoneUri) {
- putStringValue(contentResolver, providerId, SETTING_RINGTONE, ringtoneUri);
- }
-
- /**
- * A convenience method to set whether or not to show mobile indicator.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param showMobileIndicator Whether or not to show mobile indicator.
- */
- public static void setShowMobileIndicator(ContentResolver contentResolver, long providerId,
- boolean showMobileIndicator) {
- putBooleanValue(contentResolver, providerId, SETTING_SHOW_MOBILE_INDICATOR,
- showMobileIndicator);
- }
-
- /**
- * A convenience method to set whether or not to show as away when device is idle.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param showAway Whether or not to show as away when device is idle.
- */
- public static void setShowAwayOnIdle(ContentResolver contentResolver,
- long providerId, boolean showAway) {
- putBooleanValue(contentResolver, providerId, SETTING_SHOW_AWAY_ON_IDLE, showAway);
- }
-
- /**
- * A convenience method to set whether or not to upload heartbeat stat.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param uploadStat Whether or not to upload heartbeat stat.
- */
- public static void setUploadHeartbeatStat(ContentResolver contentResolver,
- long providerId, boolean uploadStat) {
- putBooleanValue(contentResolver, providerId, SETTING_UPLOAD_HEARTBEAT_STAT, uploadStat);
- }
-
- /**
- * A convenience method to set the heartbeat interval last received from the server.
- *
- * @param contentResolver The ContentResolver to use to access the setting table.
- * @param interval The heartbeat interval last received from the server.
- */
- public static void setHeartbeatInterval(ContentResolver contentResolver,
- long providerId, long interval) {
- putLongValue(contentResolver, providerId, SETTING_HEARTBEAT_INTERVAL, interval);
- }
-
- /**
- * A convenience method to set the jid resource.
- */
- public static void setJidResource(ContentResolver contentResolver,
- long providerId, String jidResource) {
- putStringValue(contentResolver, providerId, SETTING_JID_RESOURCE, jidResource);
- }
-
- public static class QueryMap extends ContentQueryMap {
- private ContentResolver mContentResolver;
- private long mProviderId;
-
- public QueryMap(ContentResolver contentResolver, long providerId, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- super(contentResolver.query(CONTENT_URI,
- new String[] {NAME,VALUE},
- PROVIDER + "=" + providerId,
- null, // no selection args
- null), // no sort order
- NAME, keepUpdated, handlerForUpdateNotifications);
- mContentResolver = contentResolver;
- mProviderId = providerId;
- }
-
- /**
- * Set if the GTalk service should automatically connect to server.
- *
- * @param autoConnect if the GTalk service should auto connect to server.
- */
- public void setAutomaticallyConnectToGTalkServer(boolean autoConnect) {
- ProviderSettings.setAutomaticallyConnectGTalk(mContentResolver, mProviderId,
- autoConnect);
- }
-
- /**
- * Check if the GTalk service should automatically connect to server.
- * @return if the GTalk service should automatically connect to server.
- */
- public boolean getAutomaticallyConnectToGTalkServer() {
- return getBoolean(SETTING_AUTOMATICALLY_CONNECT_GTALK,
- true /* default to automatically sign in */);
- }
-
- /**
- * Set whether or not the offline contacts should be hided.
- *
- * @param hideOfflineContacts Whether or not the offline contacts should be hided.
- */
- public void setHideOfflineContacts(boolean hideOfflineContacts) {
- ProviderSettings.setHideOfflineContacts(mContentResolver, mProviderId,
- hideOfflineContacts);
- }
-
- /**
- * Check if the offline contacts should be hided.
- *
- * @return Whether or not the offline contacts should be hided.
- */
- public boolean getHideOfflineContacts() {
- return getBoolean(SETTING_HIDE_OFFLINE_CONTACTS,
- false/* by default not hide the offline contacts*/);
- }
-
- /**
- * Set whether or not enable the GTalk notification.
- *
- * @param enable Whether or not enable the GTalk notification.
- */
- public void setEnableNotification(boolean enable) {
- ProviderSettings.setEnableNotification(mContentResolver, mProviderId, enable);
- }
-
- /**
- * Check if the GTalk notification is enabled.
- *
- * @return Whether or not enable the GTalk notification.
- */
- public boolean getEnableNotification() {
- return getBoolean(SETTING_ENABLE_NOTIFICATION,
- true/* by default enable the notification */);
- }
-
- /**
- * Set whether or not to vibrate on GTalk notification.
- *
- * @param vibrate Whether or not to vibrate.
- */
- public void setVibrate(boolean vibrate) {
- ProviderSettings.setVibrate(mContentResolver, mProviderId, vibrate);
- }
-
- /**
- * Gets whether or not to vibrate on GTalk notification.
- *
- * @return Whether or not to vibrate.
- */
- public boolean getVibrate() {
- return getBoolean(SETTING_VIBRATE, false /* by default disable vibrate */);
- }
-
- /**
- * Set the Uri for the ringtone.
- *
- * @param ringtoneUri The Uri of the ringtone to be set.
- */
- public void setRingtoneURI(String ringtoneUri) {
- ProviderSettings.setRingtoneURI(mContentResolver, mProviderId, ringtoneUri);
- }
-
- /**
- * Get the Uri String of the current ringtone.
- *
- * @return The Uri String of the current ringtone.
- */
- public String getRingtoneURI() {
- return getString(SETTING_RINGTONE, SETTING_RINGTONE_DEFAULT);
- }
-
- /**
- * Set whether or not to show mobile indicator to friends.
- *
- * @param showMobile whether or not to show mobile indicator.
- */
- public void setShowMobileIndicator(boolean showMobile) {
- ProviderSettings.setShowMobileIndicator(mContentResolver, mProviderId, showMobile);
- }
-
- /**
- * Gets whether or not to show mobile indicator.
- *
- * @return Whether or not to show mobile indicator.
- */
- public boolean getShowMobileIndicator() {
- return getBoolean(SETTING_SHOW_MOBILE_INDICATOR,
- true /* by default show mobile indicator */);
- }
-
- /**
- * Set whether or not to show as away when device is idle.
- *
- * @param showAway whether or not to show as away when device is idle.
- */
- public void setShowAwayOnIdle(boolean showAway) {
- ProviderSettings.setShowAwayOnIdle(mContentResolver, mProviderId, showAway);
- }
-
- /**
- * Get whether or not to show as away when device is idle.
- *
- * @return Whether or not to show as away when device is idle.
- */
- public boolean getShowAwayOnIdle() {
- return getBoolean(SETTING_SHOW_AWAY_ON_IDLE,
- true /* by default show as away on idle*/);
- }
-
- /**
- * Set whether or not to upload heartbeat stat.
- *
- * @param uploadStat whether or not to upload heartbeat stat.
- */
- public void setUploadHeartbeatStat(boolean uploadStat) {
- ProviderSettings.setUploadHeartbeatStat(mContentResolver, mProviderId, uploadStat);
- }
-
- /**
- * Get whether or not to upload heartbeat stat.
- *
- * @return Whether or not to upload heartbeat stat.
- */
- public boolean getUploadHeartbeatStat() {
- return getBoolean(SETTING_UPLOAD_HEARTBEAT_STAT,
- false /* by default do not upload */);
- }
-
- /**
- * Set the last received heartbeat interval from the server.
- *
- * @param interval the last received heartbeat interval from the server.
- */
- public void setHeartbeatInterval(long interval) {
- ProviderSettings.setHeartbeatInterval(mContentResolver, mProviderId, interval);
- }
-
- /**
- * Get the last received heartbeat interval from the server.
- *
- * @return the last received heartbeat interval from the server.
- */
- public long getHeartbeatInterval() {
- return getLong(SETTING_HEARTBEAT_INTERVAL, 0L /* an invalid default interval */);
- }
-
- /**
- * Set the JID resource.
- *
- * @param jidResource the jid resource to be stored.
- */
- public void setJidResource(String jidResource) {
- ProviderSettings.setJidResource(mContentResolver, mProviderId, jidResource);
- }
- /**
- * Get the JID resource used for the Google Talk connection
- *
- * @return the JID resource stored.
- */
- public String getJidResource() {
- return getString(SETTING_JID_RESOURCE, null);
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a boolean.
- *
- * @param name The name of the setting to retrieve.
- * @param def Value to return if the setting is not defined.
- * @return The setting's current value, or 'def' if it is not defined.
- */
- private boolean getBoolean(String name, boolean def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsBoolean(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a String.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private String getString(String name, String def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsString(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as an Integer.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private int getInteger(String name, int def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsInteger(VALUE) : def;
- }
-
- /**
- * Convenience function for retrieving a single settings value
- * as a Long.
- *
- * @param name The name of the setting to retrieve.
- * @param def The value to return if the setting is not defined.
- * @return The setting's current value or 'def' if it is not defined.
- */
- private long getLong(String name, long def) {
- ContentValues values = getValues(name);
- return values != null ? values.getAsLong(VALUE) : def;
- }
- }
-
- }
-
-
- /**
- * Columns for GTalk branding resource map cache table. This table caches the result of
- * loading the branding resources to speed up GTalk landing page start.
- */
- public interface BrandingResourceMapCacheColumns {
- /**
- * The provider ID
- * <P>Type: INTEGER</P>
- */
- String PROVIDER_ID = "provider_id";
- /**
- * The application resource ID
- * <P>Type: INTEGER</P>
- */
- String APP_RES_ID = "app_res_id";
- /**
- * The plugin resource ID
- * <P>Type: INTEGER</P>
- */
- String PLUGIN_RES_ID = "plugin_res_id";
- }
-
- /**
- * The table for caching the result of loading GTalk branding resources.
- */
- public static final class BrandingResourceMapCache
- implements BaseColumns, BrandingResourceMapCacheColumns {
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/brandingResMapCache");
- }
-
-
-
- /**
- * //TODO: move these to MCS specific provider.
- * The following are MCS stuff, and should really live in a separate provider specific to
- * MCS code.
- */
-
- /**
- * Columns from OutgoingRmq table
- */
- public interface OutgoingRmqColumns {
- String RMQ_ID = "rmq_id";
- String TIMESTAMP = "ts";
- String DATA = "data";
- String PROTOBUF_TAG = "type";
- }
-
- /**
- * //TODO: we should really move these to their own provider and database.
- * The table for storing outgoing rmq packets.
- */
- public static final class OutgoingRmq implements BaseColumns, OutgoingRmqColumns {
- private static String[] RMQ_ID_PROJECTION = new String[] {
- RMQ_ID,
- };
-
- /**
- * queryHighestRmqId
- *
- * @param resolver the content resolver
- * @return the highest rmq id assigned to the rmq packet, or 0 if there are no rmq packets
- * in the OutgoingRmq table.
- */
- public static final long queryHighestRmqId(ContentResolver resolver) {
- Cursor cursor = resolver.query(Im.OutgoingRmq.CONTENT_URI_FOR_HIGHEST_RMQ_ID,
- RMQ_ID_PROJECTION,
- null, // selection
- null, // selection args
- null // sort
- );
-
- long retVal = 0;
- try {
- //if (DBG) log("initializeRmqid: cursor.count= " + cursor.count());
-
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/outgoingRmqMessages");
-
- /**
- * The content:// style URL for the highest rmq id for the outgoing rmq messages
- */
- public static final Uri CONTENT_URI_FOR_HIGHEST_RMQ_ID =
- Uri.parse("content://com.google.android.providers.talk/outgoingHighestRmqId");
-
- /**
- * The default sort order for this table.
- */
- public static final String DEFAULT_SORT_ORDER = "rmq_id ASC";
- }
-
- /**
- * Columns for the LastRmqId table, which stores a single row for the last client rmq id
- * sent to the server.
- */
- public interface LastRmqIdColumns {
- String RMQ_ID = "rmq_id";
- }
-
- /**
- * //TODO: move these out into their own provider and database
- * The table for storing the last client rmq id sent to the server.
- */
- public static final class LastRmqId implements BaseColumns, LastRmqIdColumns {
- private static String[] PROJECTION = new String[] {
- RMQ_ID,
- };
-
- /**
- * queryLastRmqId
- *
- * queries the last rmq id saved in the LastRmqId table.
- *
- * @param resolver the content resolver.
- * @return the last rmq id stored in the LastRmqId table, or 0 if not found.
- */
- public static final long queryLastRmqId(ContentResolver resolver) {
- Cursor cursor = resolver.query(Im.LastRmqId.CONTENT_URI,
- PROJECTION,
- null, // selection
- null, // selection args
- null // sort
- );
-
- long retVal = 0;
- try {
- if (cursor.moveToFirst()) {
- retVal = cursor.getLong(cursor.getColumnIndexOrThrow(RMQ_ID));
- }
- } finally {
- cursor.close();
- }
-
- return retVal;
- }
-
- /**
- * saveLastRmqId
- *
- * saves the rmqId to the lastRmqId table. This will override the existing row if any,
- * as we only keep one row of data in this table.
- *
- * @param resolver the content resolver.
- * @param rmqId the rmq id to be saved.
- */
- public static final void saveLastRmqId(ContentResolver resolver, long rmqId) {
- ContentValues values = new ContentValues();
-
- // always replace the first row.
- values.put(_ID, 1);
- values.put(RMQ_ID, rmqId);
- resolver.insert(CONTENT_URI, values);
- }
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/lastRmqId");
- }
-
- /**
- * Columns for the s2dRmqIds table, which stores the server-to-device message
- * persistent ids. These are used in the RMQ2 protocol, where in the login request, the
- * client selective acks these s2d ids to the server.
- */
- public interface ServerToDeviceRmqIdsColumn {
- String RMQ_ID = "rmq_id";
- }
-
- public static final class ServerToDeviceRmqIds implements BaseColumns,
- ServerToDeviceRmqIdsColumn {
-
- /**
- * The content:// style URL for this table.
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://com.google.android.providers.talk/s2dids");
- }
-
-}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 062080d..c9d125b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -28,7 +28,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.MiniThumbFile;
-import android.media.ThumbnailUtil;
+import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
@@ -54,6 +54,13 @@ public final class MediaStore {
private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
/**
+ * Activity Action: Launch a music player.
+ * The activity should be able to play, browse, or manipulate music files stored on the device.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
+
+ /**
* Activity Action: Perform a search for media.
* Contains at least the {@link android.app.SearchManager#QUERY} extra.
* May also contain any combination of the following extras:
@@ -96,14 +103,12 @@ public final class MediaStore {
/**
* The name of an Intent-extra used to control the UI of a ViewImage.
* This is a boolean property that overrides the activity's default fullscreen state.
- * @hide
*/
public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
/**
* The name of an Intent-extra used to control the UI of a ViewImage.
* This is a boolean property that specifies whether or not to show action icons.
- * @hide
*/
public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
@@ -162,13 +167,11 @@ public final class MediaStore {
/**
* Specify the maximum allowed size.
- * @hide
*/
public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
/**
* Specify the maximum allowed recording duration in seconds.
- * @hide
*/
public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
@@ -179,6 +182,13 @@ public final class MediaStore {
public final static String EXTRA_OUTPUT = "output";
/**
+ * The string that is used when a media attribute is not known. For example,
+ * if an audio file does not have any meta data, the artist and album columns
+ * will be set to this value.
+ */
+ public static final String UNKNOWN_STRING = "<unknown>";
+
+ /**
* Common fields for most MediaProvider tables
*/
@@ -238,6 +248,29 @@ public final class MediaStore {
private static final int FULL_SCREEN_KIND = 2;
private static final int MICRO_KIND = 3;
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+ static final int DEFAULT_GROUP_ID = 0;
+
+ private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
+ Bitmap bitmap = null;
+ Uri thumbUri = null;
+ try {
+ long thumbId = c.getLong(0);
+ String filePath = c.getString(1);
+ thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
+ ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
+ bitmap = BitmapFactory.decodeFileDescriptor(
+ pfdInput.getFileDescriptor(), null, options);
+ pfdInput.close();
+ } catch (FileNotFoundException ex) {
+ Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+ } catch (IOException ex) {
+ Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+ } catch (OutOfMemoryError ex) {
+ Log.e(TAG, "failed to allocate memory for thumbnail "
+ + thumbUri + "; " + ex);
+ }
+ return bitmap;
+ }
/**
* This method cancels the thumbnail request so clients waiting for getThumbnail will be
@@ -246,11 +279,14 @@ public final class MediaStore {
*
* @param cr ContentResolver
* @param origId original image or video id. use -1 to cancel all requests.
+ * @param groupId the same groupId used in getThumbnail
* @param baseUri the base URI of requested thumbnails
*/
- static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) {
+ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
+ long groupId) {
Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
Cursor c = null;
try {
c = cr.query(cancelUri, PROJECTION, null, null, null);
@@ -271,18 +307,20 @@ public final class MediaStore {
* @param kind could be MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @param baseUri the base URI of requested thumbnails
+ * @param groupId the id of group to which this request belongs
* @return Bitmap bitmap of specified thumbnail kind
*/
- static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
Bitmap bitmap = null;
String filePath = null;
// Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
- // some optimization for MICRO_KIND: if the magic is non-zero, we don't bother
+ // If the magic is non-zero, we simply return thumbnail if it does exist.
// querying MediaProvider and simply return thumbnail.
- if (kind == MICRO_KIND) {
- MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
- if (thumbFile.getMagic(origId) != 0) {
+ MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
+ long magic = thumbFile.getMagic(origId);
+ if (magic != 0) {
+ if (kind == MICRO_KIND) {
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -291,20 +329,34 @@ public final class MediaStore {
}
}
return bitmap;
+ } else if (kind == MINI_KIND) {
+ String column = isVideo ? "video_id=" : "image_id=";
+ Cursor c = null;
+ try {
+ c = cr.query(baseUri, PROJECTION, column + origId, null, null);
+ if (c != null && c.moveToFirst()) {
+ bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
+ if (bitmap != null) {
+ return bitmap;
+ }
+ }
+ } finally {
+ if (c != null) c.close();
+ }
}
}
Cursor c = null;
try {
Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
- .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+ .appendQueryParameter("orig_id", String.valueOf(origId))
+ .appendQueryParameter("group_id", String.valueOf(groupId)).build();
c = cr.query(blockingUri, PROJECTION, null, null, null);
// This happens when original image/video doesn't exist.
if (c == null) return null;
// Assuming thumbnail has been generated, at least original image exists.
if (kind == MICRO_KIND) {
- MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
@@ -314,24 +366,7 @@ public final class MediaStore {
}
} else if (kind == MINI_KIND) {
if (c.moveToFirst()) {
- ParcelFileDescriptor pfdInput;
- Uri thumbUri = null;
- try {
- long thumbId = c.getLong(0);
- filePath = c.getString(1);
- thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
- pfdInput = cr.openFileDescriptor(thumbUri, "r");
- bitmap = BitmapFactory.decodeFileDescriptor(
- pfdInput.getFileDescriptor(), null, options);
- pfdInput.close();
- } catch (FileNotFoundException ex) {
- Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
- } catch (IOException ex) {
- Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
- } catch (OutOfMemoryError ex) {
- Log.e(TAG, "failed to allocate memory for thumbnail "
- + thumbUri + "; " + ex);
- }
+ bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
}
} else {
throw new IllegalArgumentException("Unsupported kind: " + kind);
@@ -339,8 +374,8 @@ public final class MediaStore {
// We probably run out of space, so create the thumbnail in memory.
if (bitmap == null) {
- Log.v(TAG, "We probably run out of space, so create the thumbnail in memory.");
-
+ Log.v(TAG, "Create the thumbnail in memory: origId=" + origId
+ + ", kind=" + kind + ", isVideo="+isVideo);
Uri uri = Uri.parse(
baseUri.buildUpon().appendPath(String.valueOf(origId))
.toString().replaceFirst("thumbnails", "media"));
@@ -353,16 +388,9 @@ public final class MediaStore {
filePath = c.getString(1);
}
if (isVideo) {
- bitmap = ThumbnailUtil.createVideoThumbnail(filePath);
- if (kind == MICRO_KIND) {
- bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
- ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
- ThumbnailUtil.MINI_THUMB_TARGET_SIZE,
- ThumbnailUtil.RECYCLE_INPUT);
- }
+ bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
} else {
- bitmap = ThumbnailUtil.createImageThumbnail(cr, filePath, uri, origId,
- kind, false);
+ bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
}
}
} catch (SQLiteException ex) {
@@ -578,8 +606,12 @@ public final class MediaStore {
}
long id = ContentUris.parseId(url);
- Bitmap miniThumb = StoreThumbnail(cr, source, id, 320F, 240F, Images.Thumbnails.MINI_KIND);
- Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F, Images.Thumbnails.MICRO_KIND);
+ // Wait until MINI_KIND thumbnail is generated.
+ Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id,
+ Images.Thumbnails.MINI_KIND, null);
+ // This is for backward compatibility.
+ Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F,
+ Images.Thumbnails.MICRO_KIND);
} else {
Log.e(TAG, "Failed to create thumbnail, removing original");
cr.delete(url, null, null);
@@ -669,7 +701,8 @@ public final class MediaStore {
* @param origId original image id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
}
/**
@@ -685,7 +718,39 @@ public final class MediaStore {
*/
public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, false);
+ }
+
+ /**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original image id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, false);
}
@@ -786,7 +851,6 @@ public final class MediaStore {
* The position, in ms, playback was at when playback for this file
* was last stopped.
* <P>Type: INTEGER (long)</P>
- * @hide
*/
public static final String BOOKMARK = "bookmark";
@@ -803,6 +867,13 @@ public final class MediaStore {
public static final String ARTIST = "artist";
/**
+ * The artist credited for the album that contains the audio file
+ * <P>Type: TEXT</P>
+ * @hide
+ */
+ public static final String ALBUM_ARTIST = "album_artist";
+
+ /**
* A non human readable key calculated from the ARTIST, used for
* searching, sorting and grouping
* <P>Type: TEXT</P>
@@ -865,7 +936,6 @@ public final class MediaStore {
/**
* Non-zero if the audio file is a podcast
* <P>Type: INTEGER (boolean)</P>
- * @hide
*/
public static final String IS_PODCAST = "is_podcast";
@@ -906,7 +976,7 @@ public final class MediaStore {
public static String keyFor(String name) {
if (name != null) {
boolean sortfirst = false;
- if (name.equals(android.media.MediaFile.UNKNOWN_STRING)) {
+ if (name.equals(UNKNOWN_STRING)) {
return "\001";
}
// Check if the first character is \001. We use this to
@@ -1194,6 +1264,27 @@ public final class MediaStore {
}
/**
+ * Convenience method to move a playlist item to a new location
+ * @param res The content resolver to use
+ * @param playlistId The numeric id of the playlist
+ * @param from The position of the item to move
+ * @param to The position to move the item to
+ * @return true on success
+ */
+ public static final boolean moveItem(ContentResolver res,
+ long playlistId, int from, int to) {
+ Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
+ playlistId)
+ .buildUpon()
+ .appendEncodedPath(String.valueOf(from))
+ .appendQueryParameter("move", "true")
+ .build();
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
+ return res.update(uri, values, null, null) != 0;
+ }
+
+ /**
* The ID within the playlist.
*/
public static final String _ID = "_id";
@@ -1598,7 +1689,26 @@ public final class MediaStore {
* @param origId original video id
*/
public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
- InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI);
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
+ InternalThumbnails.DEFAULT_GROUP_ID);
+ }
+
+ /**
+ * This method checks if the thumbnails of the specified image (origId) has been created.
+ * It will be blocked until the thumbnails are generated.
+ *
+ * @param cr ContentResolver used to dispatch queries to MediaProvider.
+ * @param origId Original image id associated with thumbnail of interest.
+ * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+ * @param options this is only used for MINI_KIND when decoding the Bitmap
+ * @return A Bitmap instance. It could be null if the original image
+ * associated with origId doesn't exist or memory is not enough.
+ */
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+ BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId,
+ InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
+ EXTERNAL_CONTENT_URI, true);
}
/**
@@ -1607,18 +1717,32 @@ public final class MediaStore {
*
* @param cr ContentResolver used to dispatch queries to MediaProvider.
* @param origId Original image id associated with thumbnail of interest.
+ * @param groupId the id of group to which this request belongs
* @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
* @param options this is only used for MINI_KIND when decoding the Bitmap
* @return A Bitmap instance. It could be null if the original image associated with
* origId doesn't exist or memory is not enough.
*/
- public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
- BitmapFactory.Options options) {
- return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
+ int kind, BitmapFactory.Options options) {
+ return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
EXTERNAL_CONTENT_URI, true);
}
/**
+ * This method cancels the thumbnail request so clients waiting for getThumbnail will be
+ * interrupted and return immediately. Only the original process which made the getThumbnail
+ * requests can cancel their own requests.
+ *
+ * @param cr ContentResolver
+ * @param origId original video id
+ * @param groupId the same groupId used in getThumbnail.
+ */
+ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
+ InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
+ }
+
+ /**
* Get the content:// style URI for the image media table on the
* given volume.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f7e55db..e8c09b0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -22,11 +22,14 @@ import org.apache.commons.codec.binary.Base64;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.content.ContentQueryMap;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
+import android.content.IContentProvider;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
@@ -38,13 +41,16 @@ import android.os.*;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.Config;
import android.util.Log;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
/**
@@ -370,6 +376,11 @@ public final class Settings {
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
+ * The account types available to add via the add account button may be restricted by adding an
+ * {@link #EXTRA_AUTHORITIES} extra to this Intent with one or more syncable content provider's
+ * authorities. Only account types which can sync with that content provider will be offered to
+ * the user.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -379,6 +390,24 @@ public final class Settings {
"android.settings.SYNC_SETTINGS";
/**
+ * Activity Action: Show add account screen for creating a new account.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * The account types available to add may be restricted by adding an {@link #EXTRA_AUTHORITIES}
+ * extra to the Intent with one or more syncable content provider's authorities. Only account
+ * types which can sync with that content provider will be offered to the user.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ADD_ACCOUNT =
+ "android.settings.ADD_ACCOUNT_SETTINGS";
+
+ /**
* Activity Action: Show settings for selecting the network operator.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -433,13 +462,66 @@ public final class Settings {
public static final String ACTION_MEMORY_CARD_SETTINGS =
"android.settings.MEMORY_CARD_SETTINGS";
+ /**
+ * Activity Action: Show settings for global search.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEARCH_SETTINGS =
+ "android.search.action.SEARCH_SETTINGS";
+
+ /**
+ * Activity Action: Show general device information settings (serial
+ * number, software version, phone number, etc.).
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DEVICE_INFO_SETTINGS =
+ "android.settings.DEVICE_INFO_SETTINGS";
+
// End of Intent actions for Settings
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'system' table.
+ */
+ public static final String CALL_METHOD_GET_SYSTEM = "GET_system";
+
+ /**
+ * @hide - Private call() method on SettingsProvider to read from 'secure' table.
+ */
+ public static final String CALL_METHOD_GET_SECURE = "GET_secure";
+
+ /**
+ * Activity Extra: Limit available options in launched activity based on the given authority.
+ * <p>
+ * This can be passed as an extra field in an Activity Intent with one or more syncable content
+ * provider's authorities as a String[]. This field is used by some intents to alter the
+ * behavior of the called activity.
+ * <p>
+ * Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types available based
+ * on the authority given.
+ */
+ public static final String EXTRA_AUTHORITIES =
+ "authorities";
+
private static final String JID_RESOURCE_PREFIX = "android";
public static final String AUTHORITY = "settings";
private static final String TAG = "Settings";
+ private static final boolean LOCAL_LOGV = Config.LOGV || false;
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
@@ -474,40 +556,104 @@ public final class Settings {
}
}
+ // Thread-safe.
private static class NameValueCache {
private final String mVersionSystemProperty;
- private final HashMap<String, String> mValues = Maps.newHashMap();
- private long mValuesVersion = 0;
private final Uri mUri;
- NameValueCache(String versionSystemProperty, Uri uri) {
+ private static final String[] SELECT_VALUE =
+ new String[] { Settings.NameValueTable.VALUE };
+ private static final String NAME_EQ_PLACEHOLDER = "name=?";
+
+ // Must synchronize on 'this' to access mValues and mValuesVersion.
+ private final HashMap<String, String> mValues = new HashMap<String, String>();
+ private long mValuesVersion = 0;
+
+ // Initially null; set lazily and held forever. Synchronized on 'this'.
+ private IContentProvider mContentProvider = null;
+
+ // The method we'll call (or null, to not use) on the provider
+ // for the fast path of retrieving settings.
+ private final String mCallCommand;
+
+ public NameValueCache(String versionSystemProperty, Uri uri, String callCommand) {
mVersionSystemProperty = versionSystemProperty;
mUri = uri;
+ mCallCommand = callCommand;
}
- String getString(ContentResolver cr, String name) {
+ public String getString(ContentResolver cr, String name) {
long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
- if (mValuesVersion != newValuesVersion) {
- mValues.clear();
- mValuesVersion = newValuesVersion;
+
+ synchronized (this) {
+ if (mValuesVersion != newValuesVersion) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " +
+ newValuesVersion + " != cached " + mValuesVersion);
+ }
+
+ mValues.clear();
+ mValuesVersion = newValuesVersion;
+ }
+
+ if (mValues.containsKey(name)) {
+ return mValues.get(name); // Could be null, that's OK -- negative caching
+ }
+ }
+
+ IContentProvider cp = null;
+ synchronized (this) {
+ cp = mContentProvider;
+ if (cp == null) {
+ cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
+ }
}
- if (!mValues.containsKey(name)) {
- String value = null;
- Cursor c = null;
+
+ // Try the fast path first, not using query(). If this
+ // fails (alternate Settings provider that doesn't support
+ // this interface?) then we fall back to the query/table
+ // interface.
+ if (mCallCommand != null) {
try {
- c = cr.query(mUri, new String[] { Settings.NameValueTable.VALUE },
- Settings.NameValueTable.NAME + "=?", new String[]{name}, null);
- if (c != null && c.moveToNext()) value = c.getString(0);
+ Bundle b = cp.call(mCallCommand, name, null);
+ if (b != null) {
+ String value = b.getPairValue();
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ return value;
+ }
+ // If the response Bundle is null, we fall through
+ // to the query interface below.
+ } catch (RemoteException e) {
+ // Not supported by the remote side? Fall through
+ // to query().
+ }
+ }
+
+ Cursor c = null;
+ try {
+ c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ new String[]{name}, null);
+ if (c == null) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri);
+ return null;
+ }
+
+ String value = c.moveToNext() ? c.getString(0) : null;
+ synchronized (this) {
mValues.put(name, value);
- } catch (SQLException e) {
- // SQL error: return null, but don't cache it.
- Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
- } finally {
- if (c != null) c.close();
+ }
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "cache miss [" + mUri.getLastPathSegment() + "]: " +
+ name + " = " + (value == null ? "(null)" : value));
}
return value;
- } else {
- return mValues.get(name);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't get key " + name + " from " + mUri, e);
+ return null; // Return null, but don't cache it.
+ } finally {
+ if (c != null) c.close();
}
}
}
@@ -520,7 +666,8 @@ public final class Settings {
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
- private static volatile NameValueCache mNameValueCache = null;
+ // Populated lazily, guarded by class object:
+ private static NameValueCache sNameValueCache = null;
private static final HashSet<String> MOVED_TO_SECURE;
static {
@@ -533,6 +680,9 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
+ MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED);
+ MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_VISIBLE);
+ MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
MOVED_TO_SECURE.add(Secure.LOGGING_ID);
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_ENABLED);
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_LAST_UPDATE);
@@ -569,10 +719,11 @@ public final class Settings {
+ " to android.provider.Settings.Secure, returning read-only value.");
return Secure.getString(resolver, name);
}
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+ if (sNameValueCache == null) {
+ sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
+ CALL_METHOD_GET_SYSTEM);
}
- return mNameValueCache.getString(resolver, name);
+ return sNameValueCache.getString(resolver, name);
}
/**
@@ -839,6 +990,11 @@ public final class Settings {
return Settings.System.putFloat(cr, FONT_SCALE, config.fontScale);
}
+ /** @hide */
+ public static boolean hasInterestingConfigurationChanges(int changes) {
+ return (changes&ActivityInfo.CONFIG_FONT_SCALE) != 0;
+ }
+
public static boolean getShowGTalkServiceStatus(ContentResolver cr) {
return getInt(cr, SHOW_GTALK_SERVICE_STATUS, 0) != 0;
}
@@ -878,6 +1034,24 @@ public final class Settings {
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
/**
+ * END_BUTTON_BEHAVIOR value for "go home".
+ * @hide
+ */
+ public static final int END_BUTTON_BEHAVIOR_HOME = 0x1;
+
+ /**
+ * END_BUTTON_BEHAVIOR value for "go to sleep".
+ * @hide
+ */
+ public static final int END_BUTTON_BEHAVIOR_SLEEP = 0x2;
+
+ /**
+ * END_BUTTON_BEHAVIOR default value.
+ * @hide
+ */
+ public static final int END_BUTTON_BEHAVIOR_DEFAULT = END_BUTTON_BEHAVIOR_SLEEP;
+
+ /**
* Whether Airplane Mode is on.
*/
public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
@@ -1013,18 +1187,25 @@ public final class Settings {
"bluetooth_discoverability_timeout";
/**
- * Whether autolock is enabled (0 = false, 1 = true)
+ * @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
+ * instead
*/
- public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
+ @Deprecated
+ public static final String LOCK_PATTERN_ENABLED = Secure.LOCK_PATTERN_ENABLED;
/**
- * Whether lock pattern is visible as user enters (0 = false, 1 = true)
+ * @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_VISIBLE}
+ * instead
*/
+ @Deprecated
public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
/**
- * Whether lock pattern will vibrate as user enters (0 = false, 1 = true)
+ * @deprecated Use
+ * {@link android.provider.Settings.Secure#LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED}
+ * instead
*/
+ @Deprecated
public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
"lock_pattern_tactile_feedback_enabled";
@@ -1076,19 +1257,16 @@ public final class Settings {
/**
* Control whether to enable automatic brightness mode.
- * @hide
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
- * @hide
*/
public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
- * @hide
*/
public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
@@ -1169,6 +1347,12 @@ public final class Settings {
public static final String VOLUME_NOTIFICATION = "volume_notification";
/**
+ * Bluetooth Headset volume. This is used internally, changing this value will
+ * not change the volume. See AudioManager.
+ */
+ public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
+
+ /**
* Whether the notifications should use the ring volume (value of 1) or
* a separate notification volume (value of 0). In most cases, users
* will have this enabled so the notification and ringer volumes will be
@@ -1185,11 +1369,22 @@ public final class Settings {
"notifications_use_ring_volume";
/**
+ * Whether silent mode should allow vibration feedback. This is used
+ * internally in AudioService and the Sound settings activity to
+ * coordinate decoupling of vibrate and silent modes. This setting
+ * will likely be removed in a future release with support for
+ * audio/vibe feedback profiles.
+ *
+ * @hide
+ */
+ public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
+
+ /**
* The mapping of stream type (integer) to its setting.
*/
public static final String[] VOLUME_SETTINGS = {
VOLUME_VOICE, VOLUME_SYSTEM, VOLUME_RING, VOLUME_MUSIC,
- VOLUME_ALARM, VOLUME_NOTIFICATION
+ VOLUME_ALARM, VOLUME_NOTIFICATION, VOLUME_BLUETOOTH_SCO
};
/**
@@ -1416,13 +1611,80 @@ public final class Settings {
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
/**
+ * Show pointer location on screen?
+ * 0 = no
+ * 1 = yes
+ * @hide
+ */
+ public static final String POINTER_LOCATION = "pointer_location";
+
+ /**
+ * Whether to play a sound for low-battery alerts.
+ * @hide
+ */
+ public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
+
+ /**
+ * Whether to play a sound for dock events.
+ * @hide
+ */
+ public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
+
+ /**
+ * Whether to play sounds when the keyguard is shown and dismissed.
+ * @hide
+ */
+ public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
+
+ /**
+ * URI for the low battery sound file.
+ * @hide
+ */
+ public static final String LOW_BATTERY_SOUND = "low_battery_sound";
+
+ /**
+ * URI for the desk dock "in" event sound.
+ * @hide
+ */
+ public static final String DESK_DOCK_SOUND = "desk_dock_sound";
+
+ /**
+ * URI for the desk dock "out" event sound.
+ * @hide
+ */
+ public static final String DESK_UNDOCK_SOUND = "desk_undock_sound";
+
+ /**
+ * URI for the car dock "in" event sound.
+ * @hide
+ */
+ public static final String CAR_DOCK_SOUND = "car_dock_sound";
+
+ /**
+ * URI for the car dock "out" event sound.
+ * @hide
+ */
+ public static final String CAR_UNDOCK_SOUND = "car_undock_sound";
+
+ /**
+ * URI for the "device locked" (keyguard shown) sound.
+ * @hide
+ */
+ public static final String LOCK_SOUND = "lock_sound";
+
+ /**
+ * URI for the "device unlocked" (keyguard dismissed) sound.
+ * @hide
+ */
+ public static final String UNLOCK_SOUND = "unlock_sound";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
STAY_ON_WHILE_PLUGGED_IN,
- END_BUTTON_BEHAVIOR,
WIFI_SLEEP_POLICY,
WIFI_USE_STATIC_IP,
WIFI_STATIC_IP,
@@ -1447,12 +1709,15 @@ public final class Settings {
VOLUME_MUSIC,
VOLUME_ALARM,
VOLUME_NOTIFICATION,
+ VOLUME_BLUETOOTH_SCO,
VOLUME_VOICE + APPEND_FOR_LAST_AUDIBLE,
VOLUME_SYSTEM + APPEND_FOR_LAST_AUDIBLE,
VOLUME_RING + APPEND_FOR_LAST_AUDIBLE,
VOLUME_MUSIC + APPEND_FOR_LAST_AUDIBLE,
VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE,
VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_BLUETOOTH_SCO + APPEND_FOR_LAST_AUDIBLE,
+ VIBRATE_IN_SILENT,
TEXT_AUTO_REPLACE,
TEXT_AUTO_CAPS,
TEXT_AUTO_PUNCTUATE,
@@ -1469,6 +1734,9 @@ public final class Settings {
TTY_MODE,
SOUND_EFFECTS_ENABLED,
HAPTIC_FEEDBACK_ENABLED,
+ POWER_SOUNDS_ENABLED,
+ DOCK_SOUNDS_ENABLED,
+ LOCKSCREEN_SOUNDS_ENABLED,
SHOW_WEB_SUGGESTIONS,
NOTIFICATION_LIGHT_PULSE
};
@@ -1713,7 +1981,8 @@ public final class Settings {
public static final class Secure extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
- private static volatile NameValueCache mNameValueCache = null;
+ // Populated lazily, guarded by class object:
+ private static NameValueCache sNameValueCache = null;
/**
* Look up a name in the database.
@@ -1722,10 +1991,11 @@ public final class Settings {
* @return the corresponding value, or null if not present
*/
public synchronized static String getString(ContentResolver resolver, String name) {
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
+ if (sNameValueCache == null) {
+ sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
+ CALL_METHOD_GET_SECURE);
}
- return mNameValueCache.getString(resolver, name);
+ return sNameValueCache.getString(resolver, name);
}
/**
@@ -1975,10 +2245,10 @@ public final class Settings {
public static final String ALLOW_MOCK_LOCATION = "mock_location";
/**
- * The Android ID (a unique 64-bit value) as a hex string.
- * Identical to that obtained by calling
- * GoogleLoginService.getAndroidId(); it is also placed here
- * so you can get it without binding to a service.
+ * A 64-bit number (as a hex string) that is randomly
+ * generated on the device's first boot and should remain
+ * constant for the lifetime of the device. (The value may
+ * change if a factory reset is performed on the device.)
*/
public static final String ANDROID_ID = "android_id";
@@ -2028,6 +2298,14 @@ public final class Settings {
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
/**
+ * List of system input methods that are currently disabled. This is a string
+ * containing the IDs of all disabled input methods, each ID separated
+ * by ':'.
+ * @hide
+ */
+ public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
+
+ /**
* Host name and port for a user-selected proxy.
*/
public static final String HTTP_PROXY = "http_proxy";
@@ -2047,6 +2325,22 @@ public final class Settings {
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
/**
+ * Whether autolock is enabled (0 = false, 1 = true)
+ */
+ public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
+
+ /**
+ * Whether lock pattern is visible as user enters (0 = false, 1 = true)
+ */
+ public static final String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
+
+ /**
+ * Whether lock pattern will vibrate as user enters (0 = false, 1 = true)
+ */
+ public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
+ "lock_pattern_tactile_feedback_enabled";
+
+ /**
* Whether assisted GPS should be enabled or not.
* @hide
*/
@@ -2062,27 +2356,29 @@ public final class Settings {
public static final String LOGGING_ID = "logging_id";
/**
- * The Logging ID (a unique 64-bit value) as a hex string.
- * Used as a pseudonymous identifier for logging.
- * @hide
- */
- public static final String LOGGING_ID2 = "logging_id2";
-
- /**
* User preference for which network(s) should be used. Only the
* connectivity service should touch this.
*/
public static final String NETWORK_PREFERENCE = "network_preference";
/**
+ * Used to disable Tethering on a device - defaults to true
+ * @hide
+ */
+ public static final String TETHER_SUPPORTED = "tether_supported";
+
+ /**
+ * No longer supported.
*/
public static final String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
/**
+ * No longer supported.
*/
public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
/**
+ * No longer supported.
*/
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
@@ -2155,6 +2451,11 @@ public final class Settings {
public static final String TTS_DEFAULT_VARIANT = "tts_default_variant";
/**
+ * Space delimited list of plugin packages that are enabled.
+ */
+ public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins";
+
+ /**
* Whether to notify the user of open networks.
* <p>
* If not connected and the scan results have an open network, we will
@@ -2192,6 +2493,36 @@ public final class Settings {
public static final String WIFI_ON = "wifi_on";
/**
+ * Used to save the Wifi_ON state prior to tethering.
+ * This state will be checked to restore Wifi after
+ * the user turns off tethering.
+ *
+ * @hide
+ */
+ public static final String WIFI_SAVED_STATE = "wifi_saved_state";
+
+ /**
+ * AP SSID
+ *
+ * @hide
+ */
+ public static final String WIFI_AP_SSID = "wifi_ap_ssid";
+
+ /**
+ * AP security
+ *
+ * @hide
+ */
+ public static final String WIFI_AP_SECURITY = "wifi_ap_security";
+
+ /**
+ * AP passphrase
+ *
+ * @hide
+ */
+ public static final String WIFI_AP_PASSWD = "wifi_ap_passwd";
+
+ /**
* The acceptable packet loss percentage (range 0 - 100) before trying
* another AP on the same network.
*/
@@ -2286,30 +2617,18 @@ public final class Settings {
public static final String BACKGROUND_DATA = "background_data";
/**
- * The time in msec, when the LAST_KMSG file was send to the checkin server.
- * We will only send the LAST_KMSG file if it was modified after this time.
- *
- * @hide
+ * Origins for which browsers should allow geolocation by default.
+ * The value is a space-separated list of origins.
*/
- public static final String CHECKIN_SEND_LAST_KMSG_TIME = "checkin_kmsg_time";
+ public static final String ALLOWED_GEOLOCATION_ORIGINS
+ = "allowed_geolocation_origins";
/**
- * The time in msec, when the apanic_console file was send to the checkin server.
- * We will only send the apanic_console file if it was modified after this time.
- *
- * @hide
- */
- public static final String CHECKIN_SEND_APANIC_CONSOLE_TIME =
- "checkin_apanic_console_time";
-
- /**
- * The time in msec, when the apanic_thread file was send to the checkin server.
- * We will only send the apanic_thread file if it was modified after this time.
- *
+ * Whether mobile data connections are allowed by the user. See
+ * ConnectivityManager for more info.
* @hide
*/
- public static final String CHECKIN_SEND_APANIC_THREAD_TIME =
- "checkin_apanic_thread_time";
+ public static final String MOBILE_DATA = "mobile_data";
/**
* The CDMA roaming mode 0 = Home Networks, CDMA default
@@ -2385,19 +2704,19 @@ public final class Settings {
public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
/**
- * Flag for allowing service provider to use location information to improve products and
- * services.
- * Type: int ( 0 = disallow, 1 = allow )
+ * Controls whether settings backup is enabled.
+ * Type: int ( 0 = disabled, 1 = enabled )
* @hide
*/
- public static final String USE_LOCATION_FOR_SERVICES = "use_location";
+ public static final String BACKUP_ENABLED = "backup_enabled";
/**
- * Controls whether settings backup is enabled.
+ * Controls whether application data is automatically restored from backup
+ * at install time.
* Type: int ( 0 = disabled, 1 = enabled )
* @hide
*/
- public static final String BACKUP_ENABLED = "backup_enabled";
+ public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
/**
* Indicates whether settings backup has been fully provisioned.
@@ -2420,222 +2739,9 @@ public final class Settings {
public static final String LAST_SETUP_SHOWN = "last_setup_shown";
/**
- * @hide
- */
- public static final String[] SETTINGS_TO_BACKUP = {
- ADB_ENABLED,
- ALLOW_MOCK_LOCATION,
- PARENTAL_CONTROL_ENABLED,
- PARENTAL_CONTROL_REDIRECT_URL,
- USB_MASS_STORAGE_ENABLED,
- ACCESSIBILITY_ENABLED,
- ENABLED_ACCESSIBILITY_SERVICES,
- TTS_USE_DEFAULTS,
- TTS_DEFAULT_RATE,
- TTS_DEFAULT_PITCH,
- TTS_DEFAULT_SYNTH,
- TTS_DEFAULT_LANG,
- TTS_DEFAULT_COUNTRY,
- WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
- WIFI_NUM_ALLOWED_CHANNELS,
- WIFI_NUM_OPEN_NETWORKS_KEPT,
- };
-
- /**
- * Helper method for determining if a location provider is enabled.
- * @param cr the content resolver to use
- * @param provider the location provider to query
- * @return true if the provider is enabled
- *
- * @hide
- */
- public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
- String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
- if (allowedProviders != null) {
- return (allowedProviders.equals(provider) ||
- allowedProviders.contains("," + provider + ",") ||
- allowedProviders.startsWith(provider + ",") ||
- allowedProviders.endsWith("," + provider));
- }
- return false;
- }
-
- /**
- * Thread-safe method for enabling or disabling a single location provider.
- * @param cr the content resolver to use
- * @param provider the location provider to enable or disable
- * @param enabled true if the provider should be enabled
- *
- * @hide
- */
- public static final void setLocationProviderEnabled(ContentResolver cr,
- String provider, boolean enabled) {
- // to ensure thread safety, we write the provider name with a '+' or '-'
- // and let the SettingsProvider handle it rather than reading and modifying
- // the list of enabled providers.
- if (enabled) {
- provider = "+" + provider;
- } else {
- provider = "-" + provider;
- }
- putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
- }
- }
-
- /**
- * Gservices settings, containing the network names for Google's
- * various services. This table holds simple name/addr pairs.
- * Addresses can be accessed through the getString() method.
- *
- * TODO: This should move to partner/google/... somewhere.
- *
- * @hide
- */
- public static final class Gservices extends NameValueTable {
- public static final String SYS_PROP_SETTING_VERSION = "sys.settings_gservices_version";
-
- /**
- * Intent action broadcast when the Gservices table is updated by the server.
- * This is broadcast once after settings change (so many values may have been updated).
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String CHANGED_ACTION =
- "com.google.gservices.intent.action.GSERVICES_CHANGED";
-
- /**
- * Intent action to override Gservices for testing. (Requires WRITE_GSERVICES permission.)
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String OVERRIDE_ACTION =
- "com.google.gservices.intent.action.GSERVICES_OVERRIDE";
-
- private static volatile NameValueCache mNameValueCache = null;
- private static final Object mNameValueCacheLock = new Object();
-
- /**
- * Look up a name in the database.
- * @param resolver to access the database with
- * @param name to look up in the table
- * @return the corresponding value, or null if not present
- */
- public static String getString(ContentResolver resolver, String name) {
- synchronized (mNameValueCacheLock) {
- if (mNameValueCache == null) {
- mNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI);
- }
- return mNameValueCache.getString(resolver, name);
- }
- }
-
- /**
- * Store a name/value pair into the database.
- * @param resolver to access the database with
- * @param name to store
- * @param value to associate with the name
- * @return true if the value was set, false on database errors
- */
- public static boolean putString(ContentResolver resolver,
- String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
- }
-
- /**
- * Look up the value for name in the database, convert it to an int using Integer.parseInt
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
- */
- public static int getInt(ContentResolver resolver, String name, int defValue) {
- String valString = getString(resolver, name);
- int value;
- try {
- value = valString != null ? Integer.parseInt(valString) : defValue;
- } catch (NumberFormatException e) {
- value = defValue;
- }
- return value;
- }
-
- /**
- * Look up the value for name in the database, convert it to a long using Long.parseLong
- * and return it. If it is null or if a NumberFormatException is caught during the
- * conversion then return defValue.
- */
- public static long getLong(ContentResolver resolver, String name, long defValue) {
- String valString = getString(resolver, name);
- long value;
- try {
- value = valString != null ? Long.parseLong(valString) : defValue;
- } catch (NumberFormatException e) {
- value = defValue;
- }
- return value;
- }
-
- /**
- * Construct the content URI for a particular name/value pair,
- * useful for monitoring changes with a ContentObserver.
- * @param name to look up in the table
- * @return the corresponding content URI, or null if not present
- */
- public static Uri getUriFor(String name) {
- return getUriFor(CONTENT_URI, name);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/gservices");
-
- /**
- * MMS - URL to use for HTTP "x-wap-profile" header
- */
- public static final String MMS_X_WAP_PROFILE_URL
- = "mms_x_wap_profile_url";
-
- /**
- * YouTube - the flag to indicate whether to use proxy
- */
- public static final String YOUTUBE_USE_PROXY
- = "youtube_use_proxy";
-
- /**
- * MMS - maximum message size in bytes for a MMS message.
- */
- public static final String MMS_MAXIMUM_MESSAGE_SIZE
- = "mms_maximum_message_size";
-
- /**
- * Event tags from the kernel event log to upload during checkin.
- */
- public static final String CHECKIN_EVENTS = "checkin_events";
-
- /**
- * Comma-separated list of service names to dump and upload during checkin.
- */
- public static final String CHECKIN_DUMPSYS_LIST = "checkin_dumpsys_list";
-
- /**
- * Comma-separated list of packages to specify for each service that is
- * dumped (currently only meaningful for user activity).
- */
- public static final String CHECKIN_PACKAGE_LIST = "checkin_package_list";
-
- /**
- * The interval (in seconds) between periodic checkin attempts.
- */
- public static final String CHECKIN_INTERVAL = "checkin_interval";
-
- /**
- * Boolean indicating if the market app should force market only checkins on
- * install/uninstall. Any non-0 value is considered true.
- */
- public static final String MARKET_FORCE_CHECKIN = "market_force_checkin";
-
- /**
* How frequently (in seconds) to check the memory status of the
* device.
+ * @hide
*/
public static final String MEMCHECK_INTERVAL = "memcheck_interval";
@@ -2643,6 +2749,7 @@ public final class Settings {
* Max frequency (in seconds) to log memory check stats, in realtime
* seconds. This allows for throttling of logs when the device is
* running for large amounts of time.
+ * @hide
*/
public static final String MEMCHECK_LOG_REALTIME_INTERVAL =
"memcheck_log_realtime_interval";
@@ -2650,6 +2757,7 @@ public final class Settings {
/**
* Boolean indicating whether rebooting due to system memory checks
* is enabled.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_ENABLED = "memcheck_system_enabled";
@@ -2657,12 +2765,14 @@ public final class Settings {
* How many bytes the system process must be below to avoid scheduling
* a soft reboot. This reboot will happen when it is next determined
* to be a good time.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_SOFT_THRESHOLD = "memcheck_system_soft";
/**
* How many bytes the system process must be below to avoid scheduling
* a hard reboot. This reboot will happen immediately.
+ * @hide
*/
public static final String MEMCHECK_SYSTEM_HARD_THRESHOLD = "memcheck_system_hard";
@@ -2670,18 +2780,21 @@ public final class Settings {
* How many bytes the phone process must be below to avoid scheduling
* a soft restart. This restart will happen when it is next determined
* to be a good time.
+ * @hide
*/
public static final String MEMCHECK_PHONE_SOFT_THRESHOLD = "memcheck_phone_soft";
/**
* How many bytes the phone process must be below to avoid scheduling
* a hard restart. This restart will happen immediately.
+ * @hide
*/
public static final String MEMCHECK_PHONE_HARD_THRESHOLD = "memcheck_phone_hard";
/**
* Boolean indicating whether restarting the phone process due to
* memory checks is enabled.
+ * @hide
*/
public static final String MEMCHECK_PHONE_ENABLED = "memcheck_phone_enabled";
@@ -2689,6 +2802,7 @@ public final class Settings {
* First time during the day it is okay to kill processes
* or reboot the device due to low memory situations. This number is
* in seconds since midnight.
+ * @hide
*/
public static final String MEMCHECK_EXEC_START_TIME = "memcheck_exec_start_time";
@@ -2696,6 +2810,7 @@ public final class Settings {
* Last time during the day it is okay to kill processes
* or reboot the device due to low memory situations. This number is
* in seconds since midnight.
+ * @hide
*/
public static final String MEMCHECK_EXEC_END_TIME = "memcheck_exec_end_time";
@@ -2703,6 +2818,7 @@ public final class Settings {
* How long the screen must have been off in order to kill processes
* or reboot. This number is in seconds. A value of -1 means to
* entirely disregard whether the screen is on.
+ * @hide
*/
public static final String MEMCHECK_MIN_SCREEN_OFF = "memcheck_min_screen_off";
@@ -2711,6 +2827,7 @@ public final class Settings {
* or reboot. This number is in seconds. Note: this value must be
* smaller than {@link #MEMCHECK_RECHECK_INTERVAL} or else it will
* always see an alarm scheduled within its time.
+ * @hide
*/
public static final String MEMCHECK_MIN_ALARM = "memcheck_min_alarm";
@@ -2720,12 +2837,14 @@ public final class Settings {
* this value must be larger than {@link #MEMCHECK_MIN_ALARM} or else
* the alarm to schedule the recheck will always appear within the
* minimum "do not execute now" time.
+ * @hide
*/
public static final String MEMCHECK_RECHECK_INTERVAL = "memcheck_recheck_interval";
/**
* How frequently (in DAYS) to reboot the device. If 0, no reboots
* will occur.
+ * @hide
*/
public static final String REBOOT_INTERVAL = "reboot_interval";
@@ -2733,6 +2852,7 @@ public final class Settings {
* First time during the day it is okay to force a reboot of the
* device (if REBOOT_INTERVAL is set). This number is
* in seconds since midnight.
+ * @hide
*/
public static final String REBOOT_START_TIME = "reboot_start_time";
@@ -2741,674 +2861,132 @@ public final class Settings {
* a reboot can be executed. If 0, a reboot will always be executed at
* exactly the given time. Otherwise, it will only be executed if
* the device is idle within the window.
+ * @hide
*/
public static final String REBOOT_WINDOW = "reboot_window";
/**
- * The minimum version of the server that is required in order for the device to accept
- * the server's recommendations about the initial sync settings to use. When this is unset,
- * blank or can't be interpreted as an integer then we will not ask the server for a
- * recommendation.
- */
- public static final String GMAIL_CONFIG_INFO_MIN_SERVER_VERSION =
- "gmail_config_info_min_server_version";
-
- /**
- * Controls whether Gmail offers a preview button for images.
- */
- public static final String GMAIL_DISALLOW_IMAGE_PREVIEWS = "gmail_disallow_image_previews";
-
- /**
- * The maximal size in bytes allowed for attachments when composing messages in Gmail
- */
- public static final String GMAIL_MAX_ATTACHMENT_SIZE = "gmail_max_attachment_size_bytes";
-
- /**
- * The timeout in milliseconds that Gmail uses when opening a connection and reading
- * from it. A missing value or a value of -1 instructs Gmail to use the defaults provided
- * by GoogleHttpClient.
- */
- public static final String GMAIL_TIMEOUT_MS = "gmail_timeout_ms";
-
- /**
- * Controls whether Gmail will request an expedited sync when a message is sent. Value must
- * be an integer where non-zero means true. Defaults to 1.
- */
- public static final String GMAIL_SEND_IMMEDIATELY = "gmail_send_immediately";
-
- /**
- * Controls whether gmail buffers server responses. Possible values are "memory", for a
- * memory-based buffer, or "file", for a temp-file-based buffer. All other values
- * (including not set) disable buffering.
- */
- public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
-
- /**
- * The maximum size in bytes allowed for the provider to gzip a protocol buffer uploaded to
- * the server.
- */
- public static final String GMAIL_MAX_GZIP_SIZE = "gmail_max_gzip_size_bytes";
-
- /**
- * Controls whether Gmail will discard uphill operations that repeatedly fail. Value must be
- * an integer where non-zero means true. Defaults to 1. This flag controls Donut devices.
- */
- public static final String GMAIL_DISCARD_ERROR_UPHILL_OP = "gmail_discard_error_uphill_op";
-
- /**
- * Controls whether Gmail will discard uphill operations that repeatedly fail. Value must be
- * an integer where non-zero means true. Defaults to 1. This flag controls Eclair and
- * future devices.
- */
- public static final String GMAIL_DISCARD_ERROR_UPHILL_OP_NEW =
- "gmail_discard_error_uphill_op_new";
-
- /**
- * Controls how many attempts Gmail will try to upload an uphill operations before it
- * abandons the operation. Defaults to 20.
- */
- public static final String GMAIL_NUM_RETRY_UPHILL_OP = "gmail_num_retry_uphill_op";
-
- /**
- * How much time in seconds Gmail will try to upload an uphill operations before it
- * abandons the operation. Defaults to 36400 (one day).
- */
- public static final String GMAIL_WAIT_TIME_RETRY_UPHILL_OP =
- "gmail_wait_time_retry_uphill_op";
-
- /**
- * Controls if the protocol buffer version of the protocol will use a multipart request for
- * attachment uploads. Value must be an integer where non-zero means true. Defaults to 0.
- */
- public static final String GMAIL_USE_MULTIPART_PROTOBUF = "gmail_use_multipart_protobuf";
-
- /**
- * the transcoder URL for mobile devices.
- */
- public static final String TRANSCODER_URL = "mobile_transcoder_url";
-
- /**
- * URL that points to the privacy terms of the Google Talk service.
- */
- public static final String GTALK_TERMS_OF_SERVICE_URL = "gtalk_terms_of_service_url";
-
- /**
- * Hostname of the GTalk server.
- */
- public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
-
- /**
- * Secure port of the GTalk server.
- */
- public static final String GTALK_SERVICE_SECURE_PORT = "gtalk_secure_port";
-
- /**
- * The server configurable RMQ acking interval
- */
- public static final String GTALK_SERVICE_RMQ_ACK_INTERVAL = "gtalk_rmq_ack_interval";
-
- /**
- * The minimum reconnect delay for short network outages or when the network is suspended
- * due to phone use.
- */
- public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT =
- "gtalk_min_reconnect_delay_short";
-
- /**
- * The reconnect variant range for short network outages or when the network is suspended
- * due to phone use. A random number between 0 and this constant is computed and
- * added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_SHORT} to form the initial reconnect
- * delay.
- */
- public static final String GTALK_SERVICE_RECONNECT_VARIANT_SHORT =
- "gtalk_reconnect_variant_short";
-
- /**
- * The minimum reconnect delay for long network outages
- */
- public static final String GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG =
- "gtalk_min_reconnect_delay_long";
-
- /**
- * The reconnect variant range for long network outages. A random number between 0 and this
- * constant is computed and added to {@link #GTALK_SERVICE_MIN_RECONNECT_DELAY_LONG} to
- * form the initial reconnect delay.
- */
- public static final String GTALK_SERVICE_RECONNECT_VARIANT_LONG =
- "gtalk_reconnect_variant_long";
-
- /**
- * The maximum reconnect delay time, in milliseconds.
- */
- public static final String GTALK_SERVICE_MAX_RECONNECT_DELAY =
- "gtalk_max_reconnect_delay";
-
- /**
- * The network downtime that is considered "short" for the above calculations,
- * in milliseconds.
- */
- public static final String GTALK_SERVICE_SHORT_NETWORK_DOWNTIME =
- "gtalk_short_network_downtime";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The away heartbeat should be used when the user is
- * logged into the GTalk app, but not actively using it.
- */
- public static final String GTALK_SERVICE_AWAY_HEARTBEAT_INTERVAL_MS =
- "gtalk_heartbeat_ping_interval_ms"; // keep the string backward compatible
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The active heartbeat should be used when the user is
- * actively using the GTalk app.
- */
- public static final String GTALK_SERVICE_ACTIVE_HEARTBEAT_INTERVAL_MS =
- "gtalk_active_heartbeat_ping_interval_ms";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The sync heartbeat should be used when the user isn't
- * logged into the GTalk app, but auto-sync is enabled.
- */
- public static final String GTALK_SERVICE_SYNC_HEARTBEAT_INTERVAL_MS =
- "gtalk_sync_heartbeat_ping_interval_ms";
-
- /**
- * How frequently we send heartbeat pings to the GTalk server. Receiving a server packet
- * will reset the heartbeat timer. The no sync heartbeat should be used when the user isn't
- * logged into the GTalk app, and auto-sync is not enabled.
- */
- public static final String GTALK_SERVICE_NOSYNC_HEARTBEAT_INTERVAL_MS =
- "gtalk_nosync_heartbeat_ping_interval_ms";
-
- /**
- * The maximum heartbeat interval used while on the WIFI network.
- */
- public static final String GTALK_SERVICE_WIFI_MAX_HEARTBEAT_INTERVAL_MS =
- "gtalk_wifi_max_heartbeat_ping_interval_ms";
-
- /**
- * How long we wait to receive a heartbeat ping acknowledgement (or another packet)
- * from the GTalk server, before deeming the connection dead.
- */
- public static final String GTALK_SERVICE_HEARTBEAT_ACK_TIMEOUT_MS =
- "gtalk_heartbeat_ack_timeout_ms";
-
- /**
- * How long after screen is turned off before we consider the user to be idle.
- */
- public static final String GTALK_SERVICE_IDLE_TIMEOUT_MS =
- "gtalk_idle_timeout_ms";
-
- /**
- * By default, GTalkService will always connect to the server regardless of the auto-sync
- * setting. However, if this parameter is true, then GTalkService will only connect
- * if auto-sync is enabled. Using the GTalk app will trigger the connection too.
- */
- public static final String GTALK_SERVICE_CONNECT_ON_AUTO_SYNC =
- "gtalk_connect_on_auto_sync";
-
- /**
- * GTalkService holds a wakelock while broadcasting the intent for data message received.
- * It then automatically release the wakelock after a timeout. This setting controls what
- * the timeout should be.
- */
- public static final String GTALK_DATA_MESSAGE_WAKELOCK_MS =
- "gtalk_data_message_wakelock_ms";
-
- /**
- * The socket read timeout used to control how long ssl handshake wait for reads before
- * timing out. This is needed so the ssl handshake doesn't hang for a long time in some
- * circumstances.
- */
- public static final String GTALK_SSL_HANDSHAKE_TIMEOUT_MS =
- "gtalk_ssl_handshake_timeout_ms";
-
- /**
- * Compress the gtalk stream.
- */
- public static final String GTALK_COMPRESS = "gtalk_compress";
-
- /**
- * This is the timeout for which Google Talk will send the message using bareJID. In a
- * established chat between two XMPP endpoints, Google Talk uses fullJID in the format
- * of user@domain/resource in order to send the message to the specific client. However,
- * if Google Talk hasn't received a message from that client after some time, it would
- * fall back to use the bareJID, which would broadcast the message to all clients for
- * the other user.
- */
- public static final String GTALK_USE_BARE_JID_TIMEOUT_MS = "gtalk_use_barejid_timeout_ms";
-
- /**
- * This is the threshold of retry number when there is an authentication expired failure
- * for Google Talk. In some situation, e.g. when a Google Apps account is disabled chat
- * service, the connection keeps failing. This threshold controls when we should stop
- * the retrying.
- */
- public static final String GTALK_MAX_RETRIES_FOR_AUTH_EXPIRED =
- "gtalk_max_retries_for_auth_expired";
-
- /**
- * a boolean setting indicating whether the GTalkService should use RMQ2 protocol or not.
- */
- public static final String GTALK_USE_RMQ2_PROTOCOL =
- "gtalk_use_rmq2";
-
- /**
- * a boolean setting indicating whether the GTalkService should support both RMQ and
- * RMQ2 protocols. This setting is true for the transitional period when we need to
- * support both protocols.
- */
- public static final String GTALK_SUPPORT_RMQ_AND_RMQ2_PROTOCOLS =
- "gtalk_support_rmq_and_rmq2";
-
- /**
- * a boolean setting controlling whether the rmq2 protocol will include stream ids in
- * the protobufs. This is used for debugging.
- */
- public static final String GTALK_RMQ2_INCLUDE_STREAM_ID =
- "gtalk_rmq2_include_stream_id";
-
- /**
- * when receiving a chat message from the server, the message could be an older message
- * whose "time sent" is x seconds from now. If x is significant enough, we want to flag
- * it so the UI can give it some special treatment when displaying the "time sent" for
- * it. This setting is to control what x is.
- */
- public static final String GTALK_OLD_CHAT_MESSAGE_THRESHOLD_IN_SEC =
- "gtalk_old_chat_msg_threshold_in_sec";
-
- /**
- * a setting to control the max connection history record GTalkService stores.
- */
- public static final String GTALK_MAX_CONNECTION_HISTORY_RECORDS =
- "gtalk_max_conn_history_records";
-
- /**
- * This is gdata url to lookup album and picture info from picasa web. It also controls
- * whether url scraping for picasa is enabled (NULL to disable).
- */
- public static final String GTALK_PICASA_ALBUM_URL =
- "gtalk_picasa_album_url";
-
- /**
- * This is the url to lookup picture info from flickr. It also controls
- * whether url scraping for flickr is enabled (NULL to disable).
- */
- public static final String GTALK_FLICKR_PHOTO_INFO_URL =
- "gtalk_flickr_photo_info_url";
-
- /**
- * This is the url to lookup an actual picture from flickr.
- */
- public static final String GTALK_FLICKR_PHOTO_URL =
- "gtalk_flickr_photo_url";
-
- /**
- * This is the gdata url to lookup info on a youtube video. It also controls
- * whether url scraping for youtube is enabled (NULL to disable).
- */
- public static final String GTALK_YOUTUBE_VIDEO_URL =
- "gtalk_youtube_video_url";
-
- /**
- * Enable/disable GTalk URL scraping for JPG images ("true" to enable).
- */
- public static final String GTALK_URL_SCRAPING_FOR_JPG =
- "gtalk_url_scraping_for_jpg";
-
- /**
- * Chat message lifetime (for pruning old chat messages).
- */
- public static final String GTALK_CHAT_MESSAGE_LIFETIME =
- "gtalk_chat_message_lifetime";
-
- /**
- * OTR message lifetime (for pruning old otr messages).
- */
- public static final String GTALK_OTR_MESSAGE_LIFETIME =
- "gtalk_otr_message_lifetime";
-
- /**
- * Chat expiration time, i.e., time since last message in the chat (for pruning old chats).
- */
- public static final String GTALK_CHAT_EXPIRATION_TIME =
- "gtalk_chat_expiration_time";
-
- /**
- * This is the url for getting the app token for server-to-device push messaging.
- */
- public static final String PUSH_MESSAGING_REGISTRATION_URL =
- "push_messaging_registration_url";
-
- /**
- * Use android://&lt;it&gt; routing infos for Google Sync Server subcriptions.
- */
- public static final String GSYNC_USE_RMQ2_ROUTING_INFO = "gsync_use_rmq2_routing_info";
-
- /**
- * Enable use of ssl session caching.
- * 'db' - save each session in a (per process) database
- * 'file' - save each session in a (per process) file
- * not set or any other value - normal java in-memory caching
- */
- public static final String SSL_SESSION_CACHE = "ssl_session_cache";
-
- /**
- * How many bytes long a message has to be, in order to be gzipped.
- */
- public static final String SYNC_MIN_GZIP_BYTES =
- "sync_min_gzip_bytes";
-
- /**
- * The hash value of the current provisioning settings
- */
- public static final String PROVISIONING_DIGEST = "digest";
-
- /**
- * Provisioning keys to block from server update
- */
- public static final String PROVISIONING_OVERRIDE = "override";
-
- /**
- * "Generic" service name for authentication requests.
- */
- public static final String GOOGLE_LOGIN_GENERIC_AUTH_SERVICE
- = "google_login_generic_auth_service";
-
- /**
- * Frequency in milliseconds at which we should sync the locally installed Vending Machine
- * content with the server.
- */
- public static final String VENDING_SYNC_FREQUENCY_MS = "vending_sync_frequency_ms";
-
- /**
- * Support URL that is opened in a browser when user clicks on 'Help and Info' in Vending
- * Machine.
- */
- public static final String VENDING_SUPPORT_URL = "vending_support_url";
-
- /**
- * Indicates if Vending Machine requires a SIM to be in the phone to allow a purchase.
- *
- * true = SIM is required
- * false = SIM is not required
- */
- public static final String VENDING_REQUIRE_SIM_FOR_PURCHASE =
- "vending_require_sim_for_purchase";
-
- /**
- * Indicates the Vending Machine backup state. It is set if the
- * Vending application has been backed up at least once.
- */
- public static final String VENDING_BACKUP_STATE = "vending_backup_state";
-
- /**
- * The current version id of the Vending Machine terms of service.
- */
- public static final String VENDING_TOS_VERSION = "vending_tos_version";
-
- /**
- * URL that points to the terms of service for Vending Machine.
- */
- public static final String VENDING_TOS_URL = "vending_tos_url";
-
- /**
- * URL to navigate to in browser (not Market) when the terms of service
- * for Vending Machine could not be accessed due to bad network
- * connection.
- */
- public static final String VENDING_TOS_MISSING_URL = "vending_tos_missing_url";
-
- /**
- * Whether to use sierraqa instead of sierra tokens for the purchase flow in
- * Vending Machine.
- *
- * true = use sierraqa
- * false = use sierra (default)
- */
- public static final String VENDING_USE_CHECKOUT_QA_SERVICE =
- "vending_use_checkout_qa_service";
-
- /**
- * Default value to use for all/free/priced filter in Market.
- * Valid values: ALL, FREE, PAID (case insensitive)
- */
- public static final String VENDING_DEFAULT_FILTER = "vending_default_filter";
- /**
- * Ranking type value to use for the first category tab (currently popular)
- */
- public static final String VENDING_TAB_1_RANKING_TYPE = "vending_tab_1_ranking_type";
-
- /**
- * Title string to use for first category tab.
- */
- public static final String VENDING_TAB_1_TITLE = "vending_tab_1_title";
-
- /**
- * Ranking type value to use for the second category tab (currently newest)
- */
- public static final String VENDING_TAB_2_RANKING_TYPE = "vending_tab_2_ranking_type";
-
- /**
- * Title string to use for second category tab.
- */
- public static final String VENDING_TAB_2_TITLE = "vending_tab_2_title";
-
- /**
- * Frequency in milliseconds at which we should request MCS heartbeats
- * from the Vending Machine client.
- */
- public static final String VENDING_HEARTBEAT_FREQUENCY_MS =
- "vending_heartbeat_frequency_ms";
-
- /**
- * Frequency in milliseconds at which we should resend pending download
- * requests to the API Server from the Vending Machine client.
- */
- public static final String VENDING_PENDING_DOWNLOAD_RESEND_FREQUENCY_MS =
- "vending_pd_resend_frequency_ms";
-
- /**
- * Time before an asset in the 'DOWNLOADING' state is considered ready
- * for an install kick on the client.
- */
- public static final String VENDING_DOWNLOADING_KICK_TIMEOUT_MS =
- "vending_downloading_kick_ms";
-
- /**
- * Size of buffer in bytes for Vending to use when reading cache files.
- */
- public static final String VENDING_DISK_INPUT_BUFFER_BYTES =
- "vending_disk_input_buffer_bytes";
-
- /**
- * Size of buffer in bytes for Vending to use when writing cache files.
- */
- public static final String VENDING_DISK_OUTPUT_BUFFER_BYTES =
- "vending_disk_output_buffer_bytes";
-
- /**
- * Frequency in milliseconds at which we should cycle through the promoted applications
- * on the home screen or the categories page.
- */
- public static final String VENDING_PROMO_REFRESH_FREQUENCY_MS =
- "vending_promo_refresh_freq_ms";
-
- /**
- * Frequency in milliseconds when we should refresh the provisioning information from
- * the carrier backend.
- */
- public static final String VENDING_CARRIER_PROVISIONING_REFRESH_FREQUENCY_MS =
- "vending_carrier_ref_freq_ms";
-
- /**
- * Interval in milliseconds after which a failed provisioning request should be retried.
- */
- public static final String VENDING_CARRIER_PROVISIONING_RETRY_MS =
- "vending_carrier_prov_retry_ms";
-
- /**
- * Buffer in milliseconds for carrier credentials to be considered valid.
- */
- public static final String VENDING_CARRIER_CREDENTIALS_BUFFER_MS =
- "vending_carrier_cred_buf_ms";
-
- /**
- * URL that points to the legal terms of service to display in Settings.
- * <p>
- * This should be a https URL. For a pretty user-friendly URL, use
- * {@link #SETTINGS_TOS_PRETTY_URL}.
- */
- public static final String SETTINGS_TOS_URL = "settings_tos_url";
-
- /**
- * URL that points to the legal terms of service to display in Settings.
- * <p>
- * This should be a pretty http URL. For the URL the device will access
- * via Settings, use {@link #SETTINGS_TOS_URL}.
+ * Threshold values for the duration and level of a discharge cycle, under
+ * which we log discharge cycle info.
+ * @hide
*/
- public static final String SETTINGS_TOS_PRETTY_URL = "settings_tos_pretty_url";
+ public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
+ "battery_discharge_duration_threshold";
+ /** @hide */
+ public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
/**
- * URL that points to the contributors to display in Settings.
- * <p>
- * This should be a https URL. For a pretty user-friendly URL, use
- * {@link #SETTINGS_CONTRIBUTORS_PRETTY_URL}.
+ * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
+ * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
+ * will never display the "Report" button.
+ * Type: int ( 0 = disallow, 1 = allow )
+ * @hide
*/
- public static final String SETTINGS_CONTRIBUTORS_URL = "settings_contributors_url";
+ public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
/**
- * URL that points to the contributors to display in Settings.
- * <p>
- * This should be a pretty http URL. For the URL the device will access
- * via Settings, use {@link #SETTINGS_CONTRIBUTORS_URL}.
+ * Nonzero causes Log.wtf() to crash.
+ * @hide
*/
- public static final String SETTINGS_CONTRIBUTORS_PRETTY_URL =
- "settings_contributors_pretty_url";
+ public static final String WTF_IS_FATAL = "wtf_is_fatal";
/**
- * URL that points to the Terms Of Service for the device.
- * <p>
- * This should be a pretty http URL.
+ * Maximum age of entries kept by {@link android.os.IDropBox}.
+ * @hide
*/
- public static final String SETUP_GOOGLE_TOS_URL = "setup_google_tos_url";
-
+ public static final String DROPBOX_AGE_SECONDS =
+ "dropbox_age_seconds";
/**
- * URL that points to the Android privacy policy for the device.
- * <p>
- * This should be a pretty http URL.
+ * Maximum number of entry files which {@link android.os.IDropBox} will keep around.
+ * @hide
*/
- public static final String SETUP_ANDROID_PRIVACY_URL = "setup_android_privacy_url";
-
+ public static final String DROPBOX_MAX_FILES =
+ "dropbox_max_files";
/**
- * URL that points to the Google privacy policy for the device.
- * <p>
- * This should be a pretty http URL.
+ * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ * @hide
*/
- public static final String SETUP_GOOGLE_PRIVACY_URL = "setup_google_privacy_url";
-
+ public static final String DROPBOX_QUOTA_KB =
+ "dropbox_quota_kb";
/**
- * Request an MSISDN token for various Google services.
+ * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ * @hide
*/
- public static final String USE_MSISDN_TOKEN = "use_msisdn_token";
-
+ public static final String DROPBOX_QUOTA_PERCENT =
+ "dropbox_quota_percent";
/**
- * RSA public key used to encrypt passwords stored in the database.
+ * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ * @hide
*/
- public static final String GLS_PUBLIC_KEY = "google_login_public_key";
-
+ public static final String DROPBOX_RESERVE_PERCENT =
+ "dropbox_reserve_percent";
/**
- * Only check parental control status if this is set to "true".
+ * Prefix for per-tag dropbox disable/enable settings.
+ * @hide
*/
- public static final String PARENTAL_CONTROL_CHECK_ENABLED =
- "parental_control_check_enabled";
-
+ public static final String DROPBOX_TAG_PREFIX =
+ "dropbox:";
/**
- * The list of applications we need to block if parental control is
- * enabled.
+ * Lines of logcat to include with system crash/ANR/etc. reports,
+ * as a prefix of the dropbox tag of the report type.
+ * For example, "logcat_for_system_server_anr" controls the lines
+ * of logcat captured with system server ANR reports. 0 to disable.
+ * @hide
*/
- public static final String PARENTAL_CONTROL_APPS_LIST =
- "parental_control_apps_list";
+ public static final String ERROR_LOGCAT_PREFIX =
+ "logcat_for_";
- /**
- * Duration in which parental control status is valid.
- */
- public static final String PARENTAL_CONTROL_TIMEOUT_IN_MS =
- "parental_control_timeout_in_ms";
/**
- * When parental control is off, we expect to get this string from the
- * litmus url.
+ * Screen timeout in milliseconds corresponding to the
+ * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
+ * possible screen timeout behavior.)
+ * @hide
*/
- public static final String PARENTAL_CONTROL_EXPECTED_RESPONSE =
- "parental_control_expected_response";
+ public static final String SHORT_KEYLIGHT_DELAY_MS =
+ "short_keylight_delay_ms";
/**
- * When the litmus url returns a 302, declare parental control to be on
- * only if the redirect url matches this regular expression.
+ * The interval in minutes after which the amount of free storage left on the
+ * device is logged to the event log
+ * @hide
*/
- public static final String PARENTAL_CONTROL_REDIRECT_REGEX =
- "parental_control_redirect_regex";
+ public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
+ "sys_free_storage_log_interval";
/**
* Threshold for the amount of change in disk free space required to report the amount of
* free space. Used to prevent spamming the logs when the disk free space isn't changing
* frequently.
+ * @hide
*/
public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD =
"disk_free_change_reporting_threshold";
- /**
- * Prefix for new Google services published by the checkin
- * server.
- */
- public static final String GOOGLE_SERVICES_PREFIX
- = "google_services:";
-
- /**
- * The maximum reconnect delay for short network outages or when the network is suspended
- * due to phone use.
- */
- public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
- "sync_max_retry_delay_in_seconds";
/**
* Minimum percentage of free storage on the device that is used to determine if
* the device is running low on storage.
* Say this value is set to 10, the device is considered running low on storage
* if 90% or more of the device storage is filled up.
+ * @hide
*/
public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
"sys_storage_threshold_percentage";
/**
- * The interval in minutes after which the amount of free storage left on the
- * device is logged to the event log
- */
- public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
- "sys_free_storage_log_interval";
-
- /**
- * The interval in milliseconds at which to check the number of SMS sent
- * out without asking for use permit, to limit the un-authorized SMS
- * usage.
- */
- public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
- "sms_outgoing_check_interval_ms";
-
- /**
- * The number of outgoing SMS sent without asking for user permit
- * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
+ * The interval in milliseconds after which Wi-Fi is considered idle.
+ * When idle, it is possible for the device to be switched from Wi-Fi to
+ * the mobile data network.
+ * @hide
*/
- public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
- "sms_outgoing_check_max_count";
+ public static final String WIFI_IDLE_MS = "wifi_idle_ms";
/**
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is on, to detect possible data
* connection problems.
+ * @hide
*/
public static final String PDP_WATCHDOG_POLL_INTERVAL_MS =
"pdp_watchdog_poll_interval_ms";
@@ -3417,6 +2995,7 @@ public final class Settings {
* The interval in milliseconds at which to check packet counts on the
* mobile data interface when screen is off, to detect possible data
* connection problems.
+ * @hide
*/
public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
"pdp_watchdog_long_poll_interval_ms";
@@ -3425,6 +3004,7 @@ public final class Settings {
* The interval in milliseconds at which to check packet counts on the
* mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
* outgoing packets has been reached without incoming packets.
+ * @hide
*/
public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
"pdp_watchdog_error_poll_interval_ms";
@@ -3433,6 +3013,7 @@ public final class Settings {
* The number of outgoing packets sent without seeing an incoming packet
* that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
* device is logged to the event log
+ * @hide
*/
public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
"pdp_watchdog_trigger_packet_count";
@@ -3441,6 +3022,7 @@ public final class Settings {
* The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS})
* after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
* attempting data connection recovery.
+ * @hide
*/
public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
"pdp_watchdog_error_poll_count";
@@ -3448,6 +3030,7 @@ public final class Settings {
/**
* The number of failed PDP reset attempts before moving to something more
* drastic: re-registering to the network.
+ * @hide
*/
public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
"pdp_watchdog_max_pdp_reset_fail_count";
@@ -3455,12 +3038,14 @@ public final class Settings {
/**
* Address to ping as a last sanity check before attempting any recovery.
* Unset or set to "0.0.0.0" to skip this check.
+ * @hide
*/
public static final String PDP_WATCHDOG_PING_ADDRESS = "pdp_watchdog_ping_address";
/**
* The "-w deadline" parameter for the ping, ie, the max time in
* seconds to spend pinging.
+ * @hide
*/
public static final String PDP_WATCHDOG_PING_DEADLINE = "pdp_watchdog_ping_deadline";
@@ -3469,221 +3054,350 @@ public final class Settings {
* after the first registration mismatch of gprs and voice service,
* to detect possible data network registration problems.
*
+ * @hide
*/
public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
"gprs_register_check_period_ms";
/**
- * The interval in milliseconds after which Wi-Fi is considered idle.
- * When idle, it is possible for the device to be switched from Wi-Fi to
- * the mobile data network.
- */
- public static final String WIFI_IDLE_MS = "wifi_idle_ms";
-
- /**
- * Screen timeout in milliseconds corresponding to the
- * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
- * possible screen timeout behavior.)
- */
- public static final String SHORT_KEYLIGHT_DELAY_MS =
- "short_keylight_delay_ms";
-
- /**
- * List of test suites (local disk filename) for the automatic instrumentation test runner.
- * The file format is similar to automated_suites.xml, see AutoTesterService.
- * If this setting is missing or empty, the automatic test runner will not start.
- */
- public static final String AUTOTEST_SUITES_FILE = "autotest_suites_file";
-
- /**
- * Interval between synchronous checkins forced by the automatic test runner.
- * If you set this to a value smaller than CHECKIN_INTERVAL, then the test runner's
- * frequent checkins will prevent asynchronous background checkins from interfering
- * with any performance measurements.
- */
- public static final String AUTOTEST_CHECKIN_SECONDS = "autotest_checkin_seconds";
-
- /**
- * Interval between reboots forced by the automatic test runner.
- */
- public static final String AUTOTEST_REBOOT_SECONDS = "autotest_reboot_seconds";
-
-
- /**
- * Threshold values for the duration and level of a discharge cycle, under
- * which we log discharge cycle info.
+ * The length of time in milli-seconds that automatic small adjustments to
+ * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * @hide
*/
- public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
- "battery_discharge_duration_threshold";
- public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+ public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
/**
- * An email address that anr bugreports should be sent to.
+ * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
+ * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
+ * exceeded.
+ * @hide
*/
- public static final String ANR_BUGREPORT_RECIPIENT = "anr_bugreport_recipient";
+ public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
/**
- * Flag for allowing service provider to use location information to improve products and
- * services.
- * Type: int ( 0 = disallow, 1 = allow )
- * @deprecated
+ * The maximum reconnect delay for short network outages or when the network is suspended
+ * due to phone use.
+ * @hide
*/
- public static final String USE_LOCATION_FOR_SERVICES = "use_location";
+ public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
+ "sync_max_retry_delay_in_seconds";
/**
- * The length of the calendar sync window into the future.
- * This specifies the number of days into the future for the sliding window sync.
- * Setting this to zero will disable sliding sync.
+ * The interval in milliseconds at which to check the number of SMS sent
+ * out without asking for use permit, to limit the un-authorized SMS
+ * usage.
+ * @hide
*/
- public static final String GOOGLE_CALENDAR_SYNC_WINDOW_DAYS =
- "google_calendar_sync_window_days";
+ public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
+ "sms_outgoing_check_interval_ms";
/**
- * How often to update the calendar sync window.
- * The window will be advanced every n days.
+ * The number of outgoing SMS sent without asking for user permit
+ * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
+ * @hide
*/
- public static final String GOOGLE_CALENDAR_SYNC_WINDOW_UPDATE_DAYS =
- "google_calendar_sync_window_update_days";
+ public static final String SMS_OUTGOING_CHECK_MAX_COUNT =
+ "sms_outgoing_check_max_count";
/**
* The number of promoted sources in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources";
/**
* The maximum number of suggestions returned by GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display";
/**
* The number of suggestions GlobalSearch will ask each non-web search source for.
+ * @hide
*/
public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source";
/**
* The number of suggestions the GlobalSearch will ask the web search source for.
+ * @hide
*/
public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT =
"search_web_results_override_limit";
/**
* The number of milliseconds that GlobalSearch will wait for suggestions from
* promoted sources before continuing with all other sources.
+ * @hide
*/
public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS =
"search_promoted_source_deadline_millis";
/**
* The number of milliseconds before GlobalSearch aborts search suggesiton queries.
+ * @hide
*/
public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis";
/**
* The maximum number of milliseconds that GlobalSearch shows the previous results
* after receiving a new query.
+ * @hide
*/
public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis";
/**
* The maximum age of log data used for shortcuts in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis";
/**
* The maximum age of log data used for source ranking in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS =
"search_max_source_event_age_millis";
/**
* The minimum number of impressions needed to rank a source in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING =
"search_min_impressions_for_source_ranking";
/**
* The minimum number of clicks needed to rank a source in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING =
"search_min_clicks_for_source_ranking";
/**
* The maximum number of shortcuts shown by GlobalSearch.
+ * @hide
*/
public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned";
/**
* The size of the core thread pool for suggestion queries in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE =
"search_query_thread_core_pool_size";
/**
* The maximum size of the thread pool for suggestion queries in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE =
"search_query_thread_max_pool_size";
/**
* The size of the core thread pool for shortcut refreshing in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE =
"search_shortcut_refresh_core_pool_size";
/**
* The maximum size of the thread pool for shortcut refreshing in GlobalSearch.
+ * @hide
*/
public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE =
"search_shortcut_refresh_max_pool_size";
/**
* The maximun time that excess threads in the GlobalSeach thread pools will
* wait before terminating.
+ * @hide
*/
public static final String SEARCH_THREAD_KEEPALIVE_SECONDS =
"search_thread_keepalive_seconds";
/**
* The maximum number of concurrent suggestion queries to each source.
+ * @hide
*/
public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
"search_per_source_concurrent_query_limit";
/**
- * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
- * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
- * will never display the "Report" button.
- * Type: int ( 0 = disallow, 1 = allow )
+ * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true)
+ * @hide
*/
- public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
+ public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd";
/**
- * Maximum size of /proc/last_kmsg content to upload after reboot.
+ * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true)
+ * @hide
*/
- public static final String LAST_KMSG_KB = "last_kmsg_kb";
+ public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart";
/**
- * The length of time in milli-seconds that automatic small adjustments to
- * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true)
+ * @hide
*/
- public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
+ public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt";
/**
- * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
- * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
- * exceeded.
+ * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true)
+ * @hide
*/
- public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
+ public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
/**
- * @deprecated
+ * If nonzero, ANRs in invisible background processes bring up a dialog.
+ * Otherwise, the process will be silently killed.
* @hide
*/
- @Deprecated // Obviated by NameValueCache: just fetch the value directly.
- public static class QueryMap extends ContentQueryMap {
+ public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
- public QueryMap(ContentResolver contentResolver, Cursor cursor, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- super(cursor, NAME, keepUpdated, handlerForUpdateNotifications);
- }
+ /**
+ * The {@link ComponentName} string of the service to be used as the voice recognition
+ * service.
+ *
+ * @hide
+ */
+ public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
- public QueryMap(ContentResolver contentResolver, boolean keepUpdated,
- Handler handlerForUpdateNotifications) {
- this(contentResolver,
- contentResolver.query(CONTENT_URI, null, null, null, null),
- keepUpdated, handlerForUpdateNotifications);
- }
+ /**
+ * What happens when the user presses the Power button while in-call
+ * and the screen is on.<br/>
+ * <b>Values:</b><br/>
+ * 1 - The Power button turns off the screen and locks the device. (Default behavior)<br/>
+ * 2 - The Power button hangs up the current call.<br/>
+ *
+ * @hide
+ */
+ public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
+
+ /**
+ * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
+ * @hide
+ */
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF = 0x1;
+
+ /**
+ * INCALL_POWER_BUTTON_BEHAVIOR value for "hang up".
+ * @hide
+ */
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_HANGUP = 0x2;
+
+ /**
+ * INCALL_POWER_BUTTON_BEHAVIOR default value.
+ * @hide
+ */
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT =
+ INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
+
+ /**
+ * The current night mode that has been selected by the user. Owned
+ * and controlled by UiModeManagerService. Constants are as per
+ * UiModeManager.
+ * @hide
+ */
+ public static final String UI_NIGHT_MODE = "ui_night_mode";
+
+ /**
+ * Let user pick default install location.
+ * @hide
+ */
+ public static final String SET_INSTALL_LOCATION = "set_install_location";
+
+ /**
+ * Default install location value.
+ * 0 = auto, let system decide
+ * 1 = internal
+ * 2 = sdcard
+ * @hide
+ */
+ public static final String DEFAULT_INSTALL_LOCATION = "default_install_location";
- public String getString(String name) {
- ContentValues cv = getValues(name);
- if (cv == null) return null;
- return cv.getAsString(VALUE);
+ /**
+ * The bandwidth throttle polling freqency in seconds
+ * @hide
+ */
+ public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec";
+
+ /**
+ * The bandwidth throttle threshold (long)
+ * @hide
+ */
+ public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes";
+
+ /**
+ * The bandwidth throttle value (kbps)
+ * @hide
+ */
+ public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps";
+
+ /**
+ * The bandwidth throttle reset calendar day (1-28)
+ * @hide
+ */
+ public static final String THROTTLE_RESET_DAY = "throttle_reset_day";
+
+ /**
+ * The throttling notifications we should send
+ * @hide
+ */
+ public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type";
+
+ /**
+ * Help URI for data throttling policy
+ * @hide
+ */
+ public static final String THROTTLE_HELP_URI = "throttle_help_uri";
+
+
+ /**
+ * @hide
+ */
+ public static final String[] SETTINGS_TO_BACKUP = {
+ ADB_ENABLED,
+ ALLOW_MOCK_LOCATION,
+ PARENTAL_CONTROL_ENABLED,
+ PARENTAL_CONTROL_REDIRECT_URL,
+ USB_MASS_STORAGE_ENABLED,
+ ACCESSIBILITY_ENABLED,
+ BACKUP_AUTO_RESTORE,
+ ENABLED_ACCESSIBILITY_SERVICES,
+ TTS_USE_DEFAULTS,
+ TTS_DEFAULT_RATE,
+ TTS_DEFAULT_PITCH,
+ TTS_DEFAULT_SYNTH,
+ TTS_DEFAULT_LANG,
+ TTS_DEFAULT_COUNTRY,
+ TTS_ENABLED_PLUGINS,
+ WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ WIFI_NUM_ALLOWED_CHANNELS,
+ WIFI_NUM_OPEN_NETWORKS_KEPT,
+ MOUNT_PLAY_NOTIFICATION_SND,
+ MOUNT_UMS_AUTOSTART,
+ MOUNT_UMS_PROMPT,
+ MOUNT_UMS_NOTIFY_ENABLED,
+ UI_NIGHT_MODE
+ };
+
+ /**
+ * Helper method for determining if a location provider is enabled.
+ * @param cr the content resolver to use
+ * @param provider the location provider to query
+ * @return true if the provider is enabled
+ */
+ public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
+ String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
+ if (allowedProviders != null) {
+ return (allowedProviders.equals(provider) ||
+ allowedProviders.contains("," + provider + ",") ||
+ allowedProviders.startsWith(provider + ",") ||
+ allowedProviders.endsWith("," + provider));
}
+ return false;
}
+ /**
+ * Thread-safe method for enabling or disabling a single location provider.
+ * @param cr the content resolver to use
+ * @param provider the location provider to enable or disable
+ * @param enabled true if the provider should be enabled
+ */
+ public static final void setLocationProviderEnabled(ContentResolver cr,
+ String provider, boolean enabled) {
+ // to ensure thread safety, we write the provider name with a '+' or '-'
+ // and let the SettingsProvider handle it rather than reading and modifying
+ // the list of enabled providers.
+ if (enabled) {
+ provider = "+" + provider;
+ } else {
+ provider = "-" + provider;
+ }
+ putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
+ }
}
/**
diff --git a/core/java/android/provider/SubscribedFeeds.java b/core/java/android/provider/SubscribedFeeds.java
deleted file mode 100644
index 8e9f402..0000000
--- a/core/java/android/provider/SubscribedFeeds.java
+++ /dev/null
@@ -1,209 +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.provider;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.accounts.Account;
-
-/**
- * The SubscribedFeeds provider stores all information about subscribed feeds.
- *
- * @hide
- */
-public class SubscribedFeeds {
- private SubscribedFeeds() {}
-
- /**
- * Columns from the Feed table that other tables join into themselves.
- */
- public interface FeedColumns {
- /**
- * The feed url.
- * <P>Type: TEXT</P>
- */
- public static final String FEED = "feed";
-
- /**
- * The authority that cares about the feed.
- * <P>Type: TEXT</P>
- */
- public static final String AUTHORITY = "authority";
-
- /**
- * The gaia service this feed is for (used for authentication).
- * <P>Type: TEXT</P>
- */
- public static final String SERVICE = "service";
- }
-
- /**
- * Provides constants to access the Feeds table and some utility methods
- * to ease using the Feeds content provider.
- */
- public static final class Feeds implements BaseColumns, SyncConstValue,
- FeedColumns {
- private Feeds() {}
-
- public static Cursor query(ContentResolver cr, String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- public static Cursor query(ContentResolver cr, String[] projection,
- String where, String[] whereArgs, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- whereArgs, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://subscribedfeeds/feeds");
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri DELETED_CONTENT_URI =
- Uri.parse("content://subscribedfeeds/deleted_feeds");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * subscribed feeds.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/subscribedfeeds";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * subscribed feed.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/subscribedfeed";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
- }
-
- /**
- * A convenience method to add a feed to the SubscribedFeeds
- * content provider. The user specifies the values of the FEED,
- * _SYNC_ACCOUNT, AUTHORITY. SERVICE, and ROUTING_INFO.
- * @param resolver used to access the underlying content provider
- * @param feed corresponds to the FEED column
- * @param account corresponds to the _SYNC_ACCOUNT column
- * @param authority corresponds to the AUTHORITY column
- * @param service corresponds to the SERVICE column
- * @return the Uri of the feed that was added
- */
- public static Uri addFeed(ContentResolver resolver,
- String feed, Account account,
- String authority, String service) {
- ContentValues values = new ContentValues();
- values.put(SubscribedFeeds.Feeds.FEED, feed);
- values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, account.name);
- values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE, account.type);
- values.put(SubscribedFeeds.Feeds.AUTHORITY, authority);
- values.put(SubscribedFeeds.Feeds.SERVICE, service);
- return resolver.insert(SubscribedFeeds.Feeds.CONTENT_URI, values);
- }
-
- public static int deleteFeed(ContentResolver resolver,
- String feed, Account account, String authority) {
- StringBuilder where = new StringBuilder();
- where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.FEED + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
- return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
- where.toString(), new String[] {account.name, account.type, feed, authority});
- }
-
- public static int deleteFeeds(ContentResolver resolver,
- Account account, String authority) {
- StringBuilder where = new StringBuilder();
- where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
- where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
- return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
- where.toString(), new String[] {account.name, account.type, authority});
- }
-
- /**
- * Columns from the Accounts table.
- */
- public interface AccountColumns {
- /**
- * The account.
- * <P>Type: TEXT</P>
- */
- public static final String _SYNC_ACCOUNT = SyncConstValue._SYNC_ACCOUNT;
-
- /**
- * The account type.
- * <P>Type: TEXT</P>
- */
- public static final String _SYNC_ACCOUNT_TYPE = SyncConstValue._SYNC_ACCOUNT_TYPE;
- }
-
- /**
- * Provides constants to access the Accounts table and some utility methods
- * to ease using it.
- */
- public static final class Accounts implements BaseColumns, AccountColumns {
- private Accounts() {}
-
- public static Cursor query(ContentResolver cr, String[] projection) {
- return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
- }
-
- public static Cursor query(ContentResolver cr, String[] projection,
- String where, String orderBy) {
- return cr.query(CONTENT_URI, projection, where,
- null, (orderBy == null) ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * The content:// style URL for this table
- */
- public static final Uri CONTENT_URI =
- Uri.parse("content://subscribedfeeds/accounts");
-
- /**
- * The MIME type of {@link #CONTENT_URI} providing a directory of
- * accounts that have subscribed feeds.
- */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/subscribedfeedaccounts";
-
- /**
- * The MIME type of a {@link #CONTENT_URI} subdirectory of a single
- * account in the subscribed feeds.
- */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/subscribedfeedaccount";
-
- /**
- * The default sort order for this table
- */
- public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
- }
-}
diff --git a/core/java/android/provider/SyncConstValue.java b/core/java/android/provider/SyncConstValue.java
index 30966eb..2fcf315 100644
--- a/core/java/android/provider/SyncConstValue.java
+++ b/core/java/android/provider/SyncConstValue.java
@@ -18,6 +18,7 @@ package android.provider;
/**
* Columns for tables that are synced to a server.
+ * @deprecated
* @hide
*/
public interface SyncConstValue
@@ -69,7 +70,7 @@ public interface SyncConstValue
* <P>Type: INTEGER (long)</P>
*/
public static final String _SYNC_DIRTY = "_sync_dirty";
-
+
/**
* Used to indicate that this account is not synced
*/
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d8c5a53..bf9e854 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -16,8 +16,6 @@
package android.provider;
-import com.google.android.mms.util.SqliteWrapper;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.ContentResolver;
@@ -25,12 +23,14 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
import android.net.Uri;
import android.telephony.SmsMessage;
import android.text.TextUtils;
-import android.text.util.Regex;
import android.util.Config;
import android.util.Log;
+import android.util.Patterns;
+
import java.util.HashSet;
import java.util.Set;
@@ -101,6 +101,12 @@ public final class Telephony {
public static final String READ = "read";
/**
+ * Indicates whether this message has been seen by the user. The "seen" flag will be
+ * used to figure out whether we need to throw up a statusbar notification or not.
+ */
+ public static final String SEEN = "seen";
+
+ /**
* The TP-Status value for the message, or -1 if no status has
* been received
*/
@@ -152,6 +158,18 @@ public final class Telephony {
* <P>Type: INTEGER (boolean)</P>
*/
public static final String LOCKED = "locked";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
+
+ /**
+ * Meta data used externally.
+ * <P>Type: TEXT</P>
+ */
+ public static final String META_DATA = "meta_data";
}
/**
@@ -243,7 +261,7 @@ public final class Telephony {
* @return true if the operation succeeded
*/
public static boolean moveMessageToFolder(Context context,
- Uri uri, int folder) {
+ Uri uri, int folder, int error) {
if (uri == null) {
return false;
}
@@ -266,7 +284,7 @@ public final class Telephony {
return false;
}
- ContentValues values = new ContentValues(2);
+ ContentValues values = new ContentValues(3);
values.put(TYPE, folder);
if (markAsUnread) {
@@ -274,6 +292,7 @@ public final class Telephony {
} else if (markAsRead) {
values.put(READ, Integer.valueOf(1));
}
+ values.put(ERROR_CODE, error);
return 1 == SqliteWrapper.update(context, context.getContentResolver(),
uri, values, null, null);
@@ -545,7 +564,8 @@ public final class Telephony {
* <li><em>transactionId (Integer)</em> - The WAP transaction
* ID</li>
* <li><em>pduType (Integer)</em> - The WAP PDU type</li>
- * <li><em>data</em> - The data payload of the message</li>
+ * <li><em>header (byte[])</em> - The header of the message</li>
+ * <li><em>data (byte[])</em> - The data payload of the message</li>
* </ul>
*
* <p>If a BroadcastReceiver encounters an error while processing
@@ -638,6 +658,12 @@ public final class Telephony {
public static final String READ = "read";
/**
+ * Indicates whether this message has been seen by the user. The "seen" flag will be
+ * used to figure out whether we need to throw up a statusbar notification or not.
+ */
+ public static final String SEEN = "seen";
+
+ /**
* The Message-ID of the message.
* <P>Type: TEXT</P>
*/
@@ -1044,6 +1070,12 @@ public final class Telephony {
* <P>Type: INTEGER (boolean)</P>
*/
public static final String LOCKED = "locked";
+
+ /**
+ * Meta data used externally.
+ * <P>Type: TEXT</P>
+ */
+ public static final String META_DATA = "meta_data";
}
/**
@@ -1089,6 +1121,7 @@ public final class Telephony {
* <P>Type: INTEGER</P>
*/
public static final String READ = "read";
+
/**
* The snippet of the latest message in the thread.
* <P>Type: TEXT</P>
@@ -1283,7 +1316,7 @@ public final class Telephony {
}
String s = extractAddrSpec(address);
- Matcher match = Regex.EMAIL_ADDRESS_PATTERN.matcher(s);
+ Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
return match.matches();
}
@@ -1298,7 +1331,7 @@ public final class Telephony {
return false;
}
- Matcher match = Regex.PHONE_PATTERN.matcher(number);
+ Matcher match = Patterns.PHONE.matcher(number);
return match.matches();
}
@@ -1629,6 +1662,13 @@ public final class Telephony {
*/
public static final String LAST_TRY = "last_try";
}
+
+ public static final class WordsTable {
+ public static final String ID = "_id";
+ public static final String SOURCE_ROW_ID = "source_id";
+ public static final String TABLE_ID = "table_to_use";
+ public static final String INDEXED_TEXT = "index_text";
+ }
}
public static final class Carriers implements BaseColumns {
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 1742e72..893db2e 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -121,10 +121,44 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
}
}
+ } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ if (streamType == AudioManager.STREAM_MUSIC) {
+ BluetoothDevice sinks[] = getConnectedSinks();
+ if (sinks.length != 0 && isPhoneDocked(sinks[0])) {
+ String address = sinks[0].getAddress();
+ int newVolLevel =
+ intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+ int oldVolLevel =
+ intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
+ String path = mBluetoothService.getObjectPathFromAddress(address);
+ if (newVolLevel > oldVolLevel) {
+ avrcpVolumeUpNative(path);
+ } else if (newVolLevel < oldVolLevel) {
+ avrcpVolumeDownNative(path);
+ }
+ }
+ }
}
}
};
+
+ private boolean isPhoneDocked(BluetoothDevice device) {
+ // This works only because these broadcast intents are "sticky"
+ Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ if (i != null) {
+ int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (dockDevice != null && device.equals(dockDevice)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
mContext = context;
@@ -145,6 +179,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
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);
mContext.registerReceiver(mReceiver, mIntentFilter);
mAudioDevices = new HashMap<BluetoothDevice, Integer>();
@@ -278,6 +313,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("connectSink(" + device + ")");
+ if (!mBluetoothService.isEnabled()) return false;
+
// ignore if there are any active sinks
if (lookupSinksMatchingStates(new int[] {
BluetoothA2dp.STATE_CONNECTING,
@@ -306,7 +343,11 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return false;
// State is DISCONNECTED
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
+
if (!connectSinkNative(path)) {
+ // Restore previous state
+ handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
}
return true;
@@ -322,7 +363,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return false;
}
- switch (getSinkState(device)) {
+ int state = getSinkState(device);
+ switch (state) {
case BluetoothA2dp.STATE_DISCONNECTED:
return false;
case BluetoothA2dp.STATE_DISCONNECTING:
@@ -330,11 +372,13 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
// State is CONNECTING or CONNECTED or PLAYING
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
if (!disconnectSinkNative(path)) {
+ // Restore previous state
+ handleSinkStateChange(device, mAudioDevices.get(device), state);
return false;
- } else {
- return true;
}
+ return true;
}
public synchronized boolean suspendSink(BluetoothDevice device) {
@@ -443,12 +487,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (state != prevState) {
if (state == BluetoothA2dp.STATE_DISCONNECTED ||
state == BluetoothA2dp.STATE_DISCONNECTING) {
- if (prevState == BluetoothA2dp.STATE_CONNECTED ||
- prevState == BluetoothA2dp.STATE_PLAYING) {
- // disconnecting or disconnected
- Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
- mContext.sendBroadcast(intent);
- }
mSinkCount--;
} else if (state == BluetoothA2dp.STATE_CONNECTED) {
mSinkCount ++;
@@ -510,6 +548,19 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return result;
}
+ private void onConnectSinkResult(String deviceObjectPath, boolean result) {
+ // If the call was a success, ignore we will update the state
+ // when we a Sink Property Change
+ if (!result) {
+ if (deviceObjectPath != null) {
+ String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ int state = getSinkState(device);
+ handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
+ }
+ }
+ }
+
@Override
protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mAudioDevices.isEmpty()) return;
@@ -531,4 +582,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private synchronized native boolean suspendSinkNative(String path);
private synchronized native boolean resumeSinkNative(String path);
private synchronized native Object []getSinkPropertiesNative(String path);
+ private synchronized native boolean avrcpVolumeUpNative(String path);
+ private synchronized native boolean avrcpVolumeDownNative(String path);
}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index b28cf43..c0e4600 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -274,9 +274,11 @@ class BluetoothEventLoop {
private void onDeviceRemoved(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address != null)
+ if (address != null) {
mBluetoothService.getBondState().setBondState(address.toUpperCase(),
BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
+ mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
+ }
}
/*package*/ void onPropertyChanged(String[] propValues) {
@@ -372,6 +374,11 @@ class BluetoothEventLoop {
Intent intent = null;
if (propValues[1].equals("true")) {
intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
+ // Set the link timeout to 8000 slots (5 sec timeout)
+ // for bluetooth docks.
+ if (mBluetoothService.isBluetoothDock(address)) {
+ mBluetoothService.setLinkTimeout(address, 8000);
+ }
} else {
intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 018f7d7..c0affd3 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -52,12 +52,17 @@ import android.util.Log;
import com.android.internal.app.IBatteryStats;
import java.io.BufferedInputStream;
+import java.io.BufferedReader;
import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
@@ -65,15 +70,15 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import java.util.Random;
public class BluetoothService extends IBluetooth.Stub {
private static final String TAG = "BluetoothService";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private int mNativeData;
private BluetoothEventLoop mEventLoop;
private boolean mIsAirplaneSensitive;
+ private boolean mIsAirplaneToggleable;
private int mBluetoothState;
private boolean mRestart = false; // need to call enable() after disable()
private boolean mIsDiscovering;
@@ -135,6 +140,14 @@ public class BluetoothService extends IBluetooth.Stub {
}
return false;
}
+
+ @Override
+ public int hashCode() {
+ int hash = 1;
+ hash = hash * 31 + (address == null ? 0 : address.hashCode());
+ hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode());
+ return hash;
+ }
}
static {
@@ -370,7 +383,7 @@ public class BluetoothService extends IBluetooth.Stub {
"Need BLUETOOTH_ADMIN permission");
// Airplane mode can prevent Bluetooth radio from being turned on.
- if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
return false;
}
if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
@@ -519,6 +532,7 @@ public class BluetoothService extends IBluetooth.Stub {
persistBluetoothOnSetting(true);
}
mIsDiscovering = false;
+ mBondState.readAutoPairingData();
mBondState.loadBondState();
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
@@ -545,7 +559,7 @@ public class BluetoothService extends IBluetooth.Stub {
mEventLoop.onPropertyChanged(propVal);
}
- if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
disable(false);
}
@@ -571,34 +585,17 @@ public class BluetoothService extends IBluetooth.Stub {
public class BondState {
private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
- private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>();
- // List of all the vendor_id prefix of Bluetooth addresses for
- // which auto pairing is not attempted.
- // The following companies are included in the list below:
- // ALPS (lexus), Murata (Prius 2007, Nokia 616), TEMIC SDS (Porsche, Audi),
- // Parrot, Zhongshan General K-mate Electronics, Great Well
- // Electronics, Flaircomm Electronics, Jatty Electronics, Delphi,
- // Clarion, Novero, Denso (Lexus, Toyota), Johnson Controls (Acura),
- // Continental Automotive, Harman/Becker, Panasonic/Kyushu Ten,
- // BMW (Motorola PCS)
- private final ArrayList<String> mAutoPairingAddressBlacklist =
- new ArrayList<String>(Arrays.asList(
- "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", "00:21:4F",
- "00:23:06", "00:24:33", "00:A0:79", "00:0E:6D", "00:13:E0", "00:21:E8",
- "00:60:57", "00:0E:9F", "00:12:1C", "00:18:91", "00:18:96", "00:13:04",
- "00:16:FD", "00:22:A0", "00:0B:4C", "00:60:6F", "00:23:3D", "00:C0:59",
- "00:0A:30", "00:1E:AE", "00:1C:D7", "00:80:F0", "00:12:8A"
- ));
-
- // List of names of Bluetooth devices for which auto pairing should be
- // disabled.
- private final ArrayList<String> mAutoPairingExactNameBlacklist =
- new ArrayList<String>(Arrays.asList(
- "Motorola IHF1000", "i.TechBlueBAND", "X5 Stereo v1.3"));
-
- private final ArrayList<String> mAutoPairingPartialNameBlacklist =
- new ArrayList<String>(Arrays.asList(
- "BMW", "Audi"));
+
+ private static final String AUTO_PAIRING_BLACKLIST =
+ "/etc/bluetooth/auto_pairing.conf";
+ private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
+ "/data/misc/bluetooth/dynamic_auto_pairing.conf";
+ private ArrayList<String> mAutoPairingAddressBlacklist;
+ private ArrayList<String> mAutoPairingExactNameBlacklist;
+ private ArrayList<String> mAutoPairingPartialNameBlacklist;
+ // Addresses added to blacklist dynamically based on usage.
+ private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
+
// If this is an outgoing connection, store the address.
// There can be only 1 pending outgoing connection at a time,
@@ -673,18 +670,29 @@ public class BluetoothService extends IBluetooth.Stub {
}
public boolean isAutoPairingBlacklisted(String address) {
- for (String blacklistAddress : mAutoPairingAddressBlacklist) {
- if (address.startsWith(blacklistAddress)) return true;
+ if (mAutoPairingAddressBlacklist != null) {
+ for (String blacklistAddress : mAutoPairingAddressBlacklist) {
+ if (address.startsWith(blacklistAddress)) return true;
+ }
}
+ if (mAutoPairingDynamicAddressBlacklist != null) {
+ for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
+ if (address.equals(blacklistAddress)) return true;
+ }
+ }
String name = getRemoteName(address);
if (name != null) {
- for (String blacklistName : mAutoPairingExactNameBlacklist) {
- if (name.equals(blacklistName)) return true;
+ if (mAutoPairingExactNameBlacklist != null) {
+ for (String blacklistName : mAutoPairingExactNameBlacklist) {
+ if (name.equals(blacklistName)) return true;
+ }
}
- for (String blacklistName : mAutoPairingPartialNameBlacklist) {
- if (name.startsWith(blacklistName)) return true;
+ if (mAutoPairingPartialNameBlacklist != null) {
+ for (String blacklistName : mAutoPairingPartialNameBlacklist) {
+ if (name.startsWith(blacklistName)) return true;
+ }
}
}
return false;
@@ -709,9 +717,12 @@ public class BluetoothService extends IBluetooth.Stub {
}
public synchronized void addAutoPairingFailure(String address) {
- if (!mAutoPairingFailures.contains(address)) {
- mAutoPairingFailures.add(address);
+ if (mAutoPairingDynamicAddressBlacklist == null) {
+ mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
}
+
+ updateAutoPairingData(address);
+ mAutoPairingDynamicAddressBlacklist.add(address);
}
public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
@@ -723,7 +734,9 @@ public class BluetoothService extends IBluetooth.Stub {
}
public synchronized boolean hasAutoPairingFailed(String address) {
- return mAutoPairingFailures.contains(address);
+ if (mAutoPairingDynamicAddressBlacklist == null) return false;
+
+ return mAutoPairingDynamicAddressBlacklist.contains(address);
}
public synchronized int getAttempt(String address) {
@@ -745,6 +758,108 @@ public class BluetoothService extends IBluetooth.Stub {
mPinAttempt.put(address, new Integer(newAttempt));
}
+ private void copyAutoPairingData() {
+ File file = null;
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+ if (file.exists()) return;
+
+ in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
+ out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ } catch (FileNotFoundException e) {
+ log("FileNotFoundException: in copyAutoPairingData");
+ } catch (IOException e) {
+ log("IOException: in copyAutoPairingData");
+ } finally {
+ try {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ } catch (IOException e) {}
+ }
+ }
+
+ public void readAutoPairingData() {
+ if (mAutoPairingAddressBlacklist != null) return;
+ copyAutoPairingData();
+ FileInputStream fstream = null;
+ try {
+ fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
+ DataInputStream in = new DataInputStream(fstream);
+ BufferedReader file = new BufferedReader(new InputStreamReader(in));
+ String line;
+ while((line = file.readLine()) != null) {
+ line = line.trim();
+ if (line.length() == 0 || line.startsWith("//")) continue;
+ String[] value = line.split("=");
+ if (value != null && value.length == 2) {
+ String[] val = value[1].split(",");
+ if (value[0].equalsIgnoreCase("AddressBlacklist")) {
+ mAutoPairingAddressBlacklist =
+ new ArrayList<String>(Arrays.asList(val));
+ } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
+ mAutoPairingExactNameBlacklist =
+ new ArrayList<String>(Arrays.asList(val));
+ } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
+ mAutoPairingPartialNameBlacklist =
+ new ArrayList<String>(Arrays.asList(val));
+ } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
+ mAutoPairingDynamicAddressBlacklist =
+ new ArrayList<String>(Arrays.asList(val));
+ } else {
+ Log.e(TAG, "Error parsing Auto pairing blacklist file");
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ log("FileNotFoundException: readAutoPairingData" + e.toString());
+ } catch (IOException e) {
+ log("IOException: readAutoPairingData" + e.toString());
+ } finally {
+ if (fstream != null) {
+ try {
+ fstream.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // This function adds a bluetooth address to the auto pairing blacklis
+ // file. These addresses are added to DynamicAddressBlacklistSection
+ private void updateAutoPairingData(String address) {
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
+ StringBuilder str = new StringBuilder();
+ if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
+ str.append("DynamicAddressBlacklist=");
+ }
+ str.append(address);
+ str.append(",");
+ out.write(str.toString());
+ } catch (FileNotFoundException e) {
+ log("FileNotFoundException: updateAutoPairingData" + e.toString());
+ } catch (IOException e) {
+ log("IOException: updateAutoPairingData" + e.toString());
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ }
}
private static String toBondStateString(int bondState) {
@@ -1291,7 +1406,9 @@ public class BluetoothService extends IBluetooth.Stub {
}
boolean ret;
- if (getBondState(address) == BluetoothDevice.BOND_BONDED) {
+ // Just do the SDP if the device is already created and UUIDs are not
+ // NULL, else create the device and then do SDP.
+ if (isRemoteDeviceInCache(address) && getRemoteUuids(address) != null) {
String path = getObjectPathFromAddress(address);
if (path == null) return false;
@@ -1597,10 +1714,17 @@ public class BluetoothService extends IBluetooth.Stub {
};
private void registerForAirplaneMode(IntentFilter filter) {
- String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
+ final ContentResolver resolver = mContext.getContentResolver();
+ final String airplaneModeRadios = Settings.System.getString(resolver,
Settings.System.AIRPLANE_MODE_RADIOS);
- mIsAirplaneSensitive = airplaneModeRadios == null
- ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+ final String toggleableRadios = Settings.System.getString(resolver,
+ Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+
+ mIsAirplaneSensitive = airplaneModeRadios == null ? true :
+ airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
+ mIsAirplaneToggleable = toggleableRadios == null ? false :
+ toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
+
if (mIsAirplaneSensitive) {
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
}
@@ -1661,6 +1785,7 @@ public class BluetoothService extends IBluetooth.Stub {
}
pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
+ pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
pw.println("Local address = " + getAddress());
pw.println("Local name = " + getName());
@@ -1787,6 +1912,13 @@ public class BluetoothService extends IBluetooth.Stub {
return path;
}
+ /*package */ void setLinkTimeout(String address, int num_slots) {
+ String path = getObjectPathFromAddress(address);
+ boolean result = setLinkTimeoutNative(path, num_slots);
+
+ if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
@@ -1830,4 +1962,5 @@ public class BluetoothService extends IBluetooth.Stub {
private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
short channel);
private native boolean removeServiceRecordNative(int handle);
+ private native boolean setLinkTimeoutNative(String path, int num_slots);
}
diff --git a/core/java/android/server/data/BuildData.java b/core/java/android/server/data/BuildData.java
deleted file mode 100644
index 53ffa3f..0000000
--- a/core/java/android/server/data/BuildData.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.data;
-
-import android.os.Build;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-import static com.android.internal.util.Objects.nonNull;
-
-/**
- * Build data transfer object. Keep in sync. with the server side version.
- */
-public class BuildData {
-
- /** The version of the data returned by write() and understood by the constructor. */
- private static final int VERSION = 0;
-
- private final String fingerprint;
- private final String incrementalVersion;
- private final long time; // in *seconds* since the epoch (not msec!)
-
- public BuildData() {
- this.fingerprint = "android:" + Build.FINGERPRINT;
- this.incrementalVersion = Build.VERSION.INCREMENTAL;
- this.time = Build.TIME / 1000; // msec -> sec
- }
-
- public BuildData(String fingerprint, String incrementalVersion, long time) {
- this.fingerprint = nonNull(fingerprint);
- this.incrementalVersion = incrementalVersion;
- this.time = time;
- }
-
- /*package*/ BuildData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != VERSION) {
- throw new IOException("Expected " + VERSION + ". Got: " + dataVersion);
- }
-
- this.fingerprint = in.readUTF();
- this.incrementalVersion = Long.toString(in.readLong());
- this.time = in.readLong();
- }
-
- /*package*/ void write(DataOutput out) throws IOException {
- out.writeInt(VERSION);
- out.writeUTF(fingerprint);
-
- // TODO: change the format/version to expect a string for this field.
- // Version 0, still used by the server side, expects a long.
- long changelist;
- try {
- changelist = Long.parseLong(incrementalVersion);
- } catch (NumberFormatException ex) {
- changelist = -1;
- }
- out.writeLong(changelist);
- out.writeLong(time);
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public String getIncrementalVersion() {
- return incrementalVersion;
- }
-
- public long getTime() {
- return time;
- }
-}
diff --git a/core/java/android/server/data/CrashData.java b/core/java/android/server/data/CrashData.java
deleted file mode 100644
index d652bb3..0000000
--- a/core/java/android/server/data/CrashData.java
+++ /dev/null
@@ -1,145 +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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-import static com.android.internal.util.Objects.nonNull;
-
-/**
- * Crash data transfer object. Keep in sync. with the server side version.
- */
-public class CrashData {
-
- final String id;
- final String activity;
- final long time;
- final BuildData buildData;
- final ThrowableData throwableData;
- final byte[] state;
-
- public CrashData(String id, String activity, BuildData buildData,
- ThrowableData throwableData) {
- this.id = nonNull(id);
- this.activity = nonNull(activity);
- this.buildData = nonNull(buildData);
- this.throwableData = nonNull(throwableData);
- this.time = System.currentTimeMillis();
- this.state = null;
- }
-
- public CrashData(String id, String activity, BuildData buildData,
- ThrowableData throwableData, byte[] state) {
- this.id = nonNull(id);
- this.activity = nonNull(activity);
- this.buildData = nonNull(buildData);
- this.throwableData = nonNull(throwableData);
- this.time = System.currentTimeMillis();
- this.state = state;
- }
-
- public CrashData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0 && dataVersion != 1) {
- throw new IOException("Expected 0 or 1. Got: " + dataVersion);
- }
-
- this.id = in.readUTF();
- this.activity = in.readUTF();
- this.time = in.readLong();
- this.buildData = new BuildData(in);
- this.throwableData = new ThrowableData(in);
- if (dataVersion == 1) {
- int len = in.readInt();
- if (len == 0) {
- this.state = null;
- } else {
- this.state = new byte[len];
- in.readFully(this.state, 0, len);
- }
- } else {
- this.state = null;
- }
- }
-
- public CrashData(String tag, Throwable throwable) {
- id = "";
- activity = tag;
- buildData = new BuildData();
- throwableData = new ThrowableData(throwable);
- time = System.currentTimeMillis();
- state = null;
- }
-
- public void write(DataOutput out) throws IOException {
- // version
- if (this.state == null) {
- out.writeInt(0);
- } else {
- out.writeInt(1);
- }
-
- out.writeUTF(this.id);
- out.writeUTF(this.activity);
- out.writeLong(this.time);
- buildData.write(out);
- throwableData.write(out);
- if (this.state != null) {
- out.writeInt(this.state.length);
- out.write(this.state, 0, this.state.length);
- }
- }
-
- public BuildData getBuildData() {
- return buildData;
- }
-
- public ThrowableData getThrowableData() {
- return throwableData;
- }
-
- public String getId() {
- return id;
- }
-
- public String getActivity() {
- return activity;
- }
-
- public long getTime() {
- return time;
- }
-
- public byte[] getState() {
- return state;
- }
-
- /**
- * Return a brief description of this CrashData record. The details of the
- * representation are subject to change.
- *
- * @return Returns a String representing the contents of the object.
- */
- @Override
- public String toString() {
- return "[CrashData: id=" + id + " activity=" + activity + " time=" + time +
- " buildData=" + buildData.toString() +
- " throwableData=" + throwableData.toString() + "]";
- }
-}
diff --git a/core/java/android/server/data/StackTraceElementData.java b/core/java/android/server/data/StackTraceElementData.java
deleted file mode 100644
index 07185a0..0000000
--- a/core/java/android/server/data/StackTraceElementData.java
+++ /dev/null
@@ -1,80 +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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Stack trace element data transfer object. Keep in sync. with the server side
- * version.
- */
-public class StackTraceElementData {
-
- final String className;
- final String fileName;
- final String methodName;
- final int lineNumber;
-
- public StackTraceElementData(StackTraceElement element) {
- this.className = element.getClassName();
-
- String fileName = element.getFileName();
- this.fileName = fileName == null ? "[unknown source]" : fileName;
-
- this.methodName = element.getMethodName();
- this.lineNumber = element.getLineNumber();
- }
-
- public StackTraceElementData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0) {
- throw new IOException("Expected 0. Got: " + dataVersion);
- }
-
- this.className = in.readUTF();
- this.fileName = in.readUTF();
- this.methodName = in.readUTF();
- this.lineNumber = in.readInt();
- }
-
- void write(DataOutput out) throws IOException {
- out.writeInt(0); // version
-
- out.writeUTF(className);
- out.writeUTF(fileName);
- out.writeUTF(methodName);
- out.writeInt(lineNumber);
- }
-
- public String getClassName() {
- return className;
- }
-
- public String getFileName() {
- return fileName;
- }
-
- public String getMethodName() {
- return methodName;
- }
-
- public int getLineNumber() {
- return lineNumber;
- }
-}
diff --git a/core/java/android/server/data/ThrowableData.java b/core/java/android/server/data/ThrowableData.java
deleted file mode 100644
index e500aca..0000000
--- a/core/java/android/server/data/ThrowableData.java
+++ /dev/null
@@ -1,138 +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.server.data;
-
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-
-/**
- * Throwable data transfer object. Keep in sync. with the server side version.
- */
-public class ThrowableData {
-
- final String message;
- final String type;
- final StackTraceElementData[] stackTrace;
- final ThrowableData cause;
-
- public ThrowableData(Throwable throwable) {
- this.type = throwable.getClass().getName();
- String message = throwable.getMessage();
- this.message = message == null ? "" : message;
-
- StackTraceElement[] elements = throwable.getStackTrace();
- this.stackTrace = new StackTraceElementData[elements.length];
- for (int i = 0; i < elements.length; i++) {
- this.stackTrace[i] = new StackTraceElementData(elements[i]);
- }
-
- Throwable cause = throwable.getCause();
- this.cause = cause == null ? null : new ThrowableData(cause);
- }
-
- public ThrowableData(DataInput in) throws IOException {
- int dataVersion = in.readInt();
- if (dataVersion != 0) {
- throw new IOException("Expected 0. Got: " + dataVersion);
- }
-
- this.message = in.readUTF();
- this.type = in.readUTF();
-
- int count = in.readInt();
- this.stackTrace = new StackTraceElementData[count];
- for (int i = 0; i < count; i++) {
- this.stackTrace[i] = new StackTraceElementData(in);
- }
-
- this.cause = in.readBoolean() ? new ThrowableData(in) : null;
- }
-
- public void write(DataOutput out) throws IOException {
- out.writeInt(0); // version
-
- out.writeUTF(message);
- out.writeUTF(type);
-
- out.writeInt(stackTrace.length);
- for (StackTraceElementData elementData : stackTrace) {
- elementData.write(out);
- }
-
- out.writeBoolean(cause != null);
- if (cause != null) {
- cause.write(out);
- }
- }
-
- public String getMessage() {
- return message;
- }
-
- public String getType() {
- return type;
- }
-
- public StackTraceElementData[] getStackTrace() {
- return stackTrace;
- }
-
- public ThrowableData getCause() {
- return cause;
- }
-
-
- public String toString() {
- return toString(null);
- }
-
- public String toString(String prefix) {
- StringBuilder builder = new StringBuilder();
- append(prefix, builder, this);
- return builder.toString();
- }
-
- private static void append(String prefix, StringBuilder builder,
- ThrowableData throwableData) {
- if (prefix != null) builder.append(prefix);
- builder.append(throwableData.getType())
- .append(": ")
- .append(throwableData.getMessage())
- .append('\n');
- for (StackTraceElementData element : throwableData.getStackTrace()) {
- if (prefix != null ) builder.append(prefix);
- builder.append(" at ")
- .append(element.getClassName())
- .append('.')
- .append(element.getMethodName())
- .append("(")
- .append(element.getFileName())
- .append(':')
- .append(element.getLineNumber())
- .append(")\n");
-
- }
-
- ThrowableData cause = throwableData.getCause();
- if (cause != null) {
- if (prefix != null ) builder.append(prefix);
- builder.append("Caused by: ");
- append(prefix, builder, cause);
- }
- }
-}
diff --git a/core/java/android/server/data/package.html b/core/java/android/server/data/package.html
deleted file mode 100755
index 1c9bf9d..0000000
--- a/core/java/android/server/data/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<html>
-<body>
- {@hide}
-</body>
-</html>
diff --git a/core/java/android/server/search/SearchDialogWrapper.java b/core/java/android/server/search/SearchDialogWrapper.java
deleted file mode 100644
index 9ee64af..0000000
--- a/core/java/android/server/search/SearchDialogWrapper.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.search;
-
-import android.app.ISearchManagerCallback;
-import android.app.SearchDialog;
-import android.app.SearchManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Runs an instance of {@link SearchDialog} on its own thread.
- */
-class SearchDialogWrapper
-implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
-
- private static final String TAG = "SearchManagerService";
- private static final boolean DBG = false;
-
- private static final String SEARCH_UI_THREAD_NAME = "SearchDialog";
- private static final int SEARCH_UI_THREAD_PRIORITY =
- android.os.Process.THREAD_PRIORITY_DEFAULT;
-
- // Takes no arguments
- private static final int MSG_INIT = 0;
- // Takes these arguments:
- // arg1: selectInitialQuery, 0 = false, 1 = true
- // arg2: globalSearch, 0 = false, 1 = true
- // obj: searchManagerCallback
- // data[KEY_INITIAL_QUERY]: initial query
- // data[KEY_LAUNCH_ACTIVITY]: launch activity
- // data[KEY_APP_SEARCH_DATA]: app search data
- // data[KEY_TRIGGER]: 0 = false, 1 = true
- private static final int MSG_START_SEARCH = 1;
- // Takes no arguments
- private static final int MSG_STOP_SEARCH = 2;
- // arg1 is activity id
- private static final int MSG_ACTIVITY_RESUMING = 3;
- // obj is the reason
- private static final int MSG_CLOSING_SYSTEM_DIALOGS = 4;
-
- private static final String KEY_INITIAL_QUERY = "q";
- private static final String KEY_LAUNCH_ACTIVITY = "a";
- private static final String KEY_APP_SEARCH_DATA = "d";
- private static final String KEY_IDENT = "i";
- private static final String KEY_TRIGGER = "t";
-
- // Context used for getting search UI resources
- private final Context mContext;
-
- // Handles messages on the search UI thread.
- private final SearchDialogHandler mSearchUiThread;
-
- // The search UI
- SearchDialog mSearchDialog;
-
- // If the search UI is visible, this is the callback for the client that showed it.
- ISearchManagerCallback mCallback = null;
-
- // Identity of last activity that started search.
- private int mStartedIdent = 0;
-
- // Identity of currently resumed activity.
- private int mResumedIdent = 0;
-
- // True if we have registered our receivers.
- private boolean mReceiverRegistered;
-
- private volatile boolean mVisible = false;
-
- /**
- * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will
- * be created some asynchronously on the search UI thread.
- *
- * @param context Context used for getting search UI resources.
- */
- public SearchDialogWrapper(Context context) {
- mContext = context;
-
- // Create the search UI thread
- HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY);
- t.start();
- mSearchUiThread = new SearchDialogHandler(t.getLooper());
-
- // Create search UI on the search UI thread
- mSearchUiThread.sendEmptyMessage(MSG_INIT);
- }
-
- public boolean isVisible() {
- return mVisible;
- }
-
- /**
- * Initializes the search UI.
- * Must be called from the search UI thread.
- */
- private void init() {
- mSearchDialog = new SearchDialog(mContext);
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
- }
-
- private void registerBroadcastReceiver() {
- if (!mReceiverRegistered) {
- IntentFilter filter = new IntentFilter(
- Intent.ACTION_CONFIGURATION_CHANGED);
- mContext.registerReceiver(mBroadcastReceiver, filter, null,
- mSearchUiThread);
- mReceiverRegistered = true;
- }
- }
-
- private void unregisterBroadcastReceiver() {
- if (mReceiverRegistered) {
- mContext.unregisterReceiver(mBroadcastReceiver);
- mReceiverRegistered = false;
- }
- }
-
- /**
- * Closes the search dialog when requested by the system (e.g. when a phone call comes in).
- */
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
- if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED);
- performOnConfigurationChanged();
- }
- }
- };
-
- //
- // External API
- //
-
- /**
- * Launches the search UI.
- * Can be called from any thread.
- *
- * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
- */
- public void startSearch(final String initialQuery,
- final boolean selectInitialQuery,
- final ComponentName launchActivity,
- final Bundle appSearchData,
- final boolean globalSearch,
- final ISearchManagerCallback searchManagerCallback,
- int ident,
- boolean trigger) {
- if (DBG) debug("startSearch()");
- Message msg = Message.obtain();
- msg.what = MSG_START_SEARCH;
- msg.arg1 = selectInitialQuery ? 1 : 0;
- msg.arg2 = globalSearch ? 1 : 0;
- msg.obj = searchManagerCallback;
- Bundle msgData = msg.getData();
- msgData.putString(KEY_INITIAL_QUERY, initialQuery);
- msgData.putParcelable(KEY_LAUNCH_ACTIVITY, launchActivity);
- msgData.putBundle(KEY_APP_SEARCH_DATA, appSearchData);
- msgData.putInt(KEY_IDENT, ident);
- msgData.putInt(KEY_TRIGGER, trigger ? 1 : 0);
- mSearchUiThread.sendMessage(msg);
- // be a little more eager in setting this so isVisible will return the correct value if
- // called immediately after startSearch
- mVisible = true;
- }
-
- /**
- * Cancels the search dialog.
- * Can be called from any thread.
- */
- public void stopSearch() {
- if (DBG) debug("stopSearch()");
- mSearchUiThread.sendEmptyMessage(MSG_STOP_SEARCH);
- // be a little more eager in setting this so isVisible will return the correct value if
- // called immediately after stopSearch
- mVisible = false;
- }
-
- /**
- * Updates the currently resumed activity.
- * Can be called from any thread.
- */
- public void activityResuming(int ident) {
- if (DBG) debug("activityResuming(ident=" + ident + ")");
- Message msg = Message.obtain();
- msg.what = MSG_ACTIVITY_RESUMING;
- msg.arg1 = ident;
- mSearchUiThread.sendMessage(msg);
- }
-
- /**
- * Handles closing of system windows/dialogs
- * Can be called from any thread.
- */
- public void closingSystemDialogs(String reason) {
- if (DBG) debug("closingSystemDialogs(reason=" + reason + ")");
- Message msg = Message.obtain();
- msg.what = MSG_CLOSING_SYSTEM_DIALOGS;
- msg.obj = reason;
- mSearchUiThread.sendMessage(msg);
- }
-
- //
- // Implementation methods that run on the search UI thread
- //
-
- private class SearchDialogHandler extends Handler {
-
- public SearchDialogHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_INIT:
- init();
- break;
- case MSG_START_SEARCH:
- handleStartSearchMessage(msg);
- break;
- case MSG_STOP_SEARCH:
- performStopSearch();
- break;
- case MSG_ACTIVITY_RESUMING:
- performActivityResuming(msg.arg1);
- break;
- case MSG_CLOSING_SYSTEM_DIALOGS:
- performClosingSystemDialogs((String)msg.obj);
- break;
- }
- }
-
- private void handleStartSearchMessage(Message msg) {
- Bundle msgData = msg.getData();
- String initialQuery = msgData.getString(KEY_INITIAL_QUERY);
- boolean selectInitialQuery = msg.arg1 != 0;
- ComponentName launchActivity =
- (ComponentName) msgData.getParcelable(KEY_LAUNCH_ACTIVITY);
- Bundle appSearchData = msgData.getBundle(KEY_APP_SEARCH_DATA);
- boolean globalSearch = msg.arg2 != 0;
- ISearchManagerCallback searchManagerCallback = (ISearchManagerCallback) msg.obj;
- int ident = msgData.getInt(KEY_IDENT);
- boolean trigger = msgData.getInt(KEY_TRIGGER) != 0;
- performStartSearch(initialQuery, selectInitialQuery, launchActivity,
- appSearchData, globalSearch, searchManagerCallback, ident, trigger);
- }
-
- }
-
- /**
- * Actually launches the search UI.
- * This must be called on the search UI thread.
- */
- void performStartSearch(String initialQuery,
- boolean selectInitialQuery,
- ComponentName launchActivity,
- Bundle appSearchData,
- boolean globalSearch,
- ISearchManagerCallback searchManagerCallback,
- int ident,
- boolean trigger) {
- if (DBG) debug("performStartSearch()");
-
- registerBroadcastReceiver();
- mCallback = searchManagerCallback;
-
- // clean up any hidden dialog that we were waiting to resume
- if (mStartedIdent != 0) {
- mSearchDialog.dismiss();
- }
-
- mStartedIdent = ident;
- if (DBG) Log.v(TAG, "******************* DIALOG: start");
-
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
- mVisible = true;
- if (trigger) {
- mSearchDialog.launchQuerySearch();
- }
- }
-
- /**
- * Actually cancels the search UI.
- * This must be called on the search UI thread.
- */
- void performStopSearch() {
- if (DBG) debug("performStopSearch()");
- if (DBG) Log.v(TAG, "******************* DIALOG: cancel");
- mSearchDialog.cancel();
- mVisible = false;
- mStartedIdent = 0;
- }
-
- /**
- * Updates the resumed activity
- * This must be called on the search UI thread.
- */
- void performActivityResuming(int ident) {
- if (DBG) debug("performResumingActivity(): mStartedIdent="
- + mStartedIdent + ", resuming: " + ident);
- this.mResumedIdent = ident;
- if (mStartedIdent != 0) {
- if (mStartedIdent == mResumedIdent) {
- // we are resuming into the activity where we previously hid the dialog, bring it
- // back
- if (DBG) Log.v(TAG, "******************* DIALOG: show");
- mSearchDialog.show();
- mVisible = true;
- } else {
- // resuming into some other activity; hide ourselves in case we ever come back
- // so we can show ourselves quickly again
- if (DBG) Log.v(TAG, "******************* DIALOG: hide");
- mSearchDialog.hide();
- mVisible = false;
- }
- }
- }
-
- /**
- * Updates due to system dialogs being closed
- * This must be called on the search UI thread.
- */
- void performClosingSystemDialogs(String reason) {
- if (DBG) debug("performClosingSystemDialogs(): mStartedIdent="
- + mStartedIdent + ", reason: " + reason);
- if (!"search".equals(reason)) {
- if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- performStopSearch();
- }
- }
-
- /**
- * Must be called from the search UI thread.
- */
- void performOnConfigurationChanged() {
- if (DBG) debug("performOnConfigurationChanged()");
- mSearchDialog.onConfigurationChanged();
- }
-
- /**
- * Called by {@link SearchDialog} when it goes away.
- */
- public void onDismiss(DialogInterface dialog) {
- if (DBG) debug("onDismiss()");
- mStartedIdent = 0;
- mVisible = false;
- callOnDismiss();
-
- // we don't need the callback anymore, release it
- mCallback = null;
- unregisterBroadcastReceiver();
- }
-
-
- /**
- * Called by {@link SearchDialog} when the user or activity cancels search.
- * Whenever this method is called, {@link #onDismiss} is always called afterwards.
- */
- public void onCancel(DialogInterface dialog) {
- if (DBG) debug("onCancel()");
- callOnCancel();
- }
-
- private void callOnDismiss() {
- if (mCallback == null) return;
- try {
- // should be safe to do on the search UI thread, since it's a oneway interface
- mCallback.onDismiss();
- } catch (DeadObjectException ex) {
- // The process that hosted the callback has died, do nothing
- } catch (RemoteException ex) {
- Log.e(TAG, "onDismiss() failed: " + ex);
- }
- }
-
- private void callOnCancel() {
- if (mCallback != null) {
- try {
- // should be safe to do on the search UI thread, since it's a oneway interface
- mCallback.onCancel();
- } catch (DeadObjectException ex) {
- // The process that hosted the callback has died, do nothing
- } catch (RemoteException ex) {
- Log.e(TAG, "onCancel() failed: " + ex);
- }
- }
- }
-
- private static void debug(String msg) {
- Thread thread = Thread.currentThread();
- Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
- }
-}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 78ea2e3..3826a01 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -16,19 +16,17 @@
package android.server.search;
-import android.app.ActivityManagerNative;
-import android.app.IActivityWatcher;
+import com.android.internal.content.PackageMonitor;
+
import android.app.ISearchManager;
-import android.app.ISearchManagerCallback;
import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
+import android.os.Process;
import android.util.Log;
import java.util.List;
@@ -41,19 +39,13 @@ public class SearchManagerService extends ISearchManager.Stub {
// general debugging support
private static final String TAG = "SearchManagerService";
- private static final boolean DBG = false;
// Context that the service is running in.
private final Context mContext;
- // This field is initialized in ensureSearchablesCreated(), and then never modified.
- // Only accessed by ensureSearchablesCreated() and getSearchables()
+ // This field is initialized lazily in getSearchables(), and then never modified.
private Searchables mSearchables;
- // This field is initialized in ensureSearchDialogCreated(), and then never modified.
- // Only accessed by ensureSearchDialogCreated() and getSearchDialog()
- private SearchDialogWrapper mSearchDialog;
-
/**
* Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
@@ -62,95 +54,50 @@ public class SearchManagerService extends ISearchManager.Stub {
*/
public SearchManagerService(Context context) {
mContext = context;
- // call initialize() after all pending actions on the main system thread have finished
- new Handler().post(new Runnable() {
- public void run() {
- initialize();
- }
- });
- }
-
- /**
- * Initializes the list of searchable activities and the search UI.
- */
- void initialize() {
- try {
- ActivityManagerNative.getDefault().registerActivityWatcher(
- mActivityWatcher);
- } catch (RemoteException e) {
- }
- }
-
- private synchronized void ensureSearchablesCreated() {
- if (mSearchables != null) return; // already created
-
- mSearchables = new Searchables(mContext);
- mSearchables.buildSearchableList();
-
- IntentFilter packageFilter = new IntentFilter();
- packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageFilter.addDataScheme("package");
- mContext.registerReceiver(mPackageChangedReceiver, packageFilter);
- }
-
- private synchronized void ensureSearchDialogCreated() {
- if (mSearchDialog != null) return;
-
- mSearchDialog = new SearchDialogWrapper(mContext);
+ mContext.registerReceiver(new BootCompletedReceiver(),
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
private synchronized Searchables getSearchables() {
- ensureSearchablesCreated();
+ if (mSearchables == null) {
+ Log.i(TAG, "Building list of searchable activities");
+ new MyPackageMonitor().register(mContext, true);
+ mSearchables = new Searchables(mContext);
+ mSearchables.buildSearchableList();
+ }
return mSearchables;
}
- private synchronized SearchDialogWrapper getSearchDialog() {
- ensureSearchDialogCreated();
- return mSearchDialog;
- }
-
/**
- * Refreshes the "searchables" list when packages are added/removed.
+ * Creates the initial searchables list after boot.
*/
- private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+ private final class BootCompletedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
- Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
- if (DBG) Log.d(TAG, "Got " + action);
- // Dismiss search dialog, since the search context may no longer be valid
- getSearchDialog().stopSearch();
- // Update list of searchable activities
- getSearchables().buildSearchableList();
- broadcastSearchablesChanged();
- }
+ new Thread() {
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ mContext.unregisterReceiver(BootCompletedReceiver.this);
+ getSearchables();
+ }
+ }.start();
}
- };
+ }
- private IActivityWatcher.Stub mActivityWatcher = new IActivityWatcher.Stub() {
- public void activityResuming(int activityId) throws RemoteException {
- if (DBG) Log.i("foo", "********************** resuming: " + activityId);
- if (mSearchDialog == null) return;
- mSearchDialog.activityResuming(activityId);
- }
- public void closingSystemDialogs(String reason) {
- if (DBG) Log.i("foo", "********************** closing dialogs: " + reason);
- if (mSearchDialog == null) return;
- mSearchDialog.closingSystemDialogs(reason);
- }
- };
-
/**
- * Informs all listeners that the list of searchables has been updated.
+ * Refreshes the "searchables" list when packages are added/removed.
*/
- void broadcastSearchablesChanged() {
- mContext.sendBroadcast(
- new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
+ class MyPackageMonitor extends PackageMonitor {
+ @Override
+ public void onSomePackagesChanged() {
+ // Update list of searchable activities
+ getSearchables().buildSearchableList();
+ // Inform all listeners that the list of searchables has been updated.
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ mContext.sendBroadcast(intent);
+ }
}
//
@@ -161,24 +108,15 @@ public class SearchManagerService extends ISearchManager.Stub {
* Returns the SearchableInfo for a given activity.
*
* @param launchActivity The activity from which we're launching this search.
- * @param globalSearch If false, this will only launch the search that has been specifically
- * defined by the application (which is usually defined as a local search). If no default
- * search is defined in the current application or activity, no search will be launched.
- * If true, this will always launch a platform-global (e.g. web-based) search instead.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
- public SearchableInfo getSearchableInfo(final ComponentName launchActivity,
- final boolean globalSearch) {
- if (globalSearch) {
- return getSearchables().getDefaultSearchable();
- } else {
- if (launchActivity == null) {
- Log.e(TAG, "getSearchableInfo(), activity == null");
- return null;
- }
- return getSearchables().getSearchableInfo(launchActivity);
+ public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
}
+ return getSearchables().getSearchableInfo(launchActivity);
}
/**
@@ -189,85 +127,17 @@ public class SearchManagerService extends ISearchManager.Stub {
}
/**
- * Returns a list of the searchable activities that handle web searches.
- * Can be called from any thread.
- */
- public List<SearchableInfo> getSearchablesForWebSearch() {
- return getSearchables().getSearchablesForWebSearchList();
- }
-
- /**
- * Returns the default searchable activity for web searches.
- * Can be called from any thread.
+ * Gets the name of the global search activity.
*/
- public SearchableInfo getDefaultSearchableForWebSearch() {
- return getSearchables().getDefaultSearchableForWebSearch();
+ public ComponentName getGlobalSearchActivity() {
+ return getSearchables().getGlobalSearchActivity();
}
/**
- * Sets the default searchable activity for web searches.
- * Can be called from any thread.
- */
- public void setDefaultWebSearch(final ComponentName component) {
- getSearchables().setDefaultWebSearch(component);
- broadcastSearchablesChanged();
- }
-
- // Search UI API
-
- /**
- * Launches the search UI. Can be called from any thread.
- *
- * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ * Gets the name of the web search activity.
*/
- public void startSearch(String initialQuery,
- boolean selectInitialQuery,
- ComponentName launchActivity,
- Bundle appSearchData,
- boolean globalSearch,
- ISearchManagerCallback searchManagerCallback,
- int ident) {
- getSearchDialog().startSearch(initialQuery,
- selectInitialQuery,
- launchActivity,
- appSearchData,
- globalSearch,
- searchManagerCallback,
- ident,
- false); // don't trigger
- }
-
- /**
- * Launches the search UI and triggers the search, as if the user had clicked on the
- * search button within the dialog.
- *
- * @see SearchManager#triggerSearch(String, android.content.ComponentName, android.os.Bundle)
- */
- public void triggerSearch(String query,
- ComponentName launchActivity,
- Bundle appSearchData,
- ISearchManagerCallback searchManagerCallback,
- int ident) {
- getSearchDialog().startSearch(
- query,
- false,
- launchActivity,
- appSearchData,
- false,
- searchManagerCallback,
- ident,
- true); // triger search after launching
- }
-
- /**
- * Cancels the search dialog. Can be called from any thread.
- */
- public void stopSearch() {
- getSearchDialog().stopSearch();
- }
-
- public boolean isVisible() {
- return mSearchDialog != null && mSearchDialog.isVisible();
+ public ComponentName getWebSearchActivity() {
+ return getSearchables().getWebSearchActivity();
}
}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index c615957..279c17d 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,13 +16,12 @@
package android.server.search;
-import com.android.internal.app.ResolverActivity;
-
+import android.Manifest;
import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -51,9 +50,8 @@ public class Searchables {
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
- private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
- private SearchableInfo mDefaultSearchable = null;
- private SearchableInfo mDefaultSearchableForWebSearch = null;
+ private ComponentName mGlobalSearchActivity = null;
+ private ComponentName mWebSearchActivity = null;
public static String GOOGLE_SEARCH_COMPONENT_NAME =
"com.android.googlesearch/.GoogleSearch";
@@ -130,10 +128,9 @@ public class Searchables {
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
- // An app or activity can declare that we should simply launch
- // "system default search" if search is invoked.
+ // This value is deprecated, return null
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
- return getDefaultSearchable();
+ return null;
}
String pkg = activity.getPackageName();
ComponentName referredActivity;
@@ -163,20 +160,6 @@ public class Searchables {
}
/**
- * Provides the system-default search activity, which you can use
- * whenever getSearchableInfo() returns null;
- *
- * @return Returns the system-default search activity, null if never defined
- */
- public synchronized SearchableInfo getDefaultSearchable() {
- return mDefaultSearchable;
- }
-
- public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
- return searchable == mDefaultSearchable;
- }
-
- /**
* Builds an entire list (suitable for display) of
* activities that are searchable, by iterating the entire set of
* ACTION_SEARCH & ACTION_WEB_SEARCH intents.
@@ -204,8 +187,6 @@ public class Searchables {
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesForWebSearchList
- = new ArrayList<SearchableInfo>();
final PackageManager pm = mContext.getPackageManager();
@@ -243,127 +224,71 @@ public class Searchables {
}
}
- if (webSearchInfoList != null) {
- for (int i = 0; i < webSearchInfoList.size(); ++i) {
- ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
- ComponentName component = new ComponentName(ai.packageName, ai.name);
- SearchableInfo searchable = newSearchablesMap.get(component);
- if (searchable == null) {
- Log.w(LOG_TAG, "did not find component in searchables: " + component);
- } else {
- newSearchablesForWebSearchList.add(searchable);
- }
- }
- }
-
- // Find the global search provider
- Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
- SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
-
- if (newDefaultSearchable == null) {
- Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
- + globalSearchActivity);
- }
+ // Find the global search activity
+ ComponentName newGlobalSearchActivity = findGlobalSearchActivity();
- // Find the default web search provider.
- ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext);
- SearchableInfo newDefaultSearchableForWebSearch = null;
- if (webSearchActivity != null) {
- newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
- }
- if (newDefaultSearchableForWebSearch == null) {
- Log.w(LOG_TAG, "No searchable info found for new default web search activity "
- + webSearchActivity);
- }
+ // Find the web search activity
+ ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
- mSearchablesForWebSearchList = newSearchablesForWebSearchList;
- mDefaultSearchable = newDefaultSearchable;
- mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
+ mGlobalSearchActivity = newGlobalSearchActivity;
+ mWebSearchActivity = newWebSearchActivity;
}
}
/**
- * Checks if the given activity component is present in the system and if so makes it the
- * preferred activity for handling ACTION_WEB_SEARCH.
- * @param component Name of the component to check and set as preferred.
- * @param action Intent action for which this activity is to be set as preferred.
- * @return true if component was detected and set as preferred activity, false if not.
+ * Finds the global search activity.
+ *
+ * This is currently implemented by returning the first activity that handles
+ * the GLOBAL_SEARCH intent and has the GLOBAL_SEARCH permission. If we allow
+ * more than one global search activity to be installed, this code must be changed.
*/
- private static boolean setPreferredActivity(Context context,
- ComponentName component, String action) {
- Log.d(LOG_TAG, "Checking component " + component);
- PackageManager pm = context.getPackageManager();
- ActivityInfo ai;
- try {
- ai = pm.getActivityInfo(component, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
-
- // The code here to find the value for bestMatch is heavily inspired by the code
- // in ResolverActivity where the preferred activity is set.
- Intent intent = new Intent(action);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
- ComponentName set[] = new ComponentName[webSearchActivities.size()];
- int bestMatch = 0;
- for (int i = 0; i < webSearchActivities.size(); ++i) {
- ResolveInfo ri = webSearchActivities.get(i);
- set[i] = new ComponentName(ri.activityInfo.packageName,
- ri.activityInfo.name);
- if (ri.match > bestMatch) bestMatch = ri.match;
+ private ComponentName findGlobalSearchActivity() {
+ Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ if (pm.checkPermission(Manifest.permission.GLOBAL_SEARCH,
+ ai.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return new ComponentName(ai.packageName, ai.name);
+ } else {
+ Log.w(LOG_TAG, "Package " + ai.packageName + " wants to handle GLOBAL_SEARCH, "
+ + "but does not have the GLOBAL_SEARCH permission.");
+ }
}
-
- Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
- IntentFilter filter = new IntentFilter(action);
- filter.addCategory(Intent.CATEGORY_DEFAULT);
- pm.replacePreferredActivity(filter, bestMatch, set, component);
- return true;
+ Log.w(LOG_TAG, "No global search activity found");
+ return null;
}
- private static ComponentName getPreferredWebSearchActivity(Context context) {
- // Check if we have a preferred web search activity.
+ /**
+ * Finds the web search activity.
+ *
+ * Only looks in the package of the global search activity.
+ */
+ private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
+ if (globalSearchActivity == null) {
+ return null;
+ }
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- PackageManager pm = context.getPackageManager();
- ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
- if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
- Log.d(LOG_TAG, "No preferred activity set for action web search.");
-
- // The components in the providers array are checked in the order of declaration so the
- // first one has the highest priority. If the component exists in the system it is set
- // as the preferred activity to handle intent action web search.
- String[] preferredActivities = context.getResources().getStringArray(
- com.android.internal.R.array.default_web_search_providers);
- for (String componentName : preferredActivities) {
- ComponentName component = ComponentName.unflattenFromString(componentName);
- if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) {
- return component;
- }
- }
- } else {
- // If the current preferred activity is GoogleSearch, and we detect
- // EnhancedGoogleSearch installed as well, set the latter as preferred since that
- // is a superset and provides more functionality.
- ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
- if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
- ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
- ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
- if (setPreferredActivity(context, enhancedGoogleSearch,
- Intent.ACTION_WEB_SEARCH)) {
- return enhancedGoogleSearch;
- }
- }
+ intent.setPackage(globalSearchActivity.getPackageName());
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> activities =
+ pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ int count = activities == null ? 0 : activities.size();
+ for (int i = 0; i < count; i++) {
+ ActivityInfo ai = activities.get(i).activityInfo;
+ // TODO: do some sanity checks here?
+ return new ComponentName(ai.packageName, ai.name);
}
-
- if (ri == null) return null;
- return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ Log.w(LOG_TAG, "No web search activity found");
+ return null;
}
/**
@@ -382,24 +307,16 @@ public class Searchables {
}
/**
- * Returns a list of the searchable activities that handle web searches.
- */
- public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
- return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
- }
-
- /**
- * Returns the default searchable activity for web searches.
+ * Gets the name of the global search activity.
*/
- public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
- return mDefaultSearchableForWebSearch;
+ public synchronized ComponentName getGlobalSearchActivity() {
+ return mGlobalSearchActivity;
}
/**
- * Sets the default searchable activity for web searches.
+ * Gets the name of the web search activity.
*/
- public synchronized void setDefaultWebSearch(ComponentName component) {
- setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH);
- buildSearchableList();
+ public synchronized ComponentName getWebSearchActivity() {
+ return mWebSearchActivity;
}
}
diff --git a/core/java/android/service/urlrenderer/IUrlRendererCallback.aidl b/core/java/android/service/urlrenderer/IUrlRendererCallback.aidl
new file mode 100644
index 0000000..004aca7
--- /dev/null
+++ b/core/java/android/service/urlrenderer/IUrlRendererCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.service.urlrenderer;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * {@hide}
+ */
+oneway interface IUrlRendererCallback {
+ void complete(String url, in ParcelFileDescriptor result);
+}
diff --git a/core/java/android/service/urlrenderer/IUrlRendererService.aidl b/core/java/android/service/urlrenderer/IUrlRendererService.aidl
new file mode 100644
index 0000000..d561fdc
--- /dev/null
+++ b/core/java/android/service/urlrenderer/IUrlRendererService.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.service.urlrenderer;
+
+import android.service.urlrenderer.IUrlRendererCallback;
+
+/**
+ * {@hide}
+ */
+interface IUrlRendererService {
+ void render(in List<String> urls, int width, int height,
+ IUrlRendererCallback cb);
+}
diff --git a/core/java/android/service/urlrenderer/UrlRenderer.java b/core/java/android/service/urlrenderer/UrlRenderer.java
new file mode 100644
index 0000000..6057d6c
--- /dev/null
+++ b/core/java/android/service/urlrenderer/UrlRenderer.java
@@ -0,0 +1,89 @@
+/*
+ * 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.service.urlrenderer;
+
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * TODO(phanna): Document this class.
+ * {@hide} while developing
+ */
+public final class UrlRenderer {
+ /**
+ * Interface for clients to receive the result of calls to
+ * {@link UrlRenderer#render}.
+ * {@hide} while developing
+ */
+ public interface Callback {
+ /**
+ * Calls to {@link render} will result in multiple invokations of this
+ * method for each url. A null result means that there was a server
+ * error or a problem rendering the url.
+ * @param url The url that has been rendered.
+ * @param result A ParcelFileDescriptor containing the encoded image
+ * data. The client is responsible for closing the stream
+ * to free resources. A null result indicates a failure
+ * to render.
+ */
+ public void complete(String url, ParcelFileDescriptor result);
+ }
+
+ private IUrlRendererService mService;
+
+ /**
+ * Create a new UrlRenderer to remotely render urls.
+ * @param service An IBinder service usually obtained through
+ * {@link ServiceConnection#onServiceConnected}
+ */
+ public UrlRenderer(IBinder service) {
+ mService = IUrlRendererService.Stub.asInterface(service);
+ }
+
+ private static class InternalCallback extends IUrlRendererCallback.Stub {
+ private final Callback mCallback;
+ InternalCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ public void complete(String url, ParcelFileDescriptor result) {
+ mCallback.complete(url, result);
+ }
+ }
+
+ /**
+ * Render the list of <var>urls</var> and invoke the <var>callback</var>
+ * for each result.
+ * @param urls A List of urls to render.
+ * @param width The desired width of the result.
+ * @param height The desired height of the result.
+ * @param callback An instance of {@link Callback} invoked for each url.
+ */
+ public void render(List<String> urls, int width, int height,
+ Callback callback) {
+ if (mService != null) {
+ try {
+ mService.render(urls, width, height,
+ new InternalCallback(callback));
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/urlrenderer/UrlRendererService.java b/core/java/android/service/urlrenderer/UrlRendererService.java
new file mode 100644
index 0000000..f7bf7d7
--- /dev/null
+++ b/core/java/android/service/urlrenderer/UrlRendererService.java
@@ -0,0 +1,93 @@
+/*
+ * 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.service.urlrenderer;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * TODO(phanna): Complete documentation.
+ * {@hide} while developing
+ */
+public abstract class UrlRendererService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.urlrenderer.UrlRendererService";
+
+ static final String TAG = "UrlRendererService";
+
+ private static class InternalCallback implements UrlRenderer.Callback {
+ private final IUrlRendererCallback mCallback;
+ InternalCallback(IUrlRendererCallback cb) {
+ mCallback = cb;
+ }
+
+ public void complete(String url, ParcelFileDescriptor result) {
+ try {
+ mCallback.complete(url, result);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+
+ private final IUrlRendererService.Stub mBinderInterface =
+ new IUrlRendererService.Stub() {
+ public void render(List<String> urls, int width, int height,
+ IUrlRendererCallback cb) {
+ processRequest(urls, width, height,
+ new InternalCallback(cb));
+ }
+ };
+
+ /**
+ * Implement to return the implementation of the internal accessibility
+ * service interface. Subclasses should not override.
+ */
+ @Override
+ public final android.os.IBinder onBind(android.content.Intent intent) {
+ return mBinderInterface;
+ }
+
+ /**
+ * When all clients unbind from the service, stop the service. Subclasses
+ * should not override.
+ */
+ @Override
+ public final boolean onUnbind(android.content.Intent intent) {
+ stopSelf();
+ return false;
+ }
+
+ /**
+ * Subclasses implement this function to process the given urls. When each
+ * url is complete, the subclass must invoke the callback with the result.
+ * @param urls A list of urls to render at the given dimensions.
+ * @param width The desired width of the result.
+ * @param height The desired height of the result.
+ * @param cb The callback to invoke when each url is complete.
+ */
+ public abstract void processRequest(List<String> urls, int width,
+ int height, UrlRenderer.Callback cb);
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 45719e4..3d1d7d6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -20,12 +20,15 @@ import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
import com.android.internal.view.BaseSurfaceHolder;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.app.Service;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -58,9 +61,13 @@ import java.util.ArrayList;
public abstract class WallpaperService extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
+ * that other applications can not abuse it.
*/
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.service.wallpaper.WallpaperService";
+ "android.service.wallpaper.WallpaperService";
/**
* Name under which a WallpaperService component publishes information
@@ -120,6 +127,7 @@ public abstract class WallpaperService extends Service {
// Current window state.
boolean mCreated;
+ boolean mSurfaceCreated;
boolean mIsCreating;
boolean mDrawingAllowed;
int mWidth;
@@ -130,10 +138,10 @@ public abstract class WallpaperService extends Service {
int mCurHeight;
int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
int mCurWindowFlags = mWindowFlags;
- boolean mDestroyReportNeeded;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
final Rect mContentInsets = new Rect();
+ final Configuration mConfiguration = new Configuration();
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
@@ -220,7 +228,7 @@ public abstract class WallpaperService extends Service {
@Override
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0);
mCaller.sendMessage(msg);
@@ -434,16 +442,17 @@ public abstract class WallpaperService extends Service {
}
int myWidth = mSurfaceHolder.getRequestedWidth();
- if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.FILL_PARENT;
+ if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
int myHeight = mSurfaceHolder.getRequestedHeight();
- if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.FILL_PARENT;
+ if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
final boolean creating = !mCreated;
+ final boolean surfaceCreating = !mSurfaceCreated;
final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
- if (forceRelayout || creating || formatChanged || sizeChanged
+ if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
|| typeChanged || flagsChanged) {
if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
@@ -479,6 +488,7 @@ public abstract class WallpaperService extends Service {
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets);
+ mCreated = true;
}
mSurfaceHolder.mSurfaceLock.lock();
@@ -487,7 +497,7 @@ public abstract class WallpaperService extends Service {
final int relayoutResult = mSession.relayout(
mWindow, mLayout, mWidth, mHeight,
View.VISIBLE, false, mWinFrame, mContentInsets,
- mVisibleInsets, mSurfaceHolder.mSurface);
+ mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface);
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
+ ", frame=" + mWinFrame);
@@ -505,9 +515,13 @@ public abstract class WallpaperService extends Service {
mSurfaceHolder.mSurfaceLock.unlock();
+ if (!mSurfaceHolder.mSurface.isValid()) {
+ reportSurfaceDestroyed();
+ if (DEBUG) Log.v(TAG, "Layout: Surface destroyed");
+ return;
+ }
+
try {
- mDestroyReportNeeded = true;
-
SurfaceHolder.Callback callbacks[] = null;
synchronized (mSurfaceHolder.mCallbacks) {
final int N = mSurfaceHolder.mCallbacks.size();
@@ -517,7 +531,7 @@ public abstract class WallpaperService extends Service {
}
}
- if (!mCreated) {
+ if (surfaceCreating) {
mIsCreating = true;
if (DEBUG) Log.v(TAG, "onSurfaceCreated("
+ mSurfaceHolder + "): " + this);
@@ -528,7 +542,8 @@ public abstract class WallpaperService extends Service {
}
}
}
- if (forceReport || creating || formatChanged || sizeChanged) {
+ if (forceReport || creating || surfaceCreating
+ || formatChanged || sizeChanged) {
if (DEBUG) {
RuntimeException e = new RuntimeException();
e.fillInStackTrace();
@@ -551,7 +566,7 @@ public abstract class WallpaperService extends Service {
}
} finally {
mIsCreating = false;
- mCreated = true;
+ mSurfaceCreated = true;
if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
mSession.finishDrawing(mWindow);
}
@@ -613,6 +628,12 @@ public abstract class WallpaperService extends Service {
mReportedVisible = visible;
if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
+ "): " + this);
+ if (visible) {
+ // If becoming visible, in preview mode the surface
+ // may have been destroyed so now we need to make
+ // sure it is re-created.
+ updateSurface(false, false);
+ }
onVisibilityChanged(visible);
}
}
@@ -637,13 +658,16 @@ public abstract class WallpaperService extends Service {
mPendingSync = false;
mOffsetMessageEnqueued = false;
}
- if (DEBUG) Log.v(TAG, "Offsets change in " + this
- + ": " + xOffset + "," + yOffset);
- final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;
- final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;
- final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;
- final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;
- onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);
+
+ if (mSurfaceCreated) {
+ if (DEBUG) Log.v(TAG, "Offsets change in " + this
+ + ": " + xOffset + "," + yOffset);
+ final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;
+ final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;
+ final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;
+ final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;
+ onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);
+ }
if (sync) {
try {
@@ -671,21 +695,9 @@ public abstract class WallpaperService extends Service {
}
}
- void detach() {
- if (mDestroyed) {
- return;
- }
-
- mDestroyed = true;
-
- if (mVisible) {
- mVisible = false;
- if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
- onVisibilityChanged(false);
- }
-
- if (mDestroyReportNeeded) {
- mDestroyReportNeeded = false;
+ void reportSurfaceDestroyed() {
+ if (mSurfaceCreated) {
+ mSurfaceCreated = false;
SurfaceHolder.Callback callbacks[];
synchronized (mSurfaceHolder.mCallbacks) {
callbacks = new SurfaceHolder.Callback[
@@ -699,6 +711,22 @@ public abstract class WallpaperService extends Service {
+ mSurfaceHolder + "): " + this);
onSurfaceDestroyed(mSurfaceHolder);
}
+ }
+
+ void detach() {
+ if (mDestroyed) {
+ return;
+ }
+
+ mDestroyed = true;
+
+ if (mVisible) {
+ mVisible = false;
+ if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
+ onVisibilityChanged(false);
+ }
+
+ reportSurfaceDestroyed();
if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
onDestroy();
@@ -707,6 +735,8 @@ public abstract class WallpaperService extends Service {
if (mCreated) {
try {
+ if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
+ + mSurfaceHolder.getSurface() + " of: " + this);
mSession.remove(mWindow);
} catch (RemoteException e) {
}
@@ -814,6 +844,7 @@ public abstract class WallpaperService extends Service {
case MSG_WINDOW_RESIZED: {
final boolean reportDraw = message.arg1 != 0;
mEngine.updateSurface(true, false);
+ mEngine.doOffsetsChanged();
if (reportDraw) {
try {
mEngine.mSession.finishDrawing(mEngine.mWindow);
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 2da2258..3d3c44b 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,44 +17,71 @@
package android.speech;
import android.os.Bundle;
-import android.speech.RecognitionResult;
/**
- * Listener for speech recognition events, used with RecognitionService.
+ * Listener for speech recognition events, used with RecognitionService.
* This gives you both the final recognition results, as well as various
* intermediate events that can be used to show visual feedback to the user.
* {@hide}
*/
-interface IRecognitionListener {
- /** Called when the endpointer is ready for the user to start speaking. */
- void onReadyForSpeech(in Bundle noiseParams);
+oneway interface IRecognitionListener {
+ /**
+ * Called when the endpointer is ready for the user to start speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(in Bundle params);
- /** The user has started to speak. */
+ /**
+ * The user has started to speak.
+ */
void onBeginningOfSpeech();
- /** The sound level in the audio stream has changed. */
+ /**
+ * The sound level in the audio stream has changed.
+ *
+ * @param rmsdB the new RMS dB value
+ */
void onRmsChanged(in float rmsdB);
/**
- * More sound has been received. Buffer is a byte buffer containing
- * a sequence of 16-bit shorts.
+ * More sound has been received.
+ *
+ * @param buffer the byte buffer containing a sequence of 16-bit shorts.
*/
void onBufferReceived(in byte[] buffer);
- /** Called after the user stops speaking. */
+ /**
+ * Called after the user stops speaking.
+ */
void onEndOfSpeech();
/**
- * A network or recognition error occurred. The code is defined in
- * {@link android.speech.RecognitionResult}
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link SpeechRecognizer}
*/
void onError(in int error);
- /**
+ /**
* Called when recognition results are ready.
- * @param results: an ordered list of the most likely results (N-best list).
- * @param key: a key associated with the results. The same results can
- * be retrieved asynchronously later using the key, if available.
+ *
+ * @param results a Bundle containing the most likely results (N-best list).
+ */
+ void onResults(in Bundle results);
+
+ /**
+ * Called when recognition partial results are ready.
+ *
+ * @param results a Bundle containing the current most likely result.
+ */
+ void onPartialResults(in Bundle results);
+
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
*/
- void onResults(in List<RecognitionResult> results, long key);
+ void onEvent(in int eventType, in Bundle params);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index a18c380..be6ef6d 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -16,22 +16,45 @@
package android.speech;
+import android.os.Bundle;
import android.content.Intent;
import android.speech.IRecognitionListener;
-import android.speech.RecognitionResult;
-// A Service interface to speech recognition. Call startListening when
-// you want to begin capturing audio; RecognitionService will automatically
-// determine when the user has finished speaking, stream the audio to the
-// recognition servers, and notify you when results are ready.
-/** {@hide} */
-interface IRecognitionService {
- // Start listening for speech. Can only call this from one thread at once.
- // see RecognizerIntent.java for constants used to specify the intent.
- void startListening(in Intent recognizerIntent,
- in IRecognitionListener listener);
-
- List<RecognitionResult> getRecognitionResults(in long key);
+/**
+* A Service interface to speech recognition. Call startListening when
+* you want to begin capturing audio; RecognitionService will automatically
+* determine when the user has finished speaking, stream the audio to the
+* recognition servers, and notify you when results are ready. In most of the cases,
+* this class should not be used directly, instead use {@link SpeechRecognizer} for
+* accessing recognition service.
+* {@hide}
+*/
+oneway interface IRecognitionService {
+ /**
+ * Starts listening for speech. Please note that the recognition service supports
+ * one listener only, therefore, if this function is called from two different threads,
+ * only the latest one will get the notifications
+ *
+ * @param recognizerIntent the intent from which the invocation occurred. Additionally,
+ * this intent can contain extra parameters to manipulate the behavior of the recognition
+ * client. For more information see {@link RecognizerIntent}.
+ * @param listener to receive callbacks, note that this must be non-null
+ */
+ void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
- void cancel();
+ /**
+ * Stops listening for speech. Speech captured so far will be recognized as
+ * if the user had stopped speaking at this point. The function has no effect unless it
+ * is called during the speech capturing.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
+ */
+ void stopListening(in IRecognitionListener listener);
+
+ /**
+ * Cancels the speech recognition.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
+ */
+ void cancel(in IRecognitionListener listener);
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
new file mode 100644
index 0000000..5eb71d7
--- /dev/null
+++ b/core/java/android/speech/RecognitionListener.java
@@ -0,0 +1,98 @@
+/*
+ * 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.speech;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Used for receiving notifications from the SpeechRecognizer when the
+ * recognition related events occur. All the callbacks are executed on the
+ * Application main thread.
+ */
+public interface RecognitionListener {
+ /**
+ * Called when the endpointer is ready for the user to start speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(Bundle params);
+
+ /**
+ * The user has started to speak.
+ */
+ void onBeginningOfSpeech();
+
+ /**
+ * The sound level in the audio stream has changed. There is no guarantee that this method will
+ * be called.
+ *
+ * @param rmsdB the new RMS dB value
+ */
+ void onRmsChanged(float rmsdB);
+
+ /**
+ * More sound has been received. The purpose of this function is to allow giving feedback to the
+ * user regarding the captured audio. There is no guarantee that this method will be called.
+ *
+ * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+ * single channel audio stream. The sample rate is implementation dependent.
+ */
+ void onBufferReceived(byte[] buffer);
+
+ /**
+ * Called after the user stops speaking.
+ */
+ void onEndOfSpeech();
+
+ /**
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link SpeechRecognizer}
+ */
+ void onError(int error);
+
+ /**
+ * Called when recognition results are ready.
+ *
+ * @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
+ */
+ void onResults(Bundle results);
+
+ /**
+ * Called when partial recognition results are available. The callback might be called at any
+ * time between {@link #onBeginningOfSpeech()} and {@link #onResults(Bundle)} when partial
+ * results are ready. This method may be called zero, one or multiple times for each call to
+ * {@link SpeechRecognizer#startListening(Intent)}, depending on the speech recognition
+ * service implementation. To request partial results, use
+ * {@link RecognizerIntent#EXTRA_PARTIAL_RESULTS}
+ *
+ * @param partialResults the returned results. To retrieve the results in
+ * ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
+ * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+ */
+ void onPartialResults(Bundle partialResults);
+
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
+ */
+ void onEvent(int eventType, Bundle params);
+}
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
deleted file mode 100644
index 95715ee..0000000
--- a/core/java/android/speech/RecognitionResult.java
+++ /dev/null
@@ -1,220 +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;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * RecognitionResult is a passive object that stores a single recognized query
- * and its search result.
- *
- * TODO: Revisit and improve this class, reconciling the different types of actions and
- * the different ways they are represented. Maybe we should have a separate result object
- * for each type, and put them (type/value) in bundle?
- * {@hide}
- */
-public class RecognitionResult implements Parcelable {
- /**
- * Status of the recognize request.
- */
- public static final int NETWORK_TIMEOUT = 1; // Network operation timed out.
-
- public static final int NETWORK_ERROR = 2; // Other network related errors.
-
- public static final int AUDIO_ERROR = 3; // Audio recording error.
-
- public static final int SERVER_ERROR = 4; // Server sends error status.
-
- public static final int CLIENT_ERROR = 5; // Other client side errors.
-
- public static final int SPEECH_TIMEOUT = 6; // No speech input
-
- public static final int NO_MATCH = 7; // No recognition result matched.
-
- public static final int SERVICE_BUSY = 8; // RecognitionService busy.
-
- /**
- * Type of the recognition results.
- */
- public static final int RAW_RECOGNITION_RESULT = 0;
-
- public static final int WEB_SEARCH_RESULT = 1;
-
- public static final int CONTACT_RESULT = 2;
-
- public static final int ACTION_RESULT = 3;
-
- /**
- * A factory method to create a raw RecognitionResult
- *
- * @param sentence the recognized text.
- */
- public static RecognitionResult newRawRecognitionResult(String sentence) {
- return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
- }
-
- /**
- * A factory method to create a RecognitionResult for contacts.
- *
- * @param contact the contact name.
- * @param phoneType the phone type.
- * @param callAction whether this result included a command to "call", or
- * just the contact name.
- */
- public static RecognitionResult newContactResult(String contact, int phoneType,
- boolean callAction) {
- return new RecognitionResult(CONTACT_RESULT, contact, phoneType, callAction);
- }
-
- /**
- * A factory method to create a RecognitionResult for a web search query.
- *
- * @param query the query string.
- * @param html the html page of the search result.
- * @param url the url that performs the search with the query.
- */
- public static RecognitionResult newWebResult(String query, String html, String url) {
- return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
- }
-
- /**
- * A factory method to create a RecognitionResult for an action.
- *
- * @param action the action type
- * @param query the query string associated with that action.
- */
- public static RecognitionResult newActionResult(int action, String query) {
- return new RecognitionResult(ACTION_RESULT, action, query);
- }
-
- public static final Parcelable.Creator<RecognitionResult> CREATOR =
- new Parcelable.Creator<RecognitionResult>() {
-
- public RecognitionResult createFromParcel(Parcel in) {
- return new RecognitionResult(in);
- }
-
- public RecognitionResult[] newArray(int size) {
- return new RecognitionResult[size];
- }
- };
-
- /**
- * Result type.
- */
- public final int mResultType;
-
- /**
- * The recognized string when mResultType is WEB_SEARCH_RESULT. The name of
- * the contact when mResultType is CONTACT_RESULT. The relevant query when
- * mResultType is ACTION_RESULT.
- */
- public final String mText;
-
- /**
- * The HTML result page for the query. If this is null, then the application
- * must use the url field to get the HTML result page.
- */
- public final String mHtml;
-
- /**
- * The url to get the result page for the query string. The application must
- * use this url instead of performing the search with the query.
- */
- public final String mUrl;
-
- /**
- * Phone number type. This is valid only when mResultType == CONTACT_RESULT.
- */
- public final int mPhoneType;
-
- /**
- * Action type. This is valid only when mResultType == ACTION_RESULT.
- */
- public final int mAction;
-
- /**
- * Whether a contact recognition result included a command to "call". This
- * is valid only when mResultType == CONTACT_RESULT.
- */
- public final boolean mCallAction;
-
- private RecognitionResult(int type, int action, String query) {
- mResultType = type;
- mAction = action;
- mText = query;
- mHtml = null;
- mUrl = null;
- mPhoneType = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, String html, String url) {
- mResultType = type;
- mText = query;
- mHtml = html;
- mUrl = url;
- mPhoneType = -1;
- mAction = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, int phoneType, boolean callAction) {
- mResultType = type;
- mText = query;
- mPhoneType = phoneType;
- mHtml = null;
- mUrl = null;
- mAction = -1;
- mCallAction = callAction;
- }
-
- private RecognitionResult(Parcel in) {
- mResultType = in.readInt();
- mText = in.readString();
- mHtml = in.readString();
- mUrl = in.readString();
- mPhoneType = in.readInt();
- mAction = in.readInt();
- mCallAction = (in.readInt() == 1);
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mResultType);
- out.writeString(mText);
- out.writeString(mHtml);
- out.writeString(mUrl);
- out.writeInt(mPhoneType);
- out.writeInt(mAction);
- out.writeInt(mCallAction ? 1 : 0);
- }
-
- @Override
- public String toString() {
- String resultType[] = {
- "RAW", "WEB", "CONTACT", "ACTION"
- };
- return "[type=" + resultType[mResultType] + ", text=" + mText + ", mUrl=" + mUrl
- + ", html=" + mHtml + ", mAction=" + mAction + ", mCallAction=" + mCallAction + "]";
- }
-
- public int describeContents() {
- // no special description
- return 0;
- }
-}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
new file mode 100644
index 0000000..75a5ed5
--- /dev/null
+++ b/core/java/android/speech/RecognitionService.java
@@ -0,0 +1,333 @@
+/*
+ * 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.speech;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides a base class for recognition service implementations. This class should be
+ * extended only in case you wish to implement a new speech recognizer. Please note that the
+ * implementation of this service is stateless.
+ */
+public abstract class RecognitionService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.speech.RecognitionService";
+
+ /**
+ * Name under which a RecognitionService component publishes information about itself.
+ * This meta-data should reference an XML resource containing a
+ * <code>&lt;{@link android.R.styleable#RecognitionService recognition-service}&gt;</code> tag.
+ */
+ public static final String SERVICE_META_DATA = "android.speech";
+
+ /** Log messages identifier */
+ private static final String TAG = "RecognitionService";
+
+ /** Debugging flag */
+ private static final boolean DBG = false;
+
+ /** Binder of the recognition service */
+ private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
+
+ /**
+ * The current callback of an application that invoked the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method
+ */
+ private Callback mCurrentCallback = null;
+
+ private static final int MSG_START_LISTENING = 1;
+
+ private static final int MSG_STOP_LISTENING = 2;
+
+ private static final int MSG_CANCEL = 3;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LISTENING:
+ StartListeningArgs args = (StartListeningArgs) msg.obj;
+ dispatchStartListening(args.mIntent, args.mListener);
+ break;
+ case MSG_STOP_LISTENING:
+ dispatchStopListening((IRecognitionListener) msg.obj);
+ break;
+ case MSG_CANCEL:
+ dispatchCancel((IRecognitionListener) msg.obj);
+ }
+ }
+ };
+
+ private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
+ mCurrentCallback = new Callback(listener);
+ RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ } else {
+ try {
+ listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ } catch (RemoteException e) {
+ Log.d(TAG, "onError call from startListening failed");
+ }
+ Log.i(TAG, "concurrent startListening received - ignoring this call");
+ }
+ }
+
+ private void dispatchStopListening(IRecognitionListener listener) {
+ try {
+ if (mCurrentCallback == null) {
+ listener.onError(SpeechRecognizer.ERROR_CLIENT);
+ Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
+ Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onStopListening(mCurrentCallback);
+ }
+ } catch (RemoteException e) { // occurs if onError fails
+ Log.d(TAG, "onError call from stopListening failed");
+ }
+ }
+
+ private void dispatchCancel(IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onCancel(mCurrentCallback);
+ mCurrentCallback = null;
+ if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
+ }
+ }
+
+ private class StartListeningArgs {
+ public final Intent mIntent;
+
+ public final IRecognitionListener mListener;
+
+ public StartListeningArgs(Intent intent, IRecognitionListener listener) {
+ this.mIntent = intent;
+ this.mListener = listener;
+ }
+ }
+
+ /**
+ * Checks whether the caller has sufficient permissions
+ *
+ * @param listener to send the error message to in case of error
+ * @return {@code true} if the caller has enough permissions, {@code false} otherwise
+ */
+ private boolean checkPermissions(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "checkPermissions");
+ if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
+ RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ try {
+ Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
+ listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
+ } catch (RemoteException re) {
+ Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
+ }
+ return false;
+ }
+
+ /**
+ * Notifies the service that it should start listening for speech.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values should be used by the recognizer.
+ * @param listener that will receive the service's callbacks
+ */
+ protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
+
+ /**
+ * Notifies the service that it should cancel the speech recognition.
+ */
+ protected abstract void onCancel(Callback listener);
+
+ /**
+ * Notifies the service that it should stop listening for speech. Speech captured so far should
+ * be recognized as if the user had stopped speaking at this point. This method is only called
+ * if the application calls it explicitly.
+ */
+ protected abstract void onStopListening(Callback listener);
+
+ @Override
+ public final IBinder onBind(final Intent intent) {
+ if (DBG) Log.d(TAG, "onBind, intent=" + intent);
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DBG) Log.d(TAG, "onDestroy");
+ mCurrentCallback = null;
+ mBinder.clearReference();
+ super.onDestroy();
+ }
+
+ /**
+ * This class receives callbacks from the speech recognition service and forwards them to the
+ * user. An instance of this class is passed to the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
+ * these methods on any thread.
+ */
+ public class Callback {
+ private final IRecognitionListener mListener;
+
+ private Callback(IRecognitionListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * The service should call this method when the user has started to speak.
+ */
+ public void beginningOfSpeech() throws RemoteException {
+ if (DBG) Log.d(TAG, "beginningOfSpeech");
+ mListener.onBeginningOfSpeech();
+ }
+
+ /**
+ * The service should call this method when sound has been received. The purpose of this
+ * function is to allow giving feedback to the user regarding the captured audio.
+ *
+ * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+ * single channel audio stream. The sample rate is implementation dependent.
+ */
+ public void bufferReceived(byte[] buffer) throws RemoteException {
+ mListener.onBufferReceived(buffer);
+ }
+
+ /**
+ * The service should call this method after the user stops speaking.
+ */
+ public void endOfSpeech() throws RemoteException {
+ mListener.onEndOfSpeech();
+ }
+
+ /**
+ * The service should call this method when a network or recognition error occurred.
+ *
+ * @param error code is defined in {@link SpeechRecognizer}
+ */
+ public void error(int error) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onError(error);
+ }
+
+ /**
+ * The service should call this method when partial recognition results are available. This
+ * method can be called at any time between {@link #beginningOfSpeech()} and
+ * {@link #results(Bundle)} when partial results are ready. This method may be called zero,
+ * one or multiple times for each call to {@link SpeechRecognizer#startListening(Intent)},
+ * depending on the speech recognition service implementation.
+ *
+ * @param partialResults the returned results. To retrieve the results in
+ * ArrayList&lt;String&gt; format use {@link Bundle#getStringArrayList(String)} with
+ * {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
+ */
+ public void partialResults(Bundle partialResults) throws RemoteException {
+ mListener.onPartialResults(partialResults);
+ }
+
+ /**
+ * The service should call this method when the endpointer is ready for the user to start
+ * speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ public void readyForSpeech(Bundle params) throws RemoteException {
+ mListener.onReadyForSpeech(params);
+ }
+
+ /**
+ * The service should call this method when recognition results are ready.
+ *
+ * @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
+ */
+ public void results(Bundle results) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onResults(results);
+ }
+
+ /**
+ * The service should call this method when the sound level in the audio stream has changed.
+ * There is no guarantee that this method will be called.
+ *
+ * @param rmsdB the new RMS dB value
+ */
+ public void rmsChanged(float rmsdB) throws RemoteException {
+ mListener.onRmsChanged(rmsdB);
+ }
+ }
+
+ /** Binder of the recognition service */
+ private static class RecognitionServiceBinder extends IRecognitionService.Stub {
+ private RecognitionService mInternalService;
+
+ public RecognitionServiceBinder(RecognitionService service) {
+ mInternalService = service;
+ }
+
+ public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
+ if (mInternalService != null && mInternalService.checkPermissions(listener)) {
+ mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
+ MSG_START_LISTENING, mInternalService.new StartListeningArgs(
+ recognizerIntent, listener)));
+ }
+ }
+
+ public void stopListening(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
+ if (mInternalService != null && mInternalService.checkPermissions(listener)) {
+ mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
+ MSG_STOP_LISTENING, listener));
+ }
+ }
+
+ public void cancel(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
+ if (mInternalService != null && mInternalService.checkPermissions(listener)) {
+ mInternalService.mHandler.sendMessage(Message.obtain(mInternalService.mHandler,
+ MSG_CANCEL, listener));
+ }
+ }
+
+ public void clearReference() {
+ mInternalService = null;
+ }
+ }
+}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
deleted file mode 100644
index 4207543..0000000
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ /dev/null
@@ -1,101 +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;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.speech.RecognitionResult;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Utils for Google's network-based speech recognizer, which lets you perform
- * speech-to-text translation through RecognitionService. IRecognitionService
- * and IRecognitionListener are the core interfaces; you begin recognition
- * through IRecognitionService and subscribe to callbacks about when the user
- * stopped speaking, results come in, errors, etc. through IRecognitionListener.
- * RecognitionServiceUtil includes default IRecognitionListener and
- * ServiceConnection implementations to reduce the amount of boilerplate.
- *
- * The Service provides no user interface. See RecognitionActivity if you
- * want the standard voice search UI.
- *
- * Below is a small skeleton of how to use the recognizer:
- *
- * ServiceConnection conn = new RecognitionServiceUtil.Connection();
- * mContext.bindService(RecognitionServiceUtil.sDefaultIntent,
- * conn, Context.BIND_AUTO_CREATE);
- * IRecognitionListener listener = new RecognitionServiceWrapper.NullListener() {
- * public void onResults(List<String> results) {
- * // Do something with recognition transcripts
- * }
- * }
- *
- * // Must wait for conn.mService to be populated, then call below
- * conn.mService.startListening(null, listener);
- *
- * {@hide}
- */
-public class RecognitionServiceUtil {
- public static final Intent sDefaultIntent = new Intent(
- RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-
- // Recognize request parameters
- public static final String USE_LOCATION = "useLocation";
- public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
-
- // Bundles
- public static final String NOISE_LEVEL = "NoiseLevel";
- public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
-
- private RecognitionServiceUtil() {}
-
- /**
- * IRecognitionListener which does nothing in response to recognition
- * callbacks. You can subclass from this and override only the methods
- * whose events you want to respond to.
- */
- public static class NullListener extends IRecognitionListener.Stub {
- public void onReadyForSpeech(Bundle bundle) {}
- public void onBeginningOfSpeech() {}
- public void onRmsChanged(float rmsdB) {}
- public void onBufferReceived(byte[] buf) {}
- public void onEndOfSpeech() {}
- public void onError(int error) {}
- public void onResults(List<RecognitionResult> results, long key) {}
- }
-
- /**
- * Basic ServiceConnection which just records mService variable.
- */
- public static class Connection implements ServiceConnection {
- public IRecognitionService mService;
-
- public synchronized void onServiceConnected(ComponentName name, IBinder service) {
- mService = IRecognitionService.Stub.asInterface(service);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- }
- }
-}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 987e763..d55a943 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -16,22 +16,45 @@
package android.speech;
+import java.util.ArrayList;
+
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
/**
* Constants for supporting speech recognition through starting an {@link Intent}
*/
public class RecognizerIntent {
+ /**
+ * The extra key used in an intent to the speech recognizer for voice search. Not
+ * generally to be used by developers. The system search dialog uses this, for example,
+ * to set a calling package for identification by a voice search API. If this extra
+ * is set by anyone but the system process, it should be overridden by the voice search
+ * implementation.
+ */
+ public final static String EXTRA_CALLING_PACKAGE = "calling_package";
+
private RecognizerIntent() {
// Not for instantiating.
}
/**
* Starts an activity that will prompt the user for speech and sends it through a
- * speech recognizer. The results will be returned via activity results, or forwarded
- * via a PendingIntent if one is provided.
+ * 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
+ * if one is provided.
+ *
+ * <p>Starting this intent with just {@link Activity#startActivity(Intent)} is not supported.
+ * You must either use {@link Activity#startActivityForResult(Intent, int)}, or provide a
+ * PendingIntent, to receive recognition results.
*
* <p>Required extras:
* <ul>
@@ -47,7 +70,7 @@ public class RecognizerIntent {
* <li>{@link #EXTRA_RESULTS_PENDINGINTENT_BUNDLE}
* </ul>
*
- * <p> Result extras:
+ * <p> Result extras (returned in the result, not to be specified in the request):
* <ul>
* <li>{@link #EXTRA_RESULTS}
* </ul>
@@ -71,9 +94,10 @@ public class RecognizerIntent {
* <li>{@link #EXTRA_PROMPT}
* <li>{@link #EXTRA_LANGUAGE}
* <li>{@link #EXTRA_MAX_RESULTS}
+ * <li>{@link #EXTRA_PARTIAL_RESULTS}
* </ul>
*
- * <p> Result extras:
+ * <p> Result extras (returned in the result, not to be specified in the request):
* <ul>
* <li>{@link #EXTRA_RESULTS}
* </ul>
@@ -84,6 +108,42 @@ public class RecognizerIntent {
public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
/**
+ * The minimum length of an utterance. We will not stop recording before this amount of time.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If you don't
+ * have a very good reason to change these, you should leave them as they are. Note also that
+ * certain values may cause undesired or unexpected results - use judiciously! Additionally,
+ * depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * complete.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * possibly complete. This is used to prevent the endpointer cutting off during very short
+ * mid-speech pauses.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
* Informs the recognizer which speech model to prefer when performing
* {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this
* information to fine tune the results. This extra is required. Activities implementing
@@ -111,8 +171,9 @@ public class RecognizerIntent {
public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
/**
- * Optional language override to inform the recognizer that it should expect speech in
- * a language different than the one set in the {@link java.util.Locale#getDefault()}.
+ * Optional IETF language tag (as defined by BCP 47), for example "en-US". This tag informs the
+ * recognizer to perform speech recognition in a language different than the one set in the
+ * {@link java.util.Locale#getDefault()}.
*/
public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
@@ -121,7 +182,14 @@ public class RecognizerIntent {
* will choose how many results to return. Must be an integer.
*/
public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
-
+
+ /**
+ * Optional boolean to indicate whether partial results should be returned by the recognizer
+ * as the user speaks (default is false). The server may ignore a request for partial
+ * results in some or all cases.
+ */
+ public static final String EXTRA_PARTIAL_RESULTS = "android.speech.extra.PARTIAL_RESULTS";
+
/**
* When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
* return results to you via the activity results mechanism. Alternatively, if you use this
@@ -130,6 +198,7 @@ public class RecognizerIntent {
*/
public static final String EXTRA_RESULTS_PENDINGINTENT =
"android.speech.extra.RESULTS_PENDINGINTENT";
+
/**
* If you use {@link #EXTRA_RESULTS_PENDINGINTENT} to supply a forwarding intent, you can
* also use this extra to supply additional extras for the final intent. The search results
@@ -150,8 +219,107 @@ public class RecognizerIntent {
public static final int RESULT_AUDIO_ERROR = Activity.RESULT_FIRST_USER + 4;
/**
- * An ArrayList<String> of the potential results when performing
- * {@link #ACTION_RECOGNIZE_SPEECH}. Only present when {@link Activity#RESULT_OK} is returned.
+ * 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.
*/
public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
+
+ /**
+ * 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.
+ * <p>
+ * This is based on the value specified by the voice search {@link Activity} in
+ * {@link #DETAILS_META_DATA}, and if this is not specified, will return null. Also if there
+ * is no chosen default to resolve for {@link #ACTION_WEB_SEARCH}, this will return null.
+ * <p>
+ * If an intent is returned and is fired, a {@link Bundle} of extras will be returned to the
+ * provided result receiver, and should ideally contain values for
+ * {@link #EXTRA_LANGUAGE_PREFERENCE} and {@link #EXTRA_SUPPORTED_LANGUAGES}.
+ * <p>
+ * (Whether these are actually provided is up to the particular implementation. It is
+ * recommended that {@link Activity}s implementing {@link #ACTION_WEB_SEARCH} provide this
+ * information, but it is not required.)
+ *
+ * @param context a context object
+ * @return the broadcast intent to fire or null if not available
+ */
+ public static final Intent getVoiceDetailsIntent(Context context) {
+ Intent voiceSearchIntent = new Intent(ACTION_WEB_SEARCH);
+ ResolveInfo ri = context.getPackageManager().resolveActivity(
+ voiceSearchIntent, PackageManager.GET_META_DATA);
+ if (ri == null || ri.activityInfo == null || ri.activityInfo.metaData == null) return null;
+
+ String className = ri.activityInfo.metaData.getString(DETAILS_META_DATA);
+ if (className == null) return null;
+
+ Intent detailsIntent = new Intent(ACTION_GET_LANGUAGE_DETAILS);
+ detailsIntent.setComponent(new ComponentName(ri.activityInfo.packageName, className));
+ return detailsIntent;
+ }
+
+ /**
+ * Meta-data name under which an {@link Activity} implementing {@link #ACTION_WEB_SEARCH} can
+ * use to expose the class name of a {@link BroadcastReceiver} which can respond to request for
+ * more information, from any of the broadcast intents specified in this class.
+ * <p>
+ * Broadcast intents can be directed to the class name specified in the meta-data by creating
+ * an {@link Intent}, setting the component with
+ * {@link Intent#setComponent(android.content.ComponentName)}, and using
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle)}
+ * with another {@link BroadcastReceiver} which can receive the results.
+ * <p>
+ * The {@link #getVoiceDetailsIntent(Context)} method is provided as a convenience to create
+ * a broadcast intent based on the value of this meta-data, if available.
+ * <p>
+ * This is optional and not all {@link Activity}s which implement {@link #ACTION_WEB_SEARCH}
+ * are required to implement this. Thus retrieving this meta-data may be null.
+ */
+ public static final String DETAILS_META_DATA = "android.speech.DETAILS";
+
+ /**
+ * A broadcast intent which can be fired to the {@link BroadcastReceiver} component specified
+ * in the meta-data defined in the {@link #DETAILS_META_DATA} meta-data of an
+ * {@link Activity} satisfying {@link #ACTION_WEB_SEARCH}.
+ * <p>
+ * When fired with
+ * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle)},
+ * a {@link Bundle} of extras will be returned to the provided result receiver, and should
+ * ideally contain values for {@link #EXTRA_LANGUAGE_PREFERENCE} and
+ * {@link #EXTRA_SUPPORTED_LANGUAGES}.
+ * <p>
+ * (Whether these are actually provided is up to the particular implementation. It is
+ * recommended that {@link Activity}s implementing {@link #ACTION_WEB_SEARCH} provide this
+ * information, but it is not required.)
+ */
+ public static final String ACTION_GET_LANGUAGE_DETAILS =
+ "android.speech.action.GET_LANGUAGE_DETAILS";
+
+ /**
+ * Specify this boolean extra in a broadcast of {@link #ACTION_GET_LANGUAGE_DETAILS} to
+ * indicate that only the current language preference is needed in the response. This
+ * avoids any additional computation if all you need is {@link #EXTRA_LANGUAGE_PREFERENCE}
+ * in the response.
+ */
+ public static final String EXTRA_ONLY_RETURN_LANGUAGE_PREFERENCE =
+ "android.speech.extra.ONLY_RETURN_LANGUAGE_PREFERENCE";
+
+ /**
+ * The key to the extra in the {@link Bundle} returned by {@link #ACTION_GET_LANGUAGE_DETAILS}
+ * which is a {@link String} that represents the current language preference this user has
+ * specified - a locale string like "en-US".
+ */
+ public static final String EXTRA_LANGUAGE_PREFERENCE =
+ "android.speech.extra.LANGUAGE_PREFERENCE";
+
+ /**
+ * The key to the extra in the {@link Bundle} returned by {@link #ACTION_GET_LANGUAGE_DETAILS}
+ * which is an {@link ArrayList} of {@link String}s that represents the languages supported by
+ * this implementation of voice recognition - a list of strings like "en-US", "cmn-Hans-CN",
+ * etc.
+ */
+ public static final String EXTRA_SUPPORTED_LANGUAGES =
+ "android.speech.extra.SUPPORTED_LANGUAGES";
}
diff --git a/core/java/android/speech/RecognizerResultsIntent.java b/core/java/android/speech/RecognizerResultsIntent.java
new file mode 100644
index 0000000..b45e4b1
--- /dev/null
+++ b/core/java/android/speech/RecognizerResultsIntent.java
@@ -0,0 +1,147 @@
+/*
+ * 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.speech;
+
+import android.os.Bundle;
+
+import java.util.ArrayList;
+
+/**
+ * Constants for intents related to showing speech recognition results.
+ *
+ * These constants should not be needed for normal utilization of speech recognition. They
+ * would only be called if you wanted to trigger a view of voice search results in your
+ * application, or implemented if you wanted to offer a different view for voice search results
+ * with your application.
+ *
+ * The standard behavior here for someone receiving an {@link #ACTION_VOICE_SEARCH_RESULTS} is to
+ * first retrieve the list of {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS}, and use any provided
+ * HTML for that result in {@link #EXTRA_VOICE_SEARCH_RESULT_HTML}, if available, to display
+ * the search results. If that is not available, then the corresponding url for that result in
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_URLS} should be used. And if even that is not available,
+ * then a search url should be constructed from the actual recognition result string.
+ *
+ * @hide for making public in a later release
+ */
+public class RecognizerResultsIntent {
+ private RecognizerResultsIntent() {
+ // Not for instantiating.
+ }
+
+ /**
+ * Intent that can be sent by implementations of voice search to display the results of
+ * a search in, for example, a web browser.
+ *
+ * This intent should always be accompanied by at least
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS}, and optionally but recommended,
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_URLS}, and sometimes
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_HTML} and
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS}.
+ *
+ * These are parallel arrays, where a recognition result string at index N of
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS} should be accompanied by a url to use for
+ * searching based on that string at index N of {@link #EXTRA_VOICE_SEARCH_RESULT_URLS},
+ * and, possibly, the full html to display for that result at index N of
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_HTML}. If full html is provided, a base url (or
+ * list of base urls) should be provided with {@link #EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS}.
+ *
+ * @hide for making public in a later release
+ */
+ public static final String ACTION_VOICE_SEARCH_RESULTS =
+ "android.speech.action.VOICE_SEARCH_RESULTS";
+
+ /**
+ * The key to an extra {@link ArrayList} of {@link String}s that contains the list of
+ * recognition alternates from voice search, in order from highest to lowest confidence.
+ *
+ * @hide for making public in a later release
+ */
+ public static final String EXTRA_VOICE_SEARCH_RESULT_STRINGS =
+ "android.speech.extras.VOICE_SEARCH_RESULT_STRINGS";
+
+ /**
+ * The key to an extra {@link ArrayList} of {@link String}s that contains the search urls
+ * to use, if available, for the recognition alternates provided in
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS}. This list should always be the same size as the
+ * one provided in {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS} - if a result cannot provide a
+ * search url, that entry in this ArrayList should be <code>null</code>, and the implementor of
+ * {@link #ACTION_VOICE_SEARCH_RESULTS} should execute a search of its own choosing,
+ * based on the recognition result string.
+ *
+ * @hide for making public in a later release
+ */
+ public static final String EXTRA_VOICE_SEARCH_RESULT_URLS =
+ "android.speech.extras.VOICE_SEARCH_RESULT_URLS";
+
+ /**
+ * The key to an extra {@link ArrayList} of {@link String}s that contains the html content to
+ * use, if available, for the recognition alternates provided in
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS}. This list should always be the same size as the
+ * one provided in {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS} - if a result cannot provide
+ * html, that entry in this list should be <code>null</code>, and the implementor of
+ * {@link #ACTION_VOICE_SEARCH_RESULTS} should back off to the corresponding url provided in
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_URLS}, if available, or else should execute a search of
+ * its own choosing, based on the recognition result string.
+ *
+ * Currently this html content should be expected in the form of a uri with scheme
+ * {@link #URI_SCHEME_INLINE} for the Browser. In the future this may change to a "content://"
+ * uri or some other identifier. Anyone who reads this extra should confirm that a result is
+ * in fact an "inline:" uri and back off to the urls or strings gracefully if it is not, thus
+ * maintaining future backwards compatibility if this changes.
+ *
+ * @hide not to be exposed immediately as the implementation details may change
+ */
+ public static final String EXTRA_VOICE_SEARCH_RESULT_HTML =
+ "android.speech.extras.VOICE_SEARCH_RESULT_HTML";
+
+ /**
+ * The key to an extra {@link ArrayList} of {@link String}s that contains the base url to
+ * assume when interpreting html provided in {@link #EXTRA_VOICE_SEARCH_RESULT_HTML}.
+ *
+ * A list of size 1 may be provided to apply the same base url to all html results.
+ * A list of the same size as {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS} may be provided
+ * to apply different base urls to each different html result in the
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_HTML} list.
+ *
+ * @hide not to be exposed immediately as the implementation details may change
+ */
+ public static final String EXTRA_VOICE_SEARCH_RESULT_HTML_BASE_URLS =
+ "android.speech.extras.VOICE_SEARCH_RESULT_HTML_BASE_URLS";
+
+ /**
+ * The key to an extra {@link ArrayList} of {@link Bundle}s that contains key/value pairs.
+ * All the values and the keys are {@link String}s. Each key/value pair represents an extra HTTP
+ * header. The keys can't be the standard HTTP headers as they are set by the WebView.
+ *
+ * A list of size 1 may be provided to apply the same HTTP headers to all web results. A
+ * list of the same size as {@link #EXTRA_VOICE_SEARCH_RESULT_STRINGS} may be provided to
+ * apply different HTTP headers to each different web result in the list. These headers will
+ * only be used in the case that the url for a particular web result (from
+ * {@link #EXTRA_VOICE_SEARCH_RESULT_URLS}) is loaded.
+ *
+ * @hide not to be exposed immediately as the implementation details may change
+ */
+ public static final String EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS =
+ "android.speech.extras.EXTRA_VOICE_SEARCH_RESULT_HTTP_HEADERS";
+
+ /**
+ * The scheme used currently for html content in {@link #EXTRA_VOICE_SEARCH_RESULT_HTML}.
+ *
+ * @hide not to be exposed immediately as the implementation details may change
+ */
+ public static final String URI_SCHEME_INLINE = "inline";
+}
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
new file mode 100644
index 0000000..8fa0d59
--- /dev/null
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -0,0 +1,480 @@
+/*
+ * 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.speech;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * This class provides access to the speech recognition service. This service allows access to the
+ * speech recognizer. Do not instantiate this class directly, instead, call
+ * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
+ * invoked only from the main application thread. Please note that the application must have
+ * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class.
+ */
+public class SpeechRecognizer {
+ /** DEBUG value to enable verbose debug prints */
+ private final static boolean DBG = false;
+
+ /** Log messages identifier */
+ private static final String TAG = "SpeechRecognizer";
+
+ /**
+ * Used to retrieve an {@code ArrayList&lt;String&gt;} 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";
+
+ /** Network operation timed out. */
+ public static final int ERROR_NETWORK_TIMEOUT = 1;
+
+ /** Other network related errors. */
+ public static final int ERROR_NETWORK = 2;
+
+ /** Audio recording error. */
+ public static final int ERROR_AUDIO = 3;
+
+ /** Server sends error status. */
+ public static final int ERROR_SERVER = 4;
+
+ /** Other client side errors. */
+ public static final int ERROR_CLIENT = 5;
+
+ /** No speech input */
+ public static final int ERROR_SPEECH_TIMEOUT = 6;
+
+ /** No recognition result matched. */
+ public static final int ERROR_NO_MATCH = 7;
+
+ /** RecognitionService busy. */
+ public static final int ERROR_RECOGNIZER_BUSY = 8;
+
+ /** Insufficient permissions */
+ public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
+
+ /** action codes */
+ private final static int MSG_START = 1;
+ private final static int MSG_STOP = 2;
+ private final static int MSG_CANCEL = 3;
+ private final static int MSG_CHANGE_LISTENER = 4;
+
+ /** The actual RecognitionService endpoint */
+ private IRecognitionService mService;
+
+ /** The connection to the actual service */
+ private Connection mConnection;
+
+ /** Context with which the manager was created */
+ private final Context mContext;
+
+ /** Component to direct service intent to */
+ private final ComponentName mServiceComponent;
+
+ /** Handler that will execute the main tasks */
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START:
+ handleStartListening((Intent) msg.obj);
+ break;
+ case MSG_STOP:
+ handleStopMessage();
+ break;
+ case MSG_CANCEL:
+ handleCancelMessage();
+ break;
+ case MSG_CHANGE_LISTENER:
+ handleChangeListener((RecognitionListener) msg.obj);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Temporary queue, saving the messages until the connection will be established, afterwards,
+ * only mHandler will receive the messages
+ */
+ private final Queue<Message> mPendingTasks = new LinkedList<Message>();
+
+ /** The Listener that will receive all the callbacks */
+ private final InternalListener mListener = new InternalListener();
+
+ /**
+ * The right way to create a {@code SpeechRecognizer} is by using
+ * {@link #createSpeechRecognizer} static factory method
+ */
+ private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
+ mContext = context;
+ mServiceComponent = serviceComponent;
+ }
+
+ /**
+ * Basic ServiceConnection that records the mService variable. Additionally, on creation it
+ * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
+ */
+ private class Connection implements ServiceConnection {
+
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ // always done on the application main thread, so no need to send message to mHandler
+ mService = IRecognitionService.Stub.asInterface(service);
+ if (DBG) Log.d(TAG, "onServiceConnected - Success");
+ while (!mPendingTasks.isEmpty()) {
+ mHandler.sendMessage(mPendingTasks.poll());
+ }
+ }
+
+ public void onServiceDisconnected(final ComponentName name) {
+ // always done on the application main thread, so no need to send message to mHandler
+ mService = null;
+ mConnection = null;
+ mPendingTasks.clear();
+ if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
+ }
+ }
+
+ /**
+ * Checks whether a speech recognition service is available on the system. If this method
+ * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
+ * fail.
+ *
+ * @param context with which {@code SpeechRecognizer} will be created
+ * @return {@code true} if recognition is available, {@code false} otherwise
+ */
+ public static boolean isRecognitionAvailable(final Context context) {
+ final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
+ new Intent(RecognitionService.SERVICE_INTERFACE), 0);
+ return list != null && list.size() != 0;
+ }
+
+ /**
+ * Factory method to create a new {@code SpeechRecognizer}. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
+ * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
+ * received.
+ *
+ * @param context in which to create {@code SpeechRecognizer}
+ * @return a new {@code SpeechRecognizer}
+ */
+ public static SpeechRecognizer createSpeechRecognizer(final Context context) {
+ return createSpeechRecognizer(context, null);
+ }
+
+ /**
+ * Factory method to create a new {@code SpeechRecognizer}. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
+ * command to the created {@code SpeechRecognizer}, otherwise no notifications will be
+ * received.
+ *
+ * Use this version of the method to specify a specific service to direct this
+ * {@link SpeechRecognizer} to. Normally you would not use this; use
+ * {@link #createSpeechRecognizer(Context)} instead to use the system default recognition
+ * service.
+ *
+ * @param context in which to create {@code SpeechRecognizer}
+ * @param serviceComponent the {@link ComponentName} of a specific service to direct this
+ * {@code SpeechRecognizer} to
+ * @return a new {@code SpeechRecognizer}
+ */
+ public static SpeechRecognizer createSpeechRecognizer(final Context context,
+ final ComponentName serviceComponent) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null)");
+ }
+ checkIsCalledFromMainThread();
+ return new SpeechRecognizer(context, serviceComponent);
+ }
+
+ /**
+ * Sets the listener that will receive all the callbacks. The previous unfinished commands will
+ * be executed with the old listener, while any following command will be executed with the new
+ * listener.
+ *
+ * @param listener listener that will receive all the callbacks from the created
+ * {@link SpeechRecognizer}, this must not be null.
+ */
+ public void setRecognitionListener(RecognitionListener listener) {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
+ }
+
+ /**
+ * Starts listening for speech. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
+ * no notifications will be received.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values will be used by the recognizer.
+ */
+ public void startListening(final Intent recognizerIntent) {
+ if (recognizerIntent == null) {
+ throw new IllegalArgumentException("intent must not be null");
+ }
+ checkIsCalledFromMainThread();
+ if (mConnection == null) { // first time connection
+ mConnection = new Connection();
+
+ Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
+
+ if (mServiceComponent == null) {
+ String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+ if (TextUtils.isEmpty(serviceComponent)) {
+ Log.e(TAG, "no selected voice recognition service");
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
+
+ serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
+ } else {
+ serviceIntent.setComponent(mServiceComponent);
+ }
+
+ if (!mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE)) {
+ Log.e(TAG, "bind to recognition service failed");
+ mConnection = null;
+ mService = null;
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
+ }
+ putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
+ }
+
+ /**
+ * Stops listening for speech. Speech captured so far will be recognized as if the user had
+ * stopped speaking at this point. Note that in the default case, this does not need to be
+ * called, as the speech endpointer will automatically stop the recognizer listening when it
+ * determines speech has completed. However, you can manipulate endpointer parameters directly
+ * using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
+ * want to manually call this method to stop listening sooner. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
+ * no notifications will be received.
+ */
+ public void stopListening() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_STOP));
+ }
+
+ /**
+ * Cancels the speech recognition. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} should be called beforehand, otherwise
+ * no notifications will be received.
+ */
+ public void cancel() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CANCEL));
+ }
+
+ private static void checkIsCalledFromMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new RuntimeException(
+ "SpeechRecognizer should be used only from the application's main thread");
+ }
+ }
+
+ private void putMessage(Message msg) {
+ if (mService == null) {
+ mPendingTasks.offer(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStartListening(Intent recognizerIntent) {
+ if (!checkOpenConnection()) {
+ return;
+ }
+ try {
+ mService.startListening(recognizerIntent, mListener);
+ if (DBG) Log.d(TAG, "service start listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "startListening() failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStopMessage() {
+ if (!checkOpenConnection()) {
+ return;
+ }
+ try {
+ mService.stopListening(mListener);
+ if (DBG) Log.d(TAG, "service stop listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "stopListening() failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleCancelMessage() {
+ if (!checkOpenConnection()) {
+ return;
+ }
+ try {
+ mService.cancel(mListener);
+ if (DBG) Log.d(TAG, "service cancel command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "cancel() failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
+ }
+
+ private boolean checkOpenConnection() {
+ if (mService != null) {
+ return true;
+ }
+ mListener.onError(ERROR_CLIENT);
+ Log.e(TAG, "not connected to the recognition service");
+ return false;
+ }
+
+ /** changes the listener */
+ private void handleChangeListener(RecognitionListener listener) {
+ if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
+ mListener.mInternalListener = listener;
+ }
+
+ /**
+ * Destroys the {@code SpeechRecognizer} object.
+ */
+ public void destroy() {
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ }
+ mPendingTasks.clear();
+ mService = null;
+ mConnection = null;
+ mListener.mInternalListener = null;
+ }
+
+ /**
+ * Internal wrapper of IRecognitionListener which will propagate the results to
+ * RecognitionListener
+ */
+ private class InternalListener extends IRecognitionListener.Stub {
+ private RecognitionListener mInternalListener;
+
+ private final static int MSG_BEGINNING_OF_SPEECH = 1;
+ private final static int MSG_BUFFER_RECEIVED = 2;
+ private final static int MSG_END_OF_SPEECH = 3;
+ private final static int MSG_ERROR = 4;
+ private final static int MSG_READY_FOR_SPEECH = 5;
+ private final static int MSG_RESULTS = 6;
+ private final static int MSG_PARTIAL_RESULTS = 7;
+ private final static int MSG_RMS_CHANGED = 8;
+ private final static int MSG_ON_EVENT = 9;
+
+ private final Handler mInternalHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mInternalListener == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_BEGINNING_OF_SPEECH:
+ mInternalListener.onBeginningOfSpeech();
+ break;
+ case MSG_BUFFER_RECEIVED:
+ mInternalListener.onBufferReceived((byte[]) msg.obj);
+ break;
+ case MSG_END_OF_SPEECH:
+ mInternalListener.onEndOfSpeech();
+ break;
+ case MSG_ERROR:
+ mInternalListener.onError((Integer) msg.obj);
+ break;
+ case MSG_READY_FOR_SPEECH:
+ mInternalListener.onReadyForSpeech((Bundle) msg.obj);
+ break;
+ case MSG_RESULTS:
+ mInternalListener.onResults((Bundle) msg.obj);
+ break;
+ case MSG_PARTIAL_RESULTS:
+ mInternalListener.onPartialResults((Bundle) msg.obj);
+ break;
+ case MSG_RMS_CHANGED:
+ mInternalListener.onRmsChanged((Float) msg.obj);
+ break;
+ case MSG_ON_EVENT:
+ mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
+ break;
+ }
+ }
+ };
+
+ public void onBeginningOfSpeech() {
+ Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
+ }
+
+ public void onBufferReceived(final byte[] buffer) {
+ Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
+ }
+
+ public void onEndOfSpeech() {
+ Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
+ }
+
+ public void onError(final int error) {
+ Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
+ }
+
+ public void onReadyForSpeech(final Bundle noiseParams) {
+ Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
+ }
+
+ public void onResults(final Bundle results) {
+ Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
+ }
+
+ public void onPartialResults(final Bundle results) {
+ Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
+ }
+
+ public void onRmsChanged(final float rmsdB) {
+ Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
+ }
+
+ public void onEvent(final int eventType, final Bundle params) {
+ Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
+ .sendToTarget();
+ }
+ }
+}
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
index 1812188..c1051c4 100755
--- a/core/java/android/speech/tts/ITts.aidl
+++ b/core/java/android/speech/tts/ITts.aidl
@@ -43,7 +43,7 @@ interface ITts {
String[] getLanguage();
- int isLanguageAvailable(in String language, in String country, in String variant);
+ 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);
@@ -59,5 +59,11 @@ interface ITts {
int unregisterCallback(in String callingApp, ITtsCallback cb);
- int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
+ 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/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 3f369dd..26c167e 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -129,8 +129,8 @@ public class TextToSpeech {
* {@link TextToSpeech#synthesizeToFile(String, HashMap, String)} with the
* {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID} key.
* @param utteranceId the identifier of the utterance.
- */
- public void onUtteranceCompleted(String utteranceId);
+ */
+ public void onUtteranceCompleted(String utteranceId);
}
@@ -256,6 +256,28 @@ public class TextToSpeech {
* 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.
+ * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
+ * 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.
+ * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
+ * 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
+ * check for by sending an ArrayList<String> of the voices that are of interest.
+ * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
+ * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
+ */
+ public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
// extras for a TTS engine's data installation
/**
@@ -286,6 +308,14 @@ public class TextToSpeech {
*/
public static final String KEY_PARAM_VARIANT = "variant";
/**
+ * {@hide}
+ */
+ public static final String KEY_PARAM_ENGINE = "engine";
+ /**
+ * {@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.
* @see TextToSpeech#speak(String, int, HashMap)
@@ -327,10 +357,21 @@ public class TextToSpeech {
* {@hide}
*/
protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
+
+ /**
+ * {@hide}
+ */
+ protected static final int PARAM_POSITION_ENGINE = 12;
+
/**
* {@hide}
*/
- protected static final int NB_CACHED_PARAMS = 6;
+ protected static final int PARAM_POSITION_PITCH = 14;
+
+ /**
+ * {@hide}
+ */
+ protected static final int NB_CACHED_PARAMS = 8;
}
/**
@@ -373,18 +414,21 @@ public class TextToSpeech {
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_RATE + 1] =
- String.valueOf(Engine.DEFAULT_RATE);
- // initialize the language cached parameters with the current Locale
- Locale defaultLoc = Locale.getDefault();
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = defaultLoc.getISO3Language();
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = defaultLoc.getISO3Country();
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = defaultLoc.getVariant();
-
+ mCachedParams[Engine.PARAM_POSITION_ENGINE] = Engine.KEY_PARAM_ENGINE;
+ mCachedParams[Engine.PARAM_POSITION_PITCH] = Engine.KEY_PARAM_PITCH;
+
+ // 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";
initTts();
}
@@ -399,6 +443,9 @@ public class TextToSpeech {
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);
@@ -684,6 +731,10 @@ public class TextToSpeech {
if (extra != null) {
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
}
+ extra = params.get(Engine.KEY_PARAM_ENGINE);
+ if (extra != null) {
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = extra;
+ }
}
result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
} catch (RemoteException e) {
@@ -819,7 +870,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -894,7 +945,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -943,7 +994,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -968,15 +1019,13 @@ public class TextToSpeech {
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) {
- result = mITts.setPitch(mPackageName, (int)(pitch*100));
+ int p = (int)(pitch*100);
+ mCachedParams[Engine.PARAM_POSITION_PITCH + 1] = String.valueOf(p);
+ result = SUCCESS;
}
- } catch (RemoteException e) {
- // TTS died; restart it.
- Log.e("TextToSpeech.java - setPitch", "RemoteException");
- e.printStackTrace();
- mStarted = false;
- initTts();
} catch (NullPointerException e) {
// TTS died; restart it.
Log.e("TextToSpeech.java - setPitch", "NullPointerException");
@@ -990,7 +1039,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1017,16 +1066,27 @@ public class TextToSpeech {
return result;
}
try {
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = loc.getISO3Language();
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = loc.getISO3Country();
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = loc.getVariant();
- // the language is not set here, instead it is cached so it will be associated
- // with all upcoming utterances. But we still need to report the language support,
- // which is achieved by calling isLanguageAvailable()
- result = mITts.isLanguageAvailable(
- mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
- mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
- mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] );
+ String language = loc.getISO3Language();
+ String country = loc.getISO3Country();
+ String variant = loc.getVariant();
+ // Check if the language, country, variant are available, and cache
+ // the available parts.
+ // Note that the language is not actually set here, instead it is cached so it
+ // will be associated with all upcoming utterances.
+ result = mITts.isLanguageAvailable(language, country, variant, mCachedParams);
+ 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] = "";
+ }
+ }
} catch (RemoteException e) {
// TTS died; restart it.
Log.e("TextToSpeech.java - setLanguage", "RemoteException");
@@ -1046,7 +1106,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1064,11 +1124,18 @@ public class TextToSpeech {
return null;
}
try {
- String[] locStrings = mITts.getLanguage();
- if ((locStrings != null) && (locStrings.length == 3)) {
- return new Locale(locStrings[0], locStrings[1], locStrings[2]);
+ // 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 null;
+ return new Locale(mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
+ mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
+ mCachedParams[Engine.PARAM_POSITION_VARIANT + 1]);
}
} catch (RemoteException e) {
// TTS died; restart it.
@@ -1111,7 +1178,7 @@ public class TextToSpeech {
}
try {
result = mITts.isLanguageAvailable(loc.getISO3Language(),
- loc.getISO3Country(), loc.getVariant());
+ loc.getISO3Country(), loc.getVariant(), mCachedParams);
} catch (RemoteException e) {
// TTS died; restart it.
Log.e("TextToSpeech.java - isLanguageAvailable", "RemoteException");
@@ -1131,7 +1198,7 @@ public class TextToSpeech {
mStarted = false;
initTts();
} finally {
- return result;
+ return result;
}
}
}
@@ -1166,10 +1233,13 @@ public class TextToSpeech {
if (extra != null) {
mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
}
+ extra = params.get(Engine.KEY_PARAM_ENGINE);
+ if (extra != null) {
+ mCachedParams[Engine.PARAM_POSITION_ENGINE + 1] = extra;
+ }
}
- if (mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename)){
- result = SUCCESS;
- }
+ result = mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename) ?
+ SUCCESS : ERROR;
} catch (RemoteException e) {
// TTS died; restart it.
Log.e("TextToSpeech.java - synthesizeToFile", "RemoteException");
@@ -1214,19 +1284,19 @@ public class TextToSpeech {
*
* @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
*/
- public int setOnUtteranceCompletedListener(
- final OnUtteranceCompletedListener listener) {
+ 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 void utteranceCompleted(String utteranceId) throws RemoteException {
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
+ }
};
try {
result = mITts.registerCallback(mPackageName, mITtscallback);
@@ -1251,7 +1321,126 @@ public class TextToSpeech {
} finally {
return result;
}
- }
+ }
}
+ /**
+ * Sets the speech synthesis engine to be used by its packagename.
+ *
+ * @param enginePackageName
+ * The packagename for the synthesis engine (ie, "com.svox.pico")
+ *
+ * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
+ */
+ 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;
+ }
+ }
+ }
+
+
+ /**
+ * Gets the packagename of the default speech synthesis engine.
+ *
+ * @return Packagename of the TTS engine that the user has chosen as their default.
+ */
+ public String getDefaultEngine() {
+ synchronized (mStartLock) {
+ String engineName = "";
+ if (!mStarted) {
+ return engineName;
+ }
+ 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;
+ }
+ }
+ }
+
+
+ /**
+ * Returns whether or not the user is forcing their defaults to override the
+ * Text-To-Speech settings set by applications.
+ *
+ * @return Whether or not defaults are enforced.
+ */
+ 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;
+ }
+ }
+ }
}
diff --git a/core/java/android/test/InstrumentationTestCase.java b/core/java/android/test/InstrumentationTestCase.java
index 22d95d1..cd33d8a 100644
--- a/core/java/android/test/InstrumentationTestCase.java
+++ b/core/java/android/test/InstrumentationTestCase.java
@@ -16,8 +16,6 @@
package android.test;
-import junit.framework.TestCase;
-
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
@@ -26,9 +24,11 @@ import android.util.Log;
import android.view.KeyEvent;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.lang.reflect.InvocationTargetException;
+
+import junit.framework.TestCase;
/**
* A test case that has access to {@link Instrumentation}.
@@ -86,7 +86,6 @@ public class InstrumentationTestCase extends TestCase {
* @param extras Optional extra stuff to pass to the activity.
* @return The activity, or null if non launched.
*/
- @SuppressWarnings("unchecked")
public final <T extends Activity> T launchActivity(
String pkg,
Class<T> activityCls,
@@ -338,6 +337,7 @@ public class InstrumentationTestCase extends TestCase {
*
* @throws Exception
*/
+ @Override
protected void tearDown() throws Exception {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
new file mode 100644
index 0000000..e4f934e
--- /dev/null
+++ b/core/java/android/text/AndroidBidi.java
@@ -0,0 +1,48 @@
+/*
+ * 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.text;
+
+/**
+ * Access the ICU bidi implementation.
+ * @hide
+ */
+/* package */ class AndroidBidi {
+
+ public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+ if (chs == null || chInfo == null) {
+ throw new NullPointerException();
+ }
+
+ if (n < 0 || chs.length < n || chInfo.length < n) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ switch(dir) {
+ case Layout.DIR_REQUEST_LTR: dir = 0; break;
+ case Layout.DIR_REQUEST_RTL: dir = 1; break;
+ case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
+ case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
+ default: dir = 0; break;
+ }
+
+ int result = runBidi(dir, chs, chInfo, n, haveInfo);
+ result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
+ return result;
+ }
+
+ private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
+} \ No newline at end of file
diff --git a/core/java/android/text/AndroidCharacter.java b/core/java/android/text/AndroidCharacter.java
index 6dfd64d..05887c5 100644
--- a/core/java/android/text/AndroidCharacter.java
+++ b/core/java/android/text/AndroidCharacter.java
@@ -22,6 +22,13 @@ package android.text;
*/
public class AndroidCharacter
{
+ public static final int EAST_ASIAN_WIDTH_NEUTRAL = 0;
+ public static final int EAST_ASIAN_WIDTH_AMBIGUOUS = 1;
+ public static final int EAST_ASIAN_WIDTH_HALF_WIDTH = 2;
+ public static final int EAST_ASIAN_WIDTH_FULL_WIDTH = 3;
+ public static final int EAST_ASIAN_WIDTH_NARROW = 4;
+ public static final int EAST_ASIAN_WIDTH_WIDE = 5;
+
/**
* Fill in the first <code>count</code> bytes of <code>dest</code> with the
* directionalities from the first <code>count</code> chars of <code>src</code>.
@@ -30,10 +37,47 @@ public class AndroidCharacter
*/
public native static void getDirectionalities(char[] src, byte[] dest,
int count);
+
+ /**
+ * Calculate the East Asian Width of a character according to
+ * <a href="http://unicode.org/reports/tr11/">Unicode TR#11</a>. The return
+ * will be one of {@link #EAST_ASIAN_WIDTH_NEUTRAL},
+ * {@link #EAST_ASIAN_WIDTH_AMBIGUOUS}, {@link #EAST_ASIAN_WIDTH_HALF_WIDTH},
+ * {@link #EAST_ASIAN_WIDTH_FULL_WIDTH}, {@link #EAST_ASIAN_WIDTH_NARROW},
+ * or {@link #EAST_ASIAN_WIDTH_WIDE}.
+ *
+ * @param input the character to measure
+ * @return the East Asian Width for input
+ */
+ public native static int getEastAsianWidth(char input);
+
+ /**
+ * Fill the first <code>count</code> bytes of <code>dest</code> with the
+ * East Asian Width from the first <code>count</code> chars of
+ * <code>src</code>. East Asian Width is calculated based on
+ * <a href="http://unicode.org/reports/tr11/">Unicode TR#11</a>. Each entry
+ * in <code>dest> will be one of {@link #EAST_ASIAN_WIDTH_NEUTRAL},
+ * {@link #EAST_ASIAN_WIDTH_AMBIGUOUS}, {@link #EAST_ASIAN_WIDTH_HALF_WIDTH},
+ * {@link #EAST_ASIAN_WIDTH_FULL_WIDTH}, {@link #EAST_ASIAN_WIDTH_NARROW},
+ * or {@link #EAST_ASIAN_WIDTH_WIDE}.
+ *
+ * @param src character array of input to measure
+ * @param start first character in array to measure
+ * @param count maximum number of characters to measure
+ * @param dest byte array of results for each character in src
+ */
+ public native static void getEastAsianWidths(char[] src, int start,
+ int count, byte[] dest);
+
/**
* Replace the specified slice of <code>text</code> with the chars'
* right-to-left mirrors (if any), returning true if any
* replacements were made.
+ *
+ * @param text array of characters to apply mirror operation
+ * @param start first character in array to mirror
+ * @param count maximum number of characters to mirror
+ * @return true if replacements were made
*/
public native static boolean mirror(char[] text, int start, int count);
diff --git a/core/java/android/text/AutoText.java b/core/java/android/text/AutoText.java
index 2fc906a..04730ec 100644
--- a/core/java/android/text/AutoText.java
+++ b/core/java/android/text/AutoText.java
@@ -18,7 +18,9 @@ package android.text;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+
import com.android.internal.util.XmlUtils;
+
import android.view.View;
import org.xmlpull.v1.XmlPullParser;
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 380e5fd..07e71f9 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -46,6 +46,7 @@ import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;
+
import com.android.internal.util.XmlUtils;
import java.io.IOException;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index a92800d..38ac9b7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -39,6 +39,8 @@ import android.view.KeyEvent;
*/
public abstract class Layout {
private static final boolean DEBUG = false;
+ private static final ParagraphStyle[] NO_PARA_SPANS =
+ ArrayUtils.emptyArray(ParagraphStyle.class);
/* package */ static final EmojiFactory EMOJI_FACTORY =
EmojiFactory.newAvailableInstance();
@@ -57,7 +59,7 @@ public abstract class Layout {
private RectF mEmojiRect;
/**
- * Return how wide a layout would be necessary to display the
+ * Return how wide a layout must be in order to display the
* specified text with one line per paragraph.
*/
public static float getDesiredWidth(CharSequence source,
@@ -66,7 +68,7 @@ public abstract class Layout {
}
/**
- * Return how wide a layout would be necessary to display the
+ * Return how wide a layout must be in order to display the
* specified text slice with one line per paragraph.
*/
public static float getDesiredWidth(CharSequence source,
@@ -82,6 +84,7 @@ public abstract class Layout {
if (next < 0)
next = end;
+ // note, omits trailing paragraph char
float w = measureText(paint, workPaint,
source, i, next, null, true, null);
@@ -97,10 +100,19 @@ public abstract class Layout {
/**
* Subclasses of Layout use this constructor to set the display text,
* width, and other standard properties.
+ * @param text the text to render
+ * @param paint the default paint for the layout. Styles can override
+ * various attributes of the paint.
+ * @param width the wrapping width for the text.
+ * @param align whether to left, right, or center the text. Styles can
+ * override the alignment.
+ * @param spacingMult factor by which to scale the font size to get the
+ * default line spacing
+ * @param spacingAdd amount to add to the default line spacing
*/
protected Layout(CharSequence text, TextPaint paint,
int width, Alignment align,
- float spacingmult, float spacingadd) {
+ float spacingMult, float spacingAdd) {
if (width < 0)
throw new IllegalArgumentException("Layout: " + width + " < 0");
@@ -109,8 +121,8 @@ public abstract class Layout {
mWorkPaint = new TextPaint();
mWidth = width;
mAlignment = align;
- mSpacingMult = spacingmult;
- mSpacingAdd = spacingadd;
+ mSpacingMult = spacingMult;
+ mSpacingAdd = spacingAdd;
mSpannedText = text instanceof Spanned;
}
@@ -141,10 +153,16 @@ public abstract class Layout {
}
/**
- * Draw the specified rectangle from this Layout on the specified Canvas,
- * with the specified path drawn between the background and the text.
+ * Draw this Layout on the specified canvas, with the highlight path drawn
+ * between the background and the text.
+ *
+ * @param c the canvas
+ * @param highlight the path of the highlight or cursor; can be null
+ * @param highlightPaint the paint for the highlight
+ * @param cursorOffsetVertical the amount to temporarily translate the
+ * canvas while rendering the highlight
*/
- public void draw(Canvas c, Path highlight, Paint highlightpaint,
+ public void draw(Canvas c, Path highlight, Paint highlightPaint,
int cursorOffsetVertical) {
int dtop, dbottom;
@@ -157,13 +175,10 @@ public abstract class Layout {
dbottom = sTempRect.bottom;
}
- TextPaint paint = mPaint;
int top = 0;
- // getLineBottom(getLineCount() -1) just calls getLineTop(getLineCount)
int bottom = getLineTop(getLineCount());
-
if (dtop > top) {
top = dtop;
}
@@ -177,16 +192,19 @@ public abstract class Layout {
int previousLineBottom = getLineTop(first);
int previousLineEnd = getLineStart(first);
+ TextPaint paint = mPaint;
CharSequence buf = mText;
+ int width = mWidth;
+ boolean spannedText = mSpannedText;
- ParagraphStyle[] nospans = ArrayUtils.emptyArray(ParagraphStyle.class);
- ParagraphStyle[] spans = nospans;
+ ParagraphStyle[] spans = NO_PARA_SPANS;
int spanend = 0;
int textLength = 0;
- boolean spannedText = mSpannedText;
+ // First, draw LineBackgroundSpans.
+ // LineBackgroundSpans know nothing about the alignment or direction of
+ // the layout or line. XXX: Should they?
if (spannedText) {
- spanend = 0;
textLength = buf.length();
for (int i = first; i <= last; i++) {
int start = previousLineEnd;
@@ -209,7 +227,7 @@ public abstract class Layout {
for (int n = 0; n < spans.length; n++) {
LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
- back.drawBackground(c, paint, 0, mWidth,
+ back.drawBackground(c, paint, 0, width,
ltop, lbaseline, lbottom,
buf, start, end,
i);
@@ -219,7 +237,7 @@ public abstract class Layout {
spanend = 0;
previousLineBottom = getLineTop(first);
previousLineEnd = getLineStart(first);
- spans = nospans;
+ spans = NO_PARA_SPANS;
}
// There can be a highlight even without spans if we are drawing
@@ -229,7 +247,7 @@ public abstract class Layout {
c.translate(0, cursorOffsetVertical);
}
- c.drawPath(highlight, highlightpaint);
+ c.drawPath(highlight, highlightPaint);
if (cursorOffsetVertical != 0) {
c.translate(0, -cursorOffsetVertical);
@@ -238,6 +256,9 @@ public abstract class Layout {
Alignment align = mAlignment;
+ // Next draw the lines, one at a time.
+ // the baseline is the top of the following line minus the current
+ // line's descent.
for (int i = first; i <= last; i++) {
int start = previousLineEnd;
@@ -249,21 +270,20 @@ public abstract class Layout {
previousLineBottom = lbottom;
int lbaseline = lbottom - getLineDescent(i);
- boolean par = false;
+ boolean isFirstParaLine = false;
if (spannedText) {
if (start == 0 || buf.charAt(start - 1) == '\n') {
- par = true;
+ isFirstParaLine = true;
}
+ // New batch of paragraph styles, compute the alignment.
+ // Last alignment style wins.
if (start >= spanend) {
-
Spanned sp = (Spanned) buf;
-
spanend = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
spans = sp.getSpans(start, spanend, ParagraphStyle.class);
align = mAlignment;
-
for (int n = spans.length-1; n >= 0; n--) {
if (spans[n] instanceof AlignmentSpan) {
align = ((AlignmentSpan) spans[n]).getAlignment();
@@ -277,6 +297,8 @@ public abstract class Layout {
int left = 0;
int right = mWidth;
+ // Draw all leading margin spans. Adjust left or right according
+ // to the paragraph direction of the line.
if (spannedText) {
final int length = spans.length;
for (int n = 0; n < length; n++) {
@@ -286,20 +308,27 @@ public abstract class Layout {
if (dir == DIR_RIGHT_TO_LEFT) {
margin.drawLeadingMargin(c, paint, right, dir, ltop,
lbaseline, lbottom, buf,
- start, end, par, this);
+ start, end, isFirstParaLine, this);
- right -= margin.getLeadingMargin(par);
+ right -= margin.getLeadingMargin(isFirstParaLine);
} else {
margin.drawLeadingMargin(c, paint, left, dir, ltop,
lbaseline, lbottom, buf,
- start, end, par, this);
+ start, end, isFirstParaLine, this);
- left += margin.getLeadingMargin(par);
+ boolean useMargin = isFirstParaLine;
+ if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
+ useMargin = count > i;
+ }
+ left += margin.getLeadingMargin(useMargin);
}
}
}
}
+ // Adjust the point at which to start rendering depending on the
+ // alignment of the paragraph.
int x;
if (align == Alignment.ALIGN_NORMAL) {
if (dir == DIR_LEFT_TO_RIGHT) {
@@ -335,6 +364,7 @@ public abstract class Layout {
Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
Assert.assertNotNull(c);
}
+ // XXX: assumes there's nothing additional to be done
c.drawText(buf, start, end, x, lbaseline, paint);
} else {
drawText(c, buf, start, end, dir, directions,
@@ -377,7 +407,7 @@ public abstract class Layout {
/**
* Increase the width of this layout to the specified width.
- * Be careful to use this only when you know it is appropriate --
+ * Be careful to use this only when you know it is appropriate&mdash;
* it does not cause the text to reflow to use the full new width.
*/
public final void increaseWidthTo(int wid) {
@@ -392,7 +422,7 @@ public abstract class Layout {
* Return the total height of this layout.
*/
public int getHeight() {
- return getLineTop(getLineCount()); // same as getLineBottom(getLineCount() - 1);
+ return getLineTop(getLineCount());
}
/**
@@ -434,33 +464,35 @@ public abstract class Layout {
bounds.left = 0; // ???
bounds.top = getLineTop(line);
bounds.right = mWidth; // ???
- bounds.bottom = getLineBottom(line);
+ bounds.bottom = getLineTop(line + 1);
}
return getLineBaseline(line);
}
/**
- * Return the vertical position of the top of the specified line.
- * If the specified line is one beyond the last line, returns the
+ * Return the vertical position of the top of the specified line
+ * (0&hellip;getLineCount()).
+ * If the specified line is equal to the line count, returns the
* bottom of the last line.
*/
public abstract int getLineTop(int line);
/**
- * Return the descent of the specified line.
+ * Return the descent of the specified line(0&hellip;getLineCount() - 1).
*/
public abstract int getLineDescent(int line);
/**
- * Return the text offset of the beginning of the specified line.
- * If the specified line is one beyond the last line, returns the
- * end of the last line.
+ * Return the text offset of the beginning of the specified line (
+ * 0&hellip;getLineCount()). If the specified line is equal to the line
+ * count, returns the length of the text.
*/
public abstract int getLineStart(int line);
/**
- * Returns the primary directionality of the paragraph containing
- * the specified line.
+ * Returns the primary directionality of the paragraph containing the
+ * specified line, either 1 for left-to-right lines, or -1 for right-to-left
+ * lines (see {@link #DIR_LEFT_TO_RIGHT}, {@link #DIR_RIGHT_TO_LEFT}).
*/
public abstract int getParagraphDirection(int line);
@@ -472,9 +504,11 @@ public abstract class Layout {
public abstract boolean getLineContainsTab(int line);
/**
- * Returns an array of directionalities for the specified line.
+ * Returns the directional run information for the specified line.
* The array alternates counts of characters in left-to-right
* and right-to-left segments of the line.
+ *
+ * <p>NOTE: this is inadequate to support bidirectional text, and will change.
*/
public abstract Directions getLineDirections(int line);
@@ -1293,7 +1327,13 @@ public abstract class Layout {
LeadingMarginSpan.class);
for (int i = 0; i < spans.length; i++) {
- left += spans[i].getLeadingMargin(par);
+ boolean margin = par;
+ LeadingMarginSpan span = spans[i];
+ if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
+ margin = count >= line;
+ }
+ left += span.getLeadingMargin(margin);
}
}
}
@@ -1554,6 +1594,21 @@ public abstract class Layout {
return h;
}
+ /**
+ * Measure width of a run of text on a single line that is known to all be
+ * in the same direction as the paragraph base direction. Returns the width,
+ * and the line metrics in fm if fm is not null.
+ *
+ * @param paint the paint for the text; will not be modified
+ * @param workPaint paint available for modification
+ * @param text text
+ * @param start start of the line
+ * @param end limit of the line
+ * @param fm object to return integer metrics in, can be null
+ * @param hasTabs true if it is known that the line has tabs
+ * @param tabs tab position information
+ * @return the width of the text from start to end
+ */
/* package */ static float measureText(TextPaint paint,
TextPaint workPaint,
CharSequence text,
@@ -1569,37 +1624,36 @@ public abstract class Layout {
int len = end - start;
- int here = 0;
- float h = 0;
- int ab = 0, be = 0;
- int top = 0, bot = 0;
+ int lastPos = 0;
+ float width = 0;
+ int ascent = 0, descent = 0, top = 0, bottom = 0;
if (fm != null) {
fm.ascent = 0;
fm.descent = 0;
}
- for (int i = hasTabs ? 0 : len; i <= len; i++) {
+ for (int pos = hasTabs ? 0 : len; pos <= len; pos++) {
int codept = 0;
Bitmap bm = null;
- if (hasTabs && i < len) {
- codept = buf[i];
+ if (hasTabs && pos < len) {
+ codept = buf[pos];
}
- if (codept >= 0xD800 && codept <= 0xDFFF && i < len) {
- codept = Character.codePointAt(buf, i);
+ if (codept >= 0xD800 && codept <= 0xDFFF && pos < len) {
+ codept = Character.codePointAt(buf, pos);
if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) {
bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept);
}
}
- if (i == len || codept == '\t' || bm != null) {
+ if (pos == len || codept == '\t' || bm != null) {
workPaint.baselineShift = 0;
- h += Styled.measureText(paint, workPaint, text,
- start + here, start + i,
+ width += Styled.measureText(paint, workPaint, text,
+ start + lastPos, start + pos,
fm);
if (fm != null) {
@@ -1612,60 +1666,80 @@ public abstract class Layout {
}
}
- if (i != len) {
+ if (pos != len) {
if (bm == null) {
- h = nextTab(text, start, end, h, tabs);
+ // no emoji, must have hit a tab
+ width = nextTab(text, start, end, width, tabs);
} else {
+ // This sets up workPaint with the font on the emoji
+ // text, so that we can extract the ascent and scale.
+
+ // We can't use the result of the previous call to
+ // measureText because the emoji might have its own style.
+ // We have to initialize workPaint here because if the
+ // text is unstyled measureText might not use workPaint
+ // at all.
workPaint.set(paint);
Styled.measureText(paint, workPaint, text,
- start + i, start + i + 1, null);
+ start + pos, start + pos + 1, null);
- float wid = (float) bm.getWidth() *
+ width += (float) bm.getWidth() *
-workPaint.ascent() / bm.getHeight();
- h += wid;
- i++;
+ // Since we had an emoji, we bump past the second half
+ // of the surrogate pair.
+ pos++;
}
}
if (fm != null) {
- if (fm.ascent < ab) {
- ab = fm.ascent;
+ if (fm.ascent < ascent) {
+ ascent = fm.ascent;
}
- if (fm.descent > be) {
- be = fm.descent;
+ if (fm.descent > descent) {
+ descent = fm.descent;
}
if (fm.top < top) {
top = fm.top;
}
- if (fm.bottom > bot) {
- bot = fm.bottom;
+ if (fm.bottom > bottom) {
+ bottom = fm.bottom;
}
- /*
- * No need to take bitmap height into account here,
- * since it is scaled to match the text height.
- */
+ // No need to take bitmap height into account here,
+ // since it is scaled to match the text height.
}
- here = i + 1;
+ lastPos = pos + 1;
}
}
if (fm != null) {
- fm.ascent = ab;
- fm.descent = be;
+ fm.ascent = ascent;
+ fm.descent = descent;
fm.top = top;
- fm.bottom = bot;
+ fm.bottom = bottom;
}
if (hasTabs)
TextUtils.recycle(buf);
- return h;
+ return width;
}
+ /**
+ * Returns the position of the next tab stop after h on the line.
+ *
+ * @param text the text
+ * @param start start of the line
+ * @param end limit of the line
+ * @param h the current horizontal offset
+ * @param tabs the tabs, can be null. If it is null, any tabs in effect
+ * on the line will be used. If there are no tabs, a default offset
+ * will be used to compute the tab stop.
+ * @return the offset of the next tab stop.
+ */
/* package */ static float nextTab(CharSequence text, int start, int end,
float h, Object[] tabs) {
float nh = Float.MAX_VALUE;
@@ -1736,6 +1810,16 @@ public abstract class Layout {
public static class Directions {
private short[] mDirections;
+ // The values in mDirections are the offsets from the first character
+ // in the line to the next flip in direction. Runs at even indices
+ // are left-to-right, the others are right-to-left. So, for example,
+ // a line that starts with a right-to-left run has 0 at mDirections[0],
+ // since the 'first' (ltr) run is zero length.
+ //
+ // The code currently assumes that each run is adjacent to the previous
+ // one, progressing in the base line direction. This isn't sufficient
+ // to handle nested runs, for example numeric text in an rtl context
+ // in an ltr paragraph.
/* package */ Directions(short[] dirs) {
mDirections = dirs;
}
@@ -1852,6 +1936,11 @@ public abstract class Layout {
public static final int DIR_LEFT_TO_RIGHT = 1;
public static final int DIR_RIGHT_TO_LEFT = -1;
+
+ /* package */ static final int DIR_REQUEST_LTR = 1;
+ /* package */ static final int DIR_REQUEST_RTL = -1;
+ /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2;
+ /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2;
public enum Alignment {
ALIGN_NORMAL,
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
index 9045c09..e2d1596 100644
--- a/core/java/android/text/LoginFilter.java
+++ b/core/java/android/text/LoginFilter.java
@@ -158,11 +158,11 @@ public abstract class LoginFilter implements InputFilter {
/**
* This filter rejects characters in the user name that are not compatible with Google login.
- * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-].
+ * It is slightly less restrictive than the above filter in that it allows [a-zA-Z0-9._-+].
*
*/
public static class UsernameFilterGeneric extends LoginFilter {
- private static final String mAllowed = "@_-."; // Additional characters
+ private static final String mAllowed = "@_-+."; // Additional characters
public UsernameFilterGeneric() {
super(false);
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index f0a5ffd..f02ad2a 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -161,6 +161,7 @@ extends Layout
else
end++;
+ int firstWidthLineCount = 1;
int firstwidth = outerwidth;
int restwidth = outerwidth;
@@ -171,8 +172,12 @@ extends Layout
sp = spanned.getSpans(start, end, LeadingMarginSpan.class);
for (int i = 0; i < sp.length; i++) {
+ LeadingMarginSpan lms = sp[i];
firstwidth -= sp[i].getLeadingMargin(true);
restwidth -= sp[i].getLeadingMargin(false);
+ if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
+ firstWidthLineCount = ((LeadingMarginSpan.LeadingMarginSpan2)lms).getLeadingMarginLineCount();
+ }
}
chooseht = spanned.getSpans(start, end, LineHeightSpan.class);
@@ -228,231 +233,27 @@ extends Layout
}
}
- if (!easy) {
- AndroidCharacter.getDirectionalities(chs, chdirs, end - start);
-
- /*
- * Determine primary paragraph direction
- */
-
- for (int j = start; j < end; j++) {
- int d = chdirs[j - start];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
- dir = DIR_LEFT_TO_RIGHT;
- break;
- }
- if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- dir = DIR_RIGHT_TO_LEFT;
- break;
- }
- }
-
- /*
- * XXX Explicit overrides should go here
- */
-
- /*
- * Weak type resolution
- */
-
- final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
- Character.DIRECTIONALITY_LEFT_TO_RIGHT :
- Character.DIRECTIONALITY_RIGHT_TO_LEFT;
-
- // dump(chdirs, n, "initial");
-
- // W1 non spacing marks
- for (int j = 0; j < n; j++) {
- if (chdirs[j] == Character.NON_SPACING_MARK) {
- if (j == 0)
- chdirs[j] = SOR;
- else
- chdirs[j] = chdirs[j - 1];
- }
- }
-
- // dump(chdirs, n, "W1");
-
- // W2 european numbers
- byte cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- cur = d;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
- if (cur ==
- Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W2");
-
- // W3 arabic letters
- for (int j = 0; j < n; j++) {
- if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
- chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- }
-
- // dump(chdirs, n, "W3");
-
- // W4 single separator between numbers
- for (int j = 1; j < n - 1; j++) {
- byte d = chdirs[j];
- byte prev = chdirs[j - 1];
- byte next = chdirs[j + 1];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
- if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
- next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
- next == Character.DIRECTIONALITY_ARABIC_NUMBER)
- chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
- }
- }
-
- // dump(chdirs, n, "W4");
-
- // W5 european number terminators
- boolean adjacent = false;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- adjacent = false;
- }
-
- //dump(chdirs, n, "W5");
-
- // W5 european number terminators part 2,
- // W6 separators and terminators
- adjacent = false;
- for (int j = n - 1; j >= 0; j--) {
- byte d = chdirs[j];
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- adjacent = true;
- else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
- if (adjacent)
- chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
- else
- chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- else {
- adjacent = false;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
- d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
- d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
- chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
- }
- }
-
- // dump(chdirs, n, "W6");
-
- // W7 strong direction of european numbers
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
-
- if (d == SOR ||
- d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
- cur = d;
-
- if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
- chdirs[j] = cur;
- }
-
- // dump(chdirs, n, "W7");
-
- // N1, N2 neutrals
- cur = SOR;
- for (int j = 0; j < n; j++) {
- byte d = chdirs[j];
+ // Ensure that none of the underlying characters are treated
+ // as viable breakpoints, and that the entire run gets the
+ // same bidi direction.
- if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- cur = d;
- } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- } else {
- byte dd = SOR;
- int k;
+ if (source instanceof Spanned) {
+ Spanned sp = (Spanned) source;
+ ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
- for (k = j + 1; k < n; k++) {
- dd = chdirs[k];
+ for (int y = 0; y < spans.length; y++) {
+ int a = sp.getSpanStart(spans[y]);
+ int b = sp.getSpanEnd(spans[y]);
- if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
- dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
- break;
- }
- if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
- dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
- dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
- break;
- }
- }
-
- for (int y = j; y < k; y++) {
- if (dd == cur)
- chdirs[y] = cur;
- else
- chdirs[y] = SOR;
- }
-
- j = k - 1;
+ for (int x = a; x < b; x++) {
+ chs[x - start] = '\uFFFC';
}
}
+ }
- // dump(chdirs, n, "final");
-
- // extra: enforce that all tabs and surrogate characters go the
- // primary direction
- // TODO: actually do directions right for surrogates
-
- for (int j = 0; j < n; j++) {
- char c = chs[j];
-
- if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
- chdirs[j] = SOR;
- }
- }
-
- // extra: enforce that object replacements go to the
- // primary direction
- // and that none of the underlying characters are treated
- // as viable breakpoints
-
- if (source instanceof Spanned) {
- Spanned sp = (Spanned) source;
- ReplacementSpan[] spans = sp.getSpans(start, end, ReplacementSpan.class);
-
- for (int y = 0; y < spans.length; y++) {
- int a = sp.getSpanStart(spans[y]);
- int b = sp.getSpanEnd(spans[y]);
-
- for (int x = a; x < b; x++) {
- chdirs[x - start] = SOR;
- chs[x - start] = '\uFFFC';
- }
- }
- }
+ if (!easy) {
+ // XXX put override flags, etc. into chdirs
+ dir = bidi(dir, chs, chdirs, n, false);
// Do mirroring for right-to-left segments
@@ -750,7 +551,9 @@ extends Layout
fitascent = fitdescent = fittop = fitbottom = 0;
okascent = okdescent = oktop = okbottom = 0;
- width = restwidth;
+ if (--firstWidthLineCount <= 0) {
+ width = restwidth;
+ }
}
}
}
@@ -803,6 +606,239 @@ extends Layout
}
}
+ /**
+ * Runs the unicode bidi algorithm on the first n chars in chs, returning
+ * the char dirs in chInfo and the base line direction of the first
+ * paragraph.
+ *
+ * XXX change result from dirs to levels
+ *
+ * @param dir the direction flag, either DIR_REQUEST_LTR,
+ * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL.
+ * @param chs the text to examine
+ * @param chInfo on input, if hasInfo is true, override and other flags
+ * representing out-of-band embedding information. On output, the generated
+ * dirs of the text.
+ * @param n the length of the text/information in chs and chInfo
+ * @param hasInfo true if chInfo has input information, otherwise the
+ * input data in chInfo is ignored.
+ * @return the resolved direction level of the first paragraph, either
+ * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT.
+ */
+ /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n,
+ boolean hasInfo) {
+
+ AndroidCharacter.getDirectionalities(chs, chInfo, n);
+
+ /*
+ * Determine primary paragraph direction if not specified
+ */
+ if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) {
+ // set up default
+ dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT;
+ for (int j = 0; j < n; j++) {
+ int d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
+ dir = DIR_LEFT_TO_RIGHT;
+ break;
+ }
+ if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ dir = DIR_RIGHT_TO_LEFT;
+ break;
+ }
+ }
+ }
+
+ final byte SOR = dir == DIR_LEFT_TO_RIGHT ?
+ Character.DIRECTIONALITY_LEFT_TO_RIGHT :
+ Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+
+ /*
+ * XXX Explicit overrides should go here
+ */
+
+ /*
+ * Weak type resolution
+ */
+
+ // dump(chdirs, n, "initial");
+
+ // W1 non spacing marks
+ for (int j = 0; j < n; j++) {
+ if (chInfo[j] == Character.NON_SPACING_MARK) {
+ if (j == 0)
+ chInfo[j] = SOR;
+ else
+ chInfo[j] = chInfo[j - 1];
+ }
+ }
+
+ // dump(chdirs, n, "W1");
+
+ // W2 european numbers
+ byte cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ cur = d;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) {
+ if (cur ==
+ Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+ }
+ }
+
+ // dump(chdirs, n, "W2");
+
+ // W3 arabic letters
+ for (int j = 0; j < n; j++) {
+ if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC)
+ chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ }
+
+ // dump(chdirs, n, "W3");
+
+ // W4 single separator between numbers
+ for (int j = 1; j < n - 1; j++) {
+ byte d = chInfo[j];
+ byte prev = chInfo[j - 1];
+ byte next = chInfo[j + 1];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) {
+ if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+ next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) {
+ if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER &&
+ next == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER &&
+ next == Character.DIRECTIONALITY_ARABIC_NUMBER)
+ chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER;
+ }
+ }
+
+ // dump(chdirs, n, "W4");
+
+ // W5 european number terminators
+ boolean adjacent = false;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ adjacent = true;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ else
+ adjacent = false;
+ }
+
+ //dump(chdirs, n, "W5");
+
+ // W5 european number terminators part 2,
+ // W6 separators and terminators
+ adjacent = false;
+ for (int j = n - 1; j >= 0; j--) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ adjacent = true;
+ else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) {
+ if (adjacent)
+ chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER;
+ else
+ chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ else {
+ adjacent = false;
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR ||
+ d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR ||
+ d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR ||
+ d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR)
+ chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ }
+
+ // dump(chdirs, n, "W6");
+
+ // W7 strong direction of european numbers
+ cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == SOR ||
+ d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT)
+ cur = d;
+
+ if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER)
+ chInfo[j] = cur;
+ }
+
+ // dump(chdirs, n, "W7");
+
+ // N1, N2 neutrals
+ cur = SOR;
+ for (int j = 0; j < n; j++) {
+ byte d = chInfo[j];
+
+ if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ cur = d;
+ } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+ d == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+ cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ } else {
+ byte dd = SOR;
+ int k;
+
+ for (k = j + 1; k < n; k++) {
+ dd = chInfo[k];
+
+ if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT ||
+ dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) {
+ break;
+ }
+ if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER ||
+ dd == Character.DIRECTIONALITY_ARABIC_NUMBER) {
+ dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT;
+ break;
+ }
+ }
+
+ for (int y = j; y < k; y++) {
+ if (dd == cur)
+ chInfo[y] = cur;
+ else
+ chInfo[y] = SOR;
+ }
+
+ j = k - 1;
+ }
+ }
+
+ // dump(chdirs, n, "final");
+
+ // extra: enforce that all tabs and surrogate characters go the
+ // primary direction
+ // TODO: actually do directions right for surrogates
+
+ for (int j = 0; j < n; j++) {
+ char c = chs[j];
+
+ if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) {
+ chInfo[j] = SOR;
+ }
+ }
+
+ return dir;
+ }
+
private static final char FIRST_CJK = '\u2E80';
/**
* Returns true if the specified character is one of those specified
@@ -1005,8 +1041,12 @@ extends Layout
int extra;
if (needMultiply) {
- extra = (int) ((below - above) * (spacingmult - 1)
- + spacingadd + 0.5);
+ double ex = (below - above) * (spacingmult - 1) + spacingadd;
+ if (ex >= 0) {
+ extra = (int)(ex + 0.5);
+ } else {
+ extra = -(int)(-ex + 0.5);
+ }
} else {
extra = 0;
}
diff --git a/core/java/android/text/Styled.java b/core/java/android/text/Styled.java
index 0aa2004..513b2cd 100644
--- a/core/java/android/text/Styled.java
+++ b/core/java/android/text/Styled.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.text;
import android.graphics.Canvas;
@@ -23,27 +22,49 @@ import android.text.style.MetricAffectingSpan;
import android.text.style.ReplacementSpan;
/**
- * This class provides static methods for drawing and measuring styled texts, like
- * {@link android.text.Spanned} object with {@link android.text.style.ReplacementSpan}.
+ * This class provides static methods for drawing and measuring styled text,
+ * like {@link android.text.Spanned} object with
+ * {@link android.text.style.ReplacementSpan}.
+ *
* @hide
*/
public class Styled
{
- private static float each(Canvas canvas,
+ /**
+ * Draws and/or measures a uniform run of text on a single line. No span of
+ * interest should start or end in the middle of this run (if not
+ * drawing, character spans that don't affect metrics can be ignored).
+ * Neither should the run direction change in the middle of the run.
+ *
+ * <p>The x position is the leading edge of the text. In a right-to-left
+ * paragraph, this will be to the right of the text to be drawn. Paint
+ * should not have an Align value other than LEFT or positioning will get
+ * confused.
+ *
+ * <p>On return, workPaint will reflect the original paint plus any
+ * modifications made by character styles on the run.
+ *
+ * <p>The returned width is signed and will be < 0 if the paragraph
+ * direction is right-to-left.
+ */
+ private static float drawUniformRun(Canvas canvas,
Spanned text, int start, int end,
- int dir, boolean reverse,
+ int dir, boolean runIsRtl,
float x, int top, int y, int bottom,
Paint.FontMetricsInt fmi,
TextPaint paint,
TextPaint workPaint,
- boolean needwid) {
+ boolean needWidth) {
- boolean havewid = false;
+ boolean haveWidth = false;
float ret = 0;
CharacterStyle[] spans = text.getSpans(start, end, CharacterStyle.class);
ReplacementSpan replacement = null;
+ // XXX: This shouldn't be modifying paint, only workPaint.
+ // However, the members belonging to TextPaint should have default
+ // values anyway. Better to ensure this in the Layout constructor.
paint.bgColor = 0;
paint.baselineShift = 0;
workPaint.set(paint);
@@ -65,9 +86,10 @@ public class Styled
CharSequence tmp;
int tmpstart, tmpend;
- if (reverse) {
+ if (runIsRtl) {
tmp = TextUtils.getReverse(text, start, end);
tmpstart = 0;
+ // XXX: assumes getReverse doesn't change the length of the text
tmpend = end - start;
} else {
tmp = text;
@@ -86,9 +108,9 @@ public class Styled
workPaint.setColor(workPaint.bgColor);
workPaint.setStyle(Paint.Style.FILL);
- if (!havewid) {
+ if (!haveWidth) {
ret = workPaint.measureText(tmp, tmpstart, tmpend);
- havewid = true;
+ haveWidth = true;
}
if (dir == Layout.DIR_RIGHT_TO_LEFT)
@@ -101,18 +123,18 @@ public class Styled
}
if (dir == Layout.DIR_RIGHT_TO_LEFT) {
- if (!havewid) {
+ if (!haveWidth) {
ret = workPaint.measureText(tmp, tmpstart, tmpend);
- havewid = true;
+ haveWidth = true;
}
canvas.drawText(tmp, tmpstart, tmpend,
x - ret, y + workPaint.baselineShift, workPaint);
} else {
- if (needwid) {
- if (!havewid) {
+ if (needWidth) {
+ if (!haveWidth) {
ret = workPaint.measureText(tmp, tmpstart, tmpend);
- havewid = true;
+ haveWidth = true;
}
}
@@ -120,9 +142,9 @@ public class Styled
x, y + workPaint.baselineShift, workPaint);
}
} else {
- if (needwid && !havewid) {
+ if (needWidth && !haveWidth) {
ret = workPaint.measureText(tmp, tmpstart, tmpend);
- havewid = true;
+ haveWidth = true;
}
}
} else {
@@ -145,25 +167,28 @@ public class Styled
}
/**
- * Return the advance widths for the characters in the string.
- * See also {@link android.graphics.Paint#getTextWidths(CharSequence, int, int, float[])}.
+ * Returns the advance widths for a uniform left-to-right run of text with
+ * no style changes in the middle of the run. If any style is replacement
+ * text, the first character will get the width of the replacement and the
+ * remaining characters will get a width of 0.
*
- * @param paint The main {@link TextPaint} object.
- * @param workPaint The {@link TextPaint} object used for temporal workspace.
- * @param text The text to measure
- * @param start The index of the first char to to measure
- * @param end The end of the text slice to measure
- * @param widths Array to receive the advance widths of the characters.
- * Must be at least a large as (end - start).
- * @param fmi FontMetrics information. Can be null.
- * @return The actual number of widths returned.
+ * @param paint the paint, will not be modified
+ * @param workPaint a paint to modify; on return will reflect the original
+ * paint plus the effect of all spans on the run
+ * @param text the text
+ * @param start the start of the run
+ * @param end the limit of the run
+ * @param widths array to receive the advance widths of the characters. Must
+ * be at least a large as (end - start).
+ * @param fmi FontMetrics information; can be null
+ * @return the actual number of widths returned
*/
public static int getTextWidths(TextPaint paint,
TextPaint workPaint,
Spanned text, int start, int end,
float[] widths, Paint.FontMetricsInt fmi) {
- // Keep workPaint as is so that developers reuse the workspace.
- MetricAffectingSpan[] spans = text.getSpans(start, end, MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans =
+ text.getSpans(start, end, MetricAffectingSpan.class);
ReplacementSpan replacement = null;
workPaint.set(paint);
@@ -186,7 +211,6 @@ public class Styled
if (end > start) {
widths[0] = wid;
-
for (int i = start + 1; i < end; i++)
widths[i - start] = 0;
}
@@ -194,19 +218,42 @@ public class Styled
return end - start;
}
- private static float foreach(Canvas canvas,
+ /**
+ * Renders and/or measures a directional run of text on a single line.
+ * Unlike {@link #drawUniformRun}, this can render runs that cross style
+ * boundaries. Returns the signed advance width, if requested.
+ *
+ * <p>The x position is the leading edge of the text. In a right-to-left
+ * paragraph, this will be to the right of the text to be drawn. Paint
+ * should not have an Align value other than LEFT or positioning will get
+ * confused.
+ *
+ * <p>This optimizes for unstyled text and so workPaint might not be
+ * modified by this call.
+ *
+ * <p>The returned advance width will be < 0 if the paragraph
+ * direction is right-to-left.
+ */
+ private static float drawDirectionalRun(Canvas canvas,
CharSequence text, int start, int end,
- int dir, boolean reverse,
+ int dir, boolean runIsRtl,
float x, int top, int y, int bottom,
Paint.FontMetricsInt fmi,
TextPaint paint,
TextPaint workPaint,
boolean needWidth) {
- if (! (text instanceof Spanned)) {
+
+ // XXX: It looks like all calls to this API match dir and runIsRtl, so
+ // having both parameters is redundant and confusing.
+
+ // fast path for unstyled text
+ if (!(text instanceof Spanned)) {
float ret = 0;
- if (reverse) {
+ if (runIsRtl) {
CharSequence tmp = TextUtils.getReverse(text, start, end);
+ // XXX: this assumes getReverse doesn't tweak the length of
+ // the text
int tmpend = end - start;
if (canvas != null || needWidth)
@@ -227,15 +274,14 @@ public class Styled
paint.getFontMetricsInt(fmi);
}
- return ret * dir; //Layout.DIR_RIGHT_TO_LEFT == -1
+ return ret * dir; // Layout.DIR_RIGHT_TO_LEFT == -1
}
float ox = x;
- int asc = 0, desc = 0;
- int ftop = 0, fbot = 0;
+ int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;
Spanned sp = (Spanned) text;
- Class division;
+ Class<?> division;
if (canvas == null)
division = MetricAffectingSpan.class;
@@ -246,20 +292,23 @@ public class Styled
for (int i = start; i < end; i = next) {
next = sp.nextSpanTransition(i, end, division);
- x += each(canvas, sp, i, next, dir, reverse,
+ // XXX: if dir and runIsRtl were not the same, this would draw
+ // spans in the wrong order, but no one appears to call it this
+ // way.
+ x += drawUniformRun(canvas, sp, i, next, dir, runIsRtl,
x, top, y, bottom, fmi, paint, workPaint,
needWidth || next != end);
if (fmi != null) {
- if (fmi.ascent < asc)
- asc = fmi.ascent;
- if (fmi.descent > desc)
- desc = fmi.descent;
-
- if (fmi.top < ftop)
- ftop = fmi.top;
- if (fmi.bottom > fbot)
- fbot = fmi.bottom;
+ if (fmi.ascent < minAscent)
+ minAscent = fmi.ascent;
+ if (fmi.descent > maxDescent)
+ maxDescent = fmi.descent;
+
+ if (fmi.top < minTop)
+ minTop = fmi.top;
+ if (fmi.bottom > maxBottom)
+ maxBottom = fmi.bottom;
}
}
@@ -267,71 +316,78 @@ public class Styled
if (start == end) {
paint.getFontMetricsInt(fmi);
} else {
- fmi.ascent = asc;
- fmi.descent = desc;
- fmi.top = ftop;
- fmi.bottom = fbot;
+ fmi.ascent = minAscent;
+ fmi.descent = maxDescent;
+ fmi.top = minTop;
+ fmi.bottom = maxBottom;
}
}
return x - ox;
}
-
+ /**
+ * Draws a unidirectional run of text on a single line, and optionally
+ * returns the signed advance. Unlike drawDirectionalRun, the paragraph
+ * direction and run direction can be different.
+ */
/* package */ static float drawText(Canvas canvas,
CharSequence text, int start, int end,
- int direction, boolean reverse,
+ int dir, boolean runIsRtl,
float x, int top, int y, int bottom,
TextPaint paint,
TextPaint workPaint,
boolean needWidth) {
- if ((direction == Layout.DIR_RIGHT_TO_LEFT && !reverse) ||
- (reverse && direction == Layout.DIR_LEFT_TO_RIGHT)) {
- float ch = foreach(null, text, start, end, Layout.DIR_LEFT_TO_RIGHT,
- false, 0, 0, 0, 0, null, paint, workPaint,
- true);
-
- ch *= direction; // DIR_RIGHT_TO_LEFT == -1
- foreach(canvas, text, start, end, -direction,
- reverse, x + ch, top, y, bottom, null, paint,
+ // XXX this logic is (dir == DIR_LEFT_TO_RIGHT) == runIsRtl
+ if ((dir == Layout.DIR_RIGHT_TO_LEFT && !runIsRtl) ||
+ (runIsRtl && dir == Layout.DIR_LEFT_TO_RIGHT)) {
+ // TODO: this needs the real direction
+ float ch = drawDirectionalRun(null, text, start, end,
+ Layout.DIR_LEFT_TO_RIGHT, false, 0, 0, 0, 0, null, paint,
+ workPaint, true);
+
+ ch *= dir; // DIR_RIGHT_TO_LEFT == -1
+ drawDirectionalRun(canvas, text, start, end, -dir,
+ runIsRtl, x + ch, top, y, bottom, null, paint,
workPaint, true);
return ch;
}
- return foreach(canvas, text, start, end, direction, reverse,
+ return drawDirectionalRun(canvas, text, start, end, dir, runIsRtl,
x, top, y, bottom, null, paint, workPaint,
needWidth);
}
/**
- * Draw the specified range of text, specified by start/end, with its origin at (x,y),
- * in the specified Paint. The origin is interpreted based on the Align setting in the
- * Paint.
- *
- * This method considers style information in the text
- * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
- * correctly draws the text).
- * See also
- * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float, float, Paint)}
- * and
- * {@link android.graphics.Canvas#drawRect(float, float, float, float, Paint)}.
+ * Draws a run of text on a single line, with its
+ * origin at (x,y), in the specified Paint. The origin is interpreted based
+ * on the Align setting in the Paint.
+ *
+ * This method considers style information in the text (e.g. even when text
+ * is an instance of {@link android.text.Spanned}, this method correctly
+ * draws the text). See also
+ * {@link android.graphics.Canvas#drawText(CharSequence, int, int, float,
+ * float, Paint)} and
+ * {@link android.graphics.Canvas#drawRect(float, float, float, float,
+ * Paint)}.
*
- * @param canvas The target canvas.
+ * @param canvas The target canvas
* @param text The text to be drawn
* @param start The index of the first character in text to draw
* @param end (end - 1) is the index of the last character in text to draw
* @param direction The direction of the text. This must be
- * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
- * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
+ * {@link android.text.Layout#DIR_LEFT_TO_RIGHT} or
+ * {@link android.text.Layout#DIR_RIGHT_TO_LEFT}.
* @param x The x-coordinate of origin for where to draw the text
* @param top The top side of the rectangle to be drawn
* @param y The y-coordinate of origin for where to draw the text
* @param bottom The bottom side of the rectangle to be drawn
* @param paint The main {@link TextPaint} object.
- * @param workPaint The {@link TextPaint} object used for temporal workspace.
- * @param needWidth If true, this method returns the width of drawn text.
- * @return Width of the drawn text if needWidth is true.
+ * @param workPaint The {@link TextPaint} object used for temporal
+ * workspace.
+ * @param needWidth If true, this method returns the width of drawn text
+ * @return Width of the drawn text if needWidth is true
*/
public static float drawText(Canvas canvas,
CharSequence text, int start, int end,
@@ -341,34 +397,37 @@ public class Styled
TextPaint workPaint,
boolean needWidth) {
// For safety.
- direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
- /*
- * Hided "reverse" parameter since it is meaningless for external developers.
- * Kept workPaint as is so that developers reuse the workspace.
- */
+ direction = direction >= 0 ? Layout.DIR_LEFT_TO_RIGHT
+ : Layout.DIR_RIGHT_TO_LEFT;
+
+ // Hide runIsRtl parameter since it is meaningless for external
+ // developers.
+ // XXX: the runIsRtl probably ought to be the same as direction, then
+ // this could draw rtl text.
return drawText(canvas, text, start, end, direction, false,
x, top, y, bottom, paint, workPaint, needWidth);
}
/**
- * Return the width of the text, considering style information in the text
- * (e.g. Even when text is an instance of {@link android.text.Spanned}, this method
- * correctly mesures the width of the text).
+ * Returns the width of a run of left-to-right text on a single line,
+ * considering style information in the text (e.g. even when text is an
+ * instance of {@link android.text.Spanned}, this method correctly measures
+ * the width of the text).
*
- * @param paint The main {@link TextPaint} object.
- * @param workPaint The {@link TextPaint} object used for temporal workspace.
- * @param text The text to measure
- * @param start The index of the first character to start measuring
+ * @param paint the main {@link TextPaint} object; will not be modified
+ * @param workPaint the {@link TextPaint} object available for modification;
+ * will not necessarily be used
+ * @param text the text to measure
+ * @param start the index of the first character to start measuring
* @param end 1 beyond the index of the last character to measure
- * @param fmi FontMetrics information. Can be null
- * @return The width of the text
+ * @param fmi FontMetrics information; can be null
+ * @return The width of the text
*/
public static float measureText(TextPaint paint,
TextPaint workPaint,
CharSequence text, int start, int end,
Paint.FontMetricsInt fmi) {
- // Keep workPaint as is so that developers reuse the workspace.
- return foreach(null, text, start, end,
+ return drawDirectionalRun(null, text, start, end,
Layout.DIR_LEFT_TO_RIGHT, false,
0, 0, 0, 0, fmi, paint, workPaint, true);
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 53096dd..9589bf3 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1501,6 +1501,28 @@ public class TextUtils {
}
/**
+ * @hide
+ */
+ public static boolean isPrintableAscii(final char c) {
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
+ }
+
+ /**
+ * @hide
+ */
+ public static boolean isPrintableAsciiOnly(final CharSequence str) {
+ final int len = str.length();
+ for (int i = 0; i < len; i++) {
+ if (!isPrintableAscii(str.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Capitalization mode for {@link #getCapsMode}: capitalize all
* characters. This value is explicitly defined to be the same as
* {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}.
@@ -1535,13 +1557,17 @@ public class TextUtils {
* @param reqModes The modes to be checked: may be any combination of
* {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
* {@link #CAP_MODE_SENTENCES}.
- *
+ *
* @return Returns the actual capitalization modes that can be in effect
* at the current position, which is any combination of
* {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
* {@link #CAP_MODE_SENTENCES}.
*/
public static int getCapsMode(CharSequence cs, int off, int reqModes) {
+ if (off < 0) {
+ return 0;
+ }
+
int i;
char c;
int mode = 0;
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 9dd8ceb..dde0889 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -21,7 +21,6 @@ import com.android.internal.R;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.pim.DateException;
import java.util.Calendar;
import java.util.Date;
@@ -814,35 +813,6 @@ public class DateUtils
&& (thenMonth == time.month)
&& (thenMonthDay == time.monthDay);
}
-
- /**
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- private static final int ctoi(String str, int index)
- throws DateException
- {
- char c = str.charAt(index);
- if (c >= '0' && c <= '9') {
- return (int)(c - '0');
- }
- throw new DateException("Expected numeric character. Got '" +
- c + "'");
- }
-
- /**
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- private static final int check(int lowerBound, int upperBound, int value)
- throws DateException
- {
- if (value >= lowerBound && value <= upperBound) {
- return value;
- }
- throw new DateException("field out of bounds. max=" + upperBound
- + " value=" + value);
- }
/**
* @hide
@@ -861,81 +831,6 @@ public class DateUtils
return false;
}
-
- // note that month in Calendar is 0 based and in all other human
- // representations, it's 1 based.
- // Returns if the Z was present, meaning that the time is in UTC
- /**
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- public static boolean parseDateTime(String str, Calendar cal)
- throws DateException
- {
- int len = str.length();
- boolean dateTime = (len == 15 || len == 16) && str.charAt(8) == 'T';
- boolean justDate = len == 8;
- if (dateTime || justDate) {
- cal.clear();
- cal.set(Calendar.YEAR,
- ctoi(str, 0)*1000 + ctoi(str, 1)*100
- + ctoi(str, 2)*10 + ctoi(str, 3));
- cal.set(Calendar.MONTH,
- check(0, 11, ctoi(str, 4)*10 + ctoi(str, 5) - 1));
- cal.set(Calendar.DAY_OF_MONTH,
- check(1, 31, ctoi(str, 6)*10 + ctoi(str, 7)));
- if (dateTime) {
- cal.set(Calendar.HOUR_OF_DAY,
- check(0, 23, ctoi(str, 9)*10 + ctoi(str, 10)));
- cal.set(Calendar.MINUTE,
- check(0, 59, ctoi(str, 11)*10 + ctoi(str, 12)));
- cal.set(Calendar.SECOND,
- check(0, 59, ctoi(str, 13)*10 + ctoi(str, 14)));
- }
- if (justDate) {
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- return true;
- }
- if (len == 15) {
- return false;
- }
- if (str.charAt(15) == 'Z') {
- return true;
- }
- }
- throw new DateException("Invalid time (expected "
- + "YYYYMMDDThhmmssZ? got '" + str + "').");
- }
-
- /**
- * Given a timezone string which can be null, and a dateTime string,
- * set that time into a calendar.
- * @hide
- * @deprecated use {@link android.text.format.Time}
- */
- public static void parseDateTime(String tz, String dateTime, Calendar out)
- throws DateException
- {
- TimeZone timezone;
- if (DateUtils.isUTC(dateTime)) {
- timezone = TimeZone.getTimeZone("UTC");
- }
- else if (tz == null) {
- timezone = TimeZone.getDefault();
- }
- else {
- timezone = TimeZone.getTimeZone(tz);
- }
-
- Calendar local = new GregorianCalendar(timezone);
- DateUtils.parseDateTime(dateTime, local);
-
- out.setTimeInMillis(local.getTimeInMillis());
- }
-
-
/**
* Return a string containing the date and time in RFC2445 format.
* Ensures that the time is written in UTC. The Calendar class doesn't
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index ab33cb3..9af42cc 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -54,7 +54,7 @@ implements MovementMethod
Selection.setSelection(buffer, 0);
return true;
} else {
- return Selection.moveUp(buffer, layout);
+ return Selection.moveUp(buffer, layout);
}
}
}
@@ -80,7 +80,7 @@ implements MovementMethod
Selection.setSelection(buffer, buffer.length());
return true;
} else {
- return Selection.moveDown(buffer, layout);
+ return Selection.moveDown(buffer, layout);
}
}
}
@@ -133,6 +133,35 @@ implements MovementMethod
}
}
+ private int getOffset(int x, int y, TextView widget){
+ // Converts the absolute X,Y coordinates to the character offset for the
+ // character whose position is closest to the specified
+ // horizontal position.
+ x -= widget.getTotalPaddingLeft();
+ y -= widget.getTotalPaddingTop();
+
+ // Clamp the position to inside of the view.
+ if (x < 0) {
+ x = 0;
+ } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
+ x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
+ }
+ if (y < 0) {
+ y = 0;
+ } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
+ y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
+ }
+
+ x += widget.getScrollX();
+ y += widget.getScrollY();
+
+ Layout layout = widget.getLayout();
+ int line = layout.getLineForVertical(y);
+
+ int offset = layout.getOffsetForHorizontal(line, x);
+ return offset;
+ }
+
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -196,12 +225,12 @@ implements MovementMethod
}
return false;
}
-
+
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
return false;
}
-
+
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
@@ -209,11 +238,107 @@ implements MovementMethod
initialScrollX = Touch.getInitialScrollX(widget, buffer);
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
-
+
boolean handled = Touch.onTouchEvent(widget, buffer, event);
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int offset = getOffset(x, y, widget);
+
+ if (cap) {
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset,
+ Spannable.SPAN_POINT_POINT);
+
+ // Disallow intercepting of the touch events, so that
+ // users can scroll and select at the same time.
+ // without this, users would get booted out of select
+ // mode once the view detected it needed to scroll.
+ widget.getParent().requestDisallowInterceptTouchEvent(true);
+ } else {
+ OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
+ OnePointFiveTapState.class);
+
+ if (tap.length > 0) {
+ if (event.getEventTime() - tap[0].mWhen <=
+ ViewConfiguration.getDoubleTapTimeout() &&
+ sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
+
+ tap[0].active = true;
+ MetaKeyKeyListener.startSelecting(widget, buffer);
+ widget.getParent().requestDisallowInterceptTouchEvent(true);
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset,
+ Spannable.SPAN_POINT_POINT);
+ }
+
+ tap[0].mWhen = event.getEventTime();
+ } else {
+ OnePointFiveTapState newtap = new OnePointFiveTapState();
+ newtap.mWhen = event.getEventTime();
+ newtap.active = false;
+ buffer.setSpan(newtap, 0, buffer.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+
+ if (cap && handled) {
+ // Before selecting, make sure we've moved out of the "slop".
+ // handled will be true, if we're in select mode AND we're
+ // OUT of the slop
+
+ // Turn long press off while we're selecting. User needs to
+ // re-tap on the selection to enable longpress
+ widget.cancelLongPress();
+
+ // Update selection as we're moving the selection area.
+
+ // Get the current touch position
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ int offset = getOffset(x, y, widget);
+
+ final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
+ OnePointFiveTapState.class);
+
+ if (tap.length > 0 && tap[0].active) {
+ // Get the last down touch position (the position at which the
+ // user started the selection)
+ int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
+
+ // Compute the selection boundaries
+ int spanstart;
+ int spanend;
+ if (offset >= lastDownOffset) {
+ // Expand from word start of the original tap to new word
+ // end, since we are selecting "forwards"
+ spanstart = findWordStart(buffer, lastDownOffset);
+ spanend = findWordEnd(buffer, offset);
+ } else {
+ // Expand to from new word start to word end of the original
+ // tap since we are selecting "backwards".
+ // The spanend will always need to be associated with the touch
+ // up position, so that refining the selection with the
+ // trackball will work as expected.
+ spanstart = findWordEnd(buffer, lastDownOffset);
+ spanend = findWordStart(buffer, offset);
+ }
+ Selection.setSelection(buffer, spanstart, spanend);
+ } else {
+ Selection.extendSelection(buffer, offset);
+ }
+ return true;
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_UP) {
// If we have scrolled, then the up shouldn't move the cursor,
// but we do need to make sure the cursor is still visible at
// the current scroll offset to avoid the scroll jumping later
@@ -223,35 +348,26 @@ implements MovementMethod
widget.moveCursorToVisibleOffset();
return true;
}
-
+
int x = (int) event.getX();
int y = (int) event.getY();
+ int off = getOffset(x, y, widget);
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
+ // XXX should do the same adjust for x as we do for the line.
- // Clamp the position to inside of the view.
- if (x < 0) {
- x = 0;
- } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
- x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
- }
- if (y < 0) {
- y = 0;
- } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
- y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
+ OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
+ OnePointFiveTapState.class);
+ if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
+ Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
+ // If we've set select mode, because there was a onepointfivetap,
+ // but there was no ensuing swipe gesture, undo the select mode
+ // and remove reference to the last onepointfivetap.
+ MetaKeyKeyListener.stopSelecting(widget, buffer);
+ for (int i=0; i < onepointfivetap.length; i++) {
+ buffer.removeSpan(onepointfivetap[i]);
+ }
+ buffer.removeSpan(LAST_TAP_DOWN);
}
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
-
- int off = layout.getOffsetForHorizontal(line, x);
-
- // XXX should do the same adjust for x as we do for the line.
-
boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_SHIFT_ON) == 1) ||
(MetaKeyKeyListener.getMetaState(buffer,
@@ -263,10 +379,10 @@ implements MovementMethod
if (tap.length > 0) {
if (event.getEventTime() - tap[0].mWhen <=
- ViewConfiguration.getDoubleTapTimeout()) {
- if (sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
- doubletap = true;
- }
+ ViewConfiguration.getDoubleTapTimeout() &&
+ sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
+
+ doubletap = true;
}
tap[0].mWhen = event.getEventTime();
@@ -278,7 +394,14 @@ implements MovementMethod
}
if (cap) {
- Selection.extendSelection(buffer, off);
+ buffer.removeSpan(LAST_TAP_DOWN);
+ if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
+ // If we selecting something with the onepointfivetap-and
+ // swipe gesture, stop it on finger up.
+ MetaKeyKeyListener.stopSelecting(widget, buffer);
+ } else {
+ Selection.extendSelection(buffer, off);
+ }
} else if (doubletap) {
Selection.setSelection(buffer,
findWordStart(buffer, off),
@@ -301,6 +424,19 @@ implements MovementMethod
long mWhen;
}
+ /* We check for a onepointfive tap. This is similar to
+ * doubletap gesture (where a finger goes down, up, down, up, in a short
+ * time period), except in the onepointfive tap, a users finger only needs
+ * to go down, up, down in a short time period. We detect this type of tap
+ * to implement the onepointfivetap-and-swipe selection gesture.
+ * This gesture allows users to select a segment of text without going
+ * through the "select text" option in the context menu.
+ */
+ private static class OnePointFiveTapState implements NoCopySpan {
+ long mWhen;
+ boolean active;
+ }
+
private static boolean sameWord(CharSequence text, int one, int two) {
int start = findWordStart(text, one);
int end = findWordEnd(text, one);
@@ -395,5 +531,7 @@ implements MovementMethod
return sInstance;
}
+
+ private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index aa8d979..42ad10e 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -24,6 +24,7 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
+import android.view.KeyEvent;
public class Touch {
private Touch() { }
@@ -139,10 +140,21 @@ public class Touch {
if (ds[0].mFarEnough) {
ds[0].mUsed = true;
-
- float dx = ds[0].mX - event.getX();
- float dy = ds[0].mY - event.getY();
-
+ boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
+ KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer,
+ MetaKeyKeyListener.META_SELECTING) != 0);
+ float dx;
+ float dy;
+ if (cap) {
+ // if we're selecting, we want the scroll to go in
+ // the direction of the drag
+ dx = event.getX() - ds[0].mX;
+ dy = event.getY() - ds[0].mY;
+ } else {
+ dx = ds[0].mX - event.getX();
+ dy = ds[0].mY - event.getY();
+ }
ds[0].mX = event.getX();
ds[0].mY = event.getY();
diff --git a/core/java/android/text/style/LeadingMarginSpan.java b/core/java/android/text/style/LeadingMarginSpan.java
index 8e212e3..2f429ff 100644
--- a/core/java/android/text/style/LeadingMarginSpan.java
+++ b/core/java/android/text/style/LeadingMarginSpan.java
@@ -23,24 +23,91 @@ import android.text.Layout;
import android.text.ParcelableSpan;
import android.text.TextUtils;
+/**
+ * A paragraph style affecting the leading margin. There can be multiple leading
+ * margin spans on a single paragraph; they will be rendered in order, each
+ * adding its margin to the ones before it. The leading margin is on the right
+ * for lines in a right-to-left paragraph.
+ */
public interface LeadingMarginSpan
extends ParagraphStyle
{
+ /**
+ * Returns the amount by which to adjust the leading margin. Positive values
+ * move away from the leading edge of the paragraph, negative values move
+ * towards it.
+ *
+ * @param first true if the request is for the first line of a paragraph,
+ * false for subsequent lines
+ * @return the offset for the margin.
+ */
public int getLeadingMargin(boolean first);
+
+ /**
+ * Renders the leading margin. This is called before the margin has been
+ * adjusted by the value returned by {@link #getLeadingMargin(boolean)}.
+ *
+ * @param c the canvas
+ * @param p the paint. The this should be left unchanged on exit.
+ * @param x the current position of the margin
+ * @param dir the base direction of the paragraph; if negative, the margin
+ * is to the right of the text, otherwise it is to the left.
+ * @param top the top of the line
+ * @param baseline the baseline of the line
+ * @param bottom the bottom of the line
+ * @param text the text
+ * @param start the start of the line
+ * @param end the end of the line
+ * @param first true if this is the first line of its paragraph
+ * @param layout the layout containing this line
+ */
public void drawLeadingMargin(Canvas c, Paint p,
int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout layout);
+
+ /**
+ * An extended version of {@link LeadingMarginSpan}, which allows
+ * the implementor to specify the number of lines of text to which
+ * this object is attached that the "first line of paragraph" margin
+ * width will be applied to.
+ */
+ public interface LeadingMarginSpan2 extends LeadingMarginSpan, WrapTogetherSpan {
+ /**
+ * Returns the number of lines of text to which this object is
+ * attached that the "first line" margin will apply to.
+ * Note that if this returns N, the first N lines of the region,
+ * not the first N lines of each paragraph, will be given the
+ * special margin width.
+ */
+ public int getLeadingMarginLineCount();
+ };
+
+ /**
+ * The standard implementation of LeadingMarginSpan, which adjusts the
+ * margin but does not do any rendering.
+ */
public static class Standard implements LeadingMarginSpan, ParcelableSpan {
private final int mFirst, mRest;
+ /**
+ * Constructor taking separate indents for the first and subsequent
+ * lines.
+ *
+ * @param first the indent for the first line of the paragraph
+ * @param rest the indent for the remaining lines of the paragraph
+ */
public Standard(int first, int rest) {
mFirst = first;
mRest = rest;
}
+ /**
+ * Constructor taking an indent for all lines.
+ * @param every the indent of each line
+ */
public Standard(int every) {
this(every, every);
}
diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java
index e5b7644..0566428 100644
--- a/core/java/android/text/style/TabStopSpan.java
+++ b/core/java/android/text/style/TabStopSpan.java
@@ -16,14 +16,31 @@
package android.text.style;
+/**
+ * Represents a single tab stop on a line.
+ */
public interface TabStopSpan
extends ParagraphStyle
{
+ /**
+ * Returns the offset of the tab stop from the leading margin of the
+ * line.
+ * @return the offset
+ */
public int getTabStop();
+ /**
+ * The default implementation of TabStopSpan.
+ */
public static class Standard
implements TabStopSpan
{
+ /**
+ * Constructor.
+ *
+ * @param where the offset of the tab stop from the leading margin of
+ * the line
+ */
public Standard(int where) {
mTab = where;
}
diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java
index 198e4fa..7112347 100644
--- a/core/java/android/text/style/UpdateAppearance.java
+++ b/core/java/android/text/style/UpdateAppearance.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.text.style;
/**
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index ce25c47..9860588 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -22,9 +22,11 @@ import android.text.style.URLSpan;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
+import android.util.Patterns;
import android.webkit.WebView;
import android.widget.TextView;
+
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
@@ -133,7 +135,7 @@ public class Linkify {
*/
public static final TransformFilter sPhoneNumberTransformFilter = new TransformFilter() {
public final String transformUrl(final Matcher match, String url) {
- return Regex.digitsAndPlusOnly(match);
+ return Patterns.digitsAndPlusOnly(match);
}
};
@@ -207,19 +209,19 @@ public class Linkify {
ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
if ((mask & WEB_URLS) != 0) {
- gatherLinks(links, text, Regex.WEB_URL_PATTERN,
+ gatherLinks(links, text, Patterns.WEB_URL,
new String[] { "http://", "https://", "rtsp://" },
sUrlMatchFilter, null);
}
if ((mask & EMAIL_ADDRESSES) != 0) {
- gatherLinks(links, text, Regex.EMAIL_ADDRESS_PATTERN,
+ gatherLinks(links, text, Patterns.EMAIL_ADDRESS,
new String[] { "mailto:" },
null, null);
}
if ((mask & PHONE_NUMBERS) != 0) {
- gatherLinks(links, text, Regex.PHONE_PATTERN,
+ gatherLinks(links, text, Patterns.PHONE,
new String[] { "tel:" },
sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
}
diff --git a/core/java/android/text/util/Regex.java b/core/java/android/text/util/Regex.java
deleted file mode 100644
index 5005b42..0000000
--- a/core/java/android/text/util/Regex.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @hide
- */
-public class Regex {
- /**
- * Regular expression pattern to match all IANA top-level domains.
- * List accurate as of 2007/06/15. List taken from:
- * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
- * This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
- */
- public static final Pattern TOP_LEVEL_DOMAIN_PATTERN
- = Pattern.compile(
- "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
- + "|(biz|b[abdefghijmnorstvwyz])"
- + "|(cat|com|coop|c[acdfghiklmnoruvxyz])"
- + "|d[ejkmoz]"
- + "|(edu|e[cegrstu])"
- + "|f[ijkmor]"
- + "|(gov|g[abdefghilmnpqrstuwy])"
- + "|h[kmnrtu]"
- + "|(info|int|i[delmnoqrst])"
- + "|(jobs|j[emop])"
- + "|k[eghimnrwyz]"
- + "|l[abcikrstuvy]"
- + "|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
- + "|(name|net|n[acefgilopruz])"
- + "|(org|om)"
- + "|(pro|p[aefghklmnrstwy])"
- + "|qa"
- + "|r[eouw]"
- + "|s[abcdeghijklmnortuvyz]"
- + "|(tel|travel|t[cdfghjklmnoprtvwz])"
- + "|u[agkmsyz]"
- + "|v[aceginu]"
- + "|w[fs]"
- + "|y[etu]"
- + "|z[amw])");
-
- /**
- * Regular expression pattern to match RFC 1738 URLs
- * List accurate as of 2007/06/15. List taken from:
- * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
- * This pattern is auto-generated by //device/tools/make-iana-tld-pattern.py
- */
- public static final Pattern WEB_URL_PATTERN
- = Pattern.compile(
- "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
- + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
- + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
- + "((?:(?:[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}\\.)+" // named host
- + "(?:" // plus top level domain
- + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
- + "|(?:biz|b[abdefghijmnorstvwyz])"
- + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
- + "|d[ejkmoz]"
- + "|(?:edu|e[cegrstu])"
- + "|f[ijkmor]"
- + "|(?:gov|g[abdefghilmnpqrstuwy])"
- + "|h[kmnrtu]"
- + "|(?:info|int|i[delmnoqrst])"
- + "|(?:jobs|j[emop])"
- + "|k[eghimnrwyz]"
- + "|l[abcikrstuvy]"
- + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
- + "|(?:name|net|n[acefgilopruz])"
- + "|(?:org|om)"
- + "|(?:pro|p[aefghklmnrstwy])"
- + "|qa"
- + "|r[eouw]"
- + "|s[abcdeghijklmnortuvyz]"
- + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
- + "|u[agkmsyz]"
- + "|v[aceginu]"
- + "|w[fs]"
- + "|y[etu]"
- + "|z[amw]))"
- + "|(?:(?:25[0-5]|2[0-4]" // or ip address
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
- + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9])))"
- + "(?:\\:\\d{1,5})?)" // plus option port number
- + "(\\/(?:(?:[a-zA-Z0-9\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
- + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
- + "(?:\\b|$)"); // and finally, a word boundary or end of
- // input. This is to stop foo.sure from
- // matching as foo.su
-
- public static final Pattern IP_ADDRESS_PATTERN
- = Pattern.compile(
- "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9]))");
-
- public static final Pattern DOMAIN_NAME_PATTERN
- = Pattern.compile(
- "(((([a-zA-Z0-9][a-zA-Z0-9\\-]*)*[a-zA-Z0-9]\\.)+"
- + TOP_LEVEL_DOMAIN_PATTERN + ")|"
- + IP_ADDRESS_PATTERN + ")");
-
- public static final Pattern EMAIL_ADDRESS_PATTERN
- = Pattern.compile(
- "[a-zA-Z0-9\\+\\.\\_\\%\\-]{1,256}" +
- "\\@" +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
- "(" +
- "\\." +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
- ")+"
- );
-
- /**
- * This pattern is intended for searching for things that look like they
- * might be phone numbers in arbitrary text, not for validating whether
- * something is in fact a phone number. It will miss many things that
- * are legitimate phone numbers.
- *
- * <p> The pattern matches the following:
- * <ul>
- * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
- * may follow.
- * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
- * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
- * </ul>
- */
- public static final Pattern PHONE_PATTERN
- = Pattern.compile( // sdd = space, dot, or dash
- "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
- + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
- + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
-
- /**
- * Convenience method to take all of the non-null matching groups in a
- * regex Matcher and return them as a concatenated string.
- *
- * @param matcher The Matcher object from which grouped text will
- * be extracted
- *
- * @return A String comprising all of the non-null matched
- * groups concatenated together
- */
- public static final String concatGroups(Matcher matcher) {
- StringBuilder b = new StringBuilder();
- final int numGroups = matcher.groupCount();
-
- for (int i = 1; i <= numGroups; i++) {
- String s = matcher.group(i);
-
- System.err.println("Group(" + i + ") : " + s);
-
- if (s != null) {
- b.append(s);
- }
- }
-
- return b.toString();
- }
-
- /**
- * Convenience method to return only the digits and plus signs
- * in the matching string.
- *
- * @param matcher The Matcher object from which digits and plus will
- * be extracted
- *
- * @return A String comprising all of the digits and plus in
- * the match
- */
- public static final String digitsAndPlusOnly(Matcher matcher) {
- StringBuilder buffer = new StringBuilder();
- String matchingRegion = matcher.group();
-
- for (int i = 0, size = matchingRegion.length(); i < size; i++) {
- char character = matchingRegion.charAt(i);
-
- if (character == '+' || Character.isDigit(character)) {
- buffer.append(character);
- }
- }
- return buffer.toString();
- }
-}
diff --git a/core/java/android/text/util/Rfc822InputFilter.java b/core/java/android/text/util/Rfc822InputFilter.java
deleted file mode 100644
index 8c8b7fc..0000000
--- a/core/java/android/text/util/Rfc822InputFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package android.text.util;
-
-import android.text.InputFilter;
-import android.text.Spanned;
-import android.text.SpannableStringBuilder;
-
-/**
- * Implements special address cleanup rules:
- * The first space key entry following an "@" symbol that is followed by any combination
- * of letters and symbols, including one+ dots and zero commas, should insert an extra
- * comma (followed by the space).
- *
- * @hide
- */
-public class Rfc822InputFilter implements InputFilter {
-
- public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
- int dstart, int dend) {
-
- // quick check - did they enter a single space?
- if (end-start != 1 || source.charAt(start) != ' ') {
- return null;
- }
-
- // determine if the characters before the new space fit the pattern
- // follow backwards and see if we find a comma, dot, or @
- int scanBack = dstart;
- boolean dotFound = false;
- while (scanBack > 0) {
- char c = dest.charAt(--scanBack);
- switch (c) {
- case '.':
- dotFound = true; // one or more dots are req'd
- break;
- case ',':
- return null;
- case '@':
- if (!dotFound) {
- return null;
- }
- // we have found a comma-insert case. now just do it
- // in the least expensive way we can.
- if (source instanceof Spanned) {
- SpannableStringBuilder sb = new SpannableStringBuilder(",");
- sb.append(source);
- return sb;
- } else {
- return ", ";
- }
- default:
- // just keep going
- }
- }
-
- // no termination cases were found, so don't edit the input
- return null;
- }
-}
diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
index cb39f7d..952d833 100644
--- a/core/java/android/text/util/Rfc822Tokenizer.java
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -41,7 +41,6 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
* It will try to be tolerant of broken syntax instead of
* returning an error.
*
- * @hide
*/
public static void tokenize(CharSequence text, Collection<Rfc822Token> out) {
StringBuilder name = new StringBuilder();
@@ -75,7 +74,7 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
name.setLength(0);
address.setLength(0);
- address.setLength(0);
+ comment.setLength(0);
} else if (c == '"') {
i++;
@@ -85,7 +84,7 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
if (c == '"') {
i++;
break;
- } else if (c == '\\') {
+ } else if (c == '\\' && i + 1 < cursor) {
name.append(text.charAt(i + 1));
i += 2;
} else {
@@ -111,7 +110,7 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
comment.append(c);
level++;
i++;
- } else if (c == '\\') {
+ } else if (c == '\\' && i + 1 < cursor) {
comment.append(text.charAt(i + 1));
i += 2;
} else {
diff --git a/core/java/android/text/util/Rfc822Validator.java b/core/java/android/text/util/Rfc822Validator.java
deleted file mode 100644
index 6a6bf69..0000000
--- a/core/java/android/text/util/Rfc822Validator.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text.util;
-
-import android.text.TextUtils;
-import android.widget.AutoCompleteTextView;
-
-import java.util.regex.Pattern;
-
-/**
- * This class works as a Validator for AutoCompleteTextView for
- * email addresses. If a token does not appear to be a valid address,
- * it is trimmed of characters that cannot legitimately appear in one
- * and has the specified domain name added. It is meant for use with
- * {@link Rfc822Token} and {@link Rfc822Tokenizer}.
- *
- * @deprecated In the future make sure we don't quietly alter the user's
- * text in ways they did not intend. Meanwhile, hide this
- * class from the public API because it does not even have
- * a full understanding of the syntax it claims to correct.
- * @hide
- */
-public class Rfc822Validator implements AutoCompleteTextView.Validator {
- /*
- * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
- * want to make sure we will keep accepting email addresses with TLD's
- * that don't exist at the time of this writing, so this regexp relaxes
- * that constraint by accepting any kind of top level domain, not just
- * ".com", ".fr", etc...
- */
- private static final Pattern EMAIL_ADDRESS_PATTERN =
- Pattern.compile("[^\\s@]+@[^\\s@]+\\.[a-zA-z][a-zA-Z][a-zA-Z]*");
-
- private String mDomain;
-
- /**
- * Constructs a new validator that uses the specified domain name as
- * the default when none is specified.
- */
- public Rfc822Validator(String domain) {
- mDomain = domain;
- }
-
- /**
- * {@inheritDoc}
- */
- public boolean isValid(CharSequence text) {
- Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
-
- return tokens.length == 1 &&
- EMAIL_ADDRESS_PATTERN.
- matcher(tokens[0].getAddress()).matches();
- }
-
- /**
- * @return a string in which all the characters that are illegal for the username
- * or the domain name part of the email address have been removed.
- */
- private String removeIllegalCharacters(String s) {
- StringBuilder result = new StringBuilder();
- int length = s.length();
- for (int i = 0; i < length; i++) {
- char c = s.charAt(i);
-
- /*
- * An RFC822 atom can contain any ASCII printing character
- * except for periods and any of the following punctuation.
- * A local-part can contain multiple atoms, concatenated by
- * periods, so do allow periods here.
- */
-
- if (c <= ' ' || c > '~') {
- continue;
- }
-
- if (c == '(' || c == ')' || c == '<' || c == '>' ||
- c == '@' || c == ',' || c == ';' || c == ':' ||
- c == '\\' || c == '"' || c == '[' || c == ']') {
- continue;
- }
-
- result.append(c);
- }
- return result.toString();
- }
-
- /**
- * {@inheritDoc}
- */
- public CharSequence fixText(CharSequence cs) {
- // Return an empty string if the email address only contains spaces, \n or \t
- if (TextUtils.getTrimmedLength(cs) == 0) return "";
-
- Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(cs);
- StringBuilder sb = new StringBuilder();
-
- for (int i = 0; i < tokens.length; i++) {
- String text = tokens[i].getAddress();
- int index = text.indexOf('@');
- if (index < 0) {
- // If there is no @, just append the domain of the account
- tokens[i].setAddress(removeIllegalCharacters(text) + "@" + mDomain);
- } else {
- // Otherwise, remove the illegal characters on both sides of the '@'
- String fix = removeIllegalCharacters(text.substring(0, index));
- String domain = removeIllegalCharacters(text.substring(index + 1));
- tokens[i].setAddress(fix + "@" + (domain.length() != 0 ? domain : mDomain));
- }
-
- sb.append(tokens[i].toString());
- if (i + 1 < tokens.length) {
- sb.append(", ");
- }
- }
-
- return sb;
- }
-}
diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java
new file mode 100644
index 0000000..1f2a5a7
--- /dev/null
+++ b/core/java/android/util/Base64.java
@@ -0,0 +1,741 @@
+/*
+ * 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 java.io.UnsupportedEncodingException;
+
+/**
+ * Utilities for encoding and decoding the Base64 representation of
+ * binary data. See RFCs <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
+ * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
+ */
+public class Base64 {
+ /**
+ * Default values for encoder/decoder flags.
+ */
+ public static final int DEFAULT = 0;
+
+ /**
+ * Encoder flag bit to omit the padding '=' characters at the end
+ * of the output (if any).
+ */
+ public static final int NO_PADDING = 1;
+
+ /**
+ * Encoder flag bit to omit all line terminators (i.e., the output
+ * will be on one long line).
+ */
+ public static final int NO_WRAP = 2;
+
+ /**
+ * Encoder flag bit to indicate lines should be terminated with a
+ * CRLF pair instead of just an LF. Has no effect if {@code
+ * NO_WRAP} is specified as well.
+ */
+ public static final int CRLF = 4;
+
+ /**
+ * Encoder/decoder flag bit to indicate using the "URL and
+ * filename safe" variant of Base64 (see RFC 3548 section 4) where
+ * {@code -} and {@code _} are used in place of {@code +} and
+ * {@code /}.
+ */
+ public static final int URL_SAFE = 8;
+
+ /**
+ * Flag to pass to {@link Base64OutputStream} to indicate that it
+ * should not close the output stream it is wrapping when it
+ * itself is closed.
+ */
+ public static final int NO_CLOSE = 16;
+
+ // --------------------------------------------------------
+ // shared code
+ // --------------------------------------------------------
+
+ /* package */ static abstract class Coder {
+ public byte[] output;
+ public int op;
+
+ /**
+ * Encode/decode another block of input data. this.output is
+ * provided by the caller, and must be big enough to hold all
+ * the coded data. On exit, this.opwill be set to the length
+ * of the coded data.
+ *
+ * @param finish true if this is the final call to process for
+ * this object. Will finalize the coder state and
+ * include any final bytes in the output.
+ *
+ * @return true if the input so far is good; false if some
+ * error has been detected in the input stream..
+ */
+ public abstract boolean process(byte[] input, int offset, int len, boolean finish);
+
+ /**
+ * @return the maximum number of bytes a call to process()
+ * could produce for the given number of input bytes. This may
+ * be an overestimate.
+ */
+ public abstract int maxOutputSize(int len);
+ }
+
+ // --------------------------------------------------------
+ // decoding
+ // --------------------------------------------------------
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * <p>The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param str the input String to decode, which is converted to
+ * bytes using the default charset
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(String str, int flags) {
+ return decode(str.getBytes(), flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * <p>The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input array to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int flags) {
+ return decode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * <p>The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the data to decode
+ * @param offset the position within the input array at which to start
+ * @param len the number of bytes of input to decode
+ * @param flags controls certain features of the decoded output.
+ * Pass {@code DEFAULT} to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int offset, int len, int flags) {
+ // Allocate space for the most data the input could represent.
+ // (It could contain less if it contains whitespace, etc.)
+ Decoder decoder = new Decoder(flags, new byte[len*3/4]);
+
+ if (!decoder.process(input, offset, len, true)) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+
+ // Maybe we got lucky and allocated exactly enough output space.
+ if (decoder.op == decoder.output.length) {
+ return decoder.output;
+ }
+
+ // Need to shorten the array, so allocate a new one of the
+ // right size and copy.
+ byte[] temp = new byte[decoder.op];
+ System.arraycopy(decoder.output, 0, temp, 0, decoder.op);
+ return temp;
+ }
+
+ /* package */ static class Decoder extends Coder {
+ /**
+ * Lookup table for turning bytes into their position in the
+ * Base64 alphabet.
+ */
+ private static final int DECODE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /**
+ * Decode lookup table for the "web safe" variant (RFC 3548
+ * sec. 4) where - and _ replace + and /.
+ */
+ private static final int DECODE_WEBSAFE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /** Non-data values in the DECODE arrays. */
+ private static final int SKIP = -1;
+ private static final int EQUALS = -2;
+
+ /**
+ * States 0-3 are reading through the next input tuple.
+ * State 4 is having read one '=' and expecting exactly
+ * one more.
+ * State 5 is expecting no more data or padding characters
+ * in the input.
+ * State 6 is the error state; an error has been detected
+ * in the input and no future input can "fix" it.
+ */
+ private int state; // state number (0 to 6)
+ private int value;
+
+ final private int[] alphabet;
+
+ public Decoder(int flags, byte[] output) {
+ this.output = output;
+
+ alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE;
+ state = 0;
+ value = 0;
+ }
+
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could decode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 3/4 + 10;
+ }
+
+ /**
+ * Decode another block of input data.
+ *
+ * @return true if the state machine is still healthy. false if
+ * bad base-64 data has been detected in the input stream.
+ */
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ if (this.state == 6) return false;
+
+ int p = offset;
+ len += offset;
+
+ // Using local variables makes the decoder about 12%
+ // faster than if we manipulate the member variables in
+ // the loop. (Even alphabet makes a measurable
+ // difference, which is somewhat surprising to me since
+ // the member variable is final.)
+ int state = this.state;
+ int value = this.value;
+ int op = 0;
+ final byte[] output = this.output;
+ final int[] alphabet = this.alphabet;
+
+ while (p < len) {
+ // Try the fast path: we're starting a new tuple and the
+ // next four bytes of the input stream are all data
+ // bytes. This corresponds to going through states
+ // 0-1-2-3-0. We expect to use this method for most of
+ // the data.
+ //
+ // If any of the next four bytes of input are non-data
+ // (whitespace, etc.), value will end up negative. (All
+ // the non-data values in decode are small negative
+ // numbers, so shifting any of them up and or'ing them
+ // together will result in a value with its top bit set.)
+ //
+ // You can remove this whole block and the output should
+ // be the same, just slower.
+ if (state == 0) {
+ while (p+4 <= len &&
+ (value = ((alphabet[input[p] & 0xff] << 18) |
+ (alphabet[input[p+1] & 0xff] << 12) |
+ (alphabet[input[p+2] & 0xff] << 6) |
+ (alphabet[input[p+3] & 0xff]))) >= 0) {
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ p += 4;
+ }
+ if (p >= len) break;
+ }
+
+ // The fast path isn't available -- either we've read a
+ // partial tuple, or the next four input bytes aren't all
+ // data, or whatever. Fall back to the slower state
+ // machine implementation.
+
+ int d = alphabet[input[p++] & 0xff];
+
+ switch (state) {
+ case 0:
+ if (d >= 0) {
+ value = d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 1:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 2:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect exactly one more padding character.
+ output[op++] = (byte) (value >> 4);
+ state = 4;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 3:
+ if (d >= 0) {
+ // Emit the output triple and return to state 0.
+ value = (value << 6) | d;
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ state = 0;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect no further data or padding characters.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ state = 5;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 4:
+ if (d == EQUALS) {
+ ++state;
+ } else if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+
+ case 5:
+ if (d != SKIP) {
+ this.state = 6;
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (!finish) {
+ // We're out of input, but a future call could provide
+ // more.
+ this.state = state;
+ this.value = value;
+ this.op = op;
+ return true;
+ }
+
+ // Done reading input. Now figure out where we are left in
+ // the state machine and finish up.
+
+ switch (state) {
+ case 0:
+ // Output length is a multiple of three. Fine.
+ break;
+ case 1:
+ // Read one extra input byte, which isn't enough to
+ // make another output byte. Illegal.
+ this.state = 6;
+ return false;
+ case 2:
+ // Read two extra input bytes, enough to emit 1 more
+ // output byte. Fine.
+ output[op++] = (byte) (value >> 4);
+ break;
+ case 3:
+ // Read three extra input bytes, enough to emit 2 more
+ // output bytes. Fine.
+ output[op++] = (byte) (value >> 10);
+ output[op++] = (byte) (value >> 2);
+ break;
+ case 4:
+ // Read one padding '=' when we expected 2. Illegal.
+ this.state = 6;
+ return false;
+ case 5:
+ // Read all the padding '='s we expected and no more.
+ // Fine.
+ break;
+ }
+
+ this.state = state;
+ this.op = op;
+ return true;
+ }
+ }
+
+ // --------------------------------------------------------
+ // encoding
+ // --------------------------------------------------------
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int flags) {
+ try {
+ return new String(encode(input, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static String encodeToString(byte[] input, int offset, int len, int flags) {
+ try {
+ return new String(encode(input, offset, len, flags), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ // US-ASCII is guaranteed to be available.
+ throw new AssertionError(e);
+ }
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int flags) {
+ return encode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * start
+ * @param len the number of bytes of input to encode
+ * @param flags controls certain features of the encoded output.
+ * Passing {@code DEFAULT} results in output that
+ * adheres to RFC 2045.
+ */
+ public static byte[] encode(byte[] input, int offset, int len, int flags) {
+ Encoder encoder = new Encoder(flags, null);
+
+ // Compute the exact length of the array we will produce.
+ int output_len = len / 3 * 4;
+
+ // Account for the tail of the data and the padding bytes, if any.
+ if (encoder.do_padding) {
+ if (len % 3 > 0) {
+ output_len += 4;
+ }
+ } else {
+ switch (len % 3) {
+ case 0: break;
+ case 1: output_len += 2; break;
+ case 2: output_len += 3; break;
+ }
+ }
+
+ // Account for the newlines, if any.
+ if (encoder.do_newline && len > 0) {
+ output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) *
+ (encoder.do_cr ? 2 : 1);
+ }
+
+ encoder.output = new byte[output_len];
+ encoder.process(input, offset, len, true);
+
+ assert encoder.op == output_len;
+
+ return encoder.output;
+ }
+
+ /* package */ static class Encoder extends Coder {
+ /**
+ * Emit a new line every this many output tuples. Corresponds to
+ * a 76-character line length (the maximum allowable according to
+ * <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>).
+ */
+ public static final int LINE_GROUPS = 19;
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+ };
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE_WEBSAFE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
+ };
+
+ final private byte[] tail;
+ /* package */ int tailLen;
+ private int count;
+
+ final public boolean do_padding;
+ final public boolean do_newline;
+ final public boolean do_cr;
+ final private byte[] alphabet;
+
+ public Encoder(int flags, byte[] output) {
+ this.output = output;
+
+ do_padding = (flags & NO_PADDING) == 0;
+ do_newline = (flags & NO_WRAP) == 0;
+ do_cr = (flags & CRLF) != 0;
+ alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+ tail = new byte[2];
+ tailLen = 0;
+
+ count = do_newline ? LINE_GROUPS : -1;
+ }
+
+ /**
+ * @return an overestimate for the number of bytes {@code
+ * len} bytes could encode to.
+ */
+ public int maxOutputSize(int len) {
+ return len * 8/5 + 10;
+ }
+
+ public boolean process(byte[] input, int offset, int len, boolean finish) {
+ // Using local variables makes the encoder about 9% faster.
+ final byte[] alphabet = this.alphabet;
+ final byte[] output = this.output;
+ int op = 0;
+ int count = this.count;
+
+ int p = offset;
+ len += offset;
+ int v = -1;
+
+ // First we need to concatenate the tail of the previous call
+ // with any input bytes available now and see if we can empty
+ // the tail.
+
+ switch (tailLen) {
+ case 0:
+ // There was no tail.
+ break;
+
+ case 1:
+ if (p+2 <= len) {
+ // A 1-byte tail with at least 2 bytes of
+ // input available now.
+ v = ((tail[0] & 0xff) << 16) |
+ ((input[p++] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ };
+ break;
+
+ case 2:
+ if (p+1 <= len) {
+ // A 2-byte tail with at least 1 byte of input.
+ v = ((tail[0] & 0xff) << 16) |
+ ((tail[1] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ tailLen = 0;
+ }
+ break;
+ }
+
+ if (v != -1) {
+ output[op++] = alphabet[(v >> 18) & 0x3f];
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ // At this point either there is no tail, or there are fewer
+ // than 3 bytes of input available.
+
+ // The main loop, turning 3 input bytes into 4 output bytes on
+ // each iteration.
+ while (p+3 <= len) {
+ v = ((input[p] & 0xff) << 16) |
+ ((input[p+1] & 0xff) << 8) |
+ (input[p+2] & 0xff);
+ output[op] = alphabet[(v >> 18) & 0x3f];
+ output[op+1] = alphabet[(v >> 12) & 0x3f];
+ output[op+2] = alphabet[(v >> 6) & 0x3f];
+ output[op+3] = alphabet[v & 0x3f];
+ p += 3;
+ op += 4;
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ if (finish) {
+ // Finish up the tail of the input. Note that we need to
+ // consume any bytes in tail before any bytes
+ // remaining in input; there should be at most two bytes
+ // total.
+
+ if (p-tailLen == len-1) {
+ int t = 0;
+ v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4;
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (p-tailLen == len-2) {
+ int t = 0;
+ v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) |
+ (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2);
+ tailLen -= t;
+ output[op++] = alphabet[(v >> 12) & 0x3f];
+ output[op++] = alphabet[(v >> 6) & 0x3f];
+ output[op++] = alphabet[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+
+ assert tailLen == 0;
+ assert p == len;
+ } else {
+ // Save the leftovers in tail to be consumed on the next
+ // call to encodeInternal.
+
+ if (p == len-1) {
+ tail[tailLen++] = input[p];
+ } else if (p == len-2) {
+ tail[tailLen++] = input[p];
+ tail[tailLen++] = input[p+1];
+ }
+ }
+
+ this.op = op;
+ this.count = count;
+
+ return true;
+ }
+ }
+
+ private Base64() { } // don't instantiate
+}
diff --git a/core/java/android/util/Base64InputStream.java b/core/java/android/util/Base64InputStream.java
new file mode 100644
index 0000000..da3911d
--- /dev/null
+++ b/core/java/android/util/Base64InputStream.java
@@ -0,0 +1,153 @@
+/*
+ * 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 java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An InputStream that does Base64 decoding on the data read through
+ * it.
+ */
+public class Base64InputStream extends FilterInputStream {
+ private final Base64.Coder coder;
+
+ private static byte[] EMPTY = new byte[0];
+
+ private static final int BUFFER_SIZE = 2048;
+ private boolean eof;
+ private byte[] inputBuffer;
+ private int outputStart;
+ private int outputEnd;
+
+ /**
+ * An InputStream that performs Base64 decoding on the data read
+ * from the wrapped stream.
+ *
+ * @param in the InputStream to read the source data from
+ * @param flags bit flags for controlling the decoder; see the
+ * constants in {@link Base64}
+ */
+ public Base64InputStream(InputStream in, int flags) {
+ this(in, flags, false);
+ }
+
+ /**
+ * Performs Base64 encoding or decoding on the data read from the
+ * wrapped InputStream.
+ *
+ * @param in the InputStream to read the source data from
+ * @param flags bit flags for controlling the decoder; see the
+ * constants in {@link Base64}
+ * @param encode true to encode, false to decode
+ *
+ * @hide
+ */
+ public Base64InputStream(InputStream in, int flags, boolean encode) {
+ super(in);
+ eof = false;
+ inputBuffer = new byte[BUFFER_SIZE];
+ if (encode) {
+ coder = new Base64.Encoder(flags, null);
+ } else {
+ coder = new Base64.Decoder(flags, null);
+ }
+ coder.output = new byte[coder.maxOutputSize(BUFFER_SIZE)];
+ outputStart = 0;
+ outputEnd = 0;
+ }
+
+ public boolean markSupported() {
+ return false;
+ }
+
+ public void mark(int readlimit) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void reset() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void close() throws IOException {
+ in.close();
+ inputBuffer = null;
+ }
+
+ public int available() {
+ return outputEnd - outputStart;
+ }
+
+ public long skip(long n) throws IOException {
+ if (outputStart >= outputEnd) {
+ refill();
+ }
+ if (outputStart >= outputEnd) {
+ return 0;
+ }
+ long bytes = Math.min(n, outputEnd-outputStart);
+ outputStart += bytes;
+ return bytes;
+ }
+
+ public int read() throws IOException {
+ if (outputStart >= outputEnd) {
+ refill();
+ }
+ if (outputStart >= outputEnd) {
+ return -1;
+ } else {
+ return coder.output[outputStart++];
+ }
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (outputStart >= outputEnd) {
+ refill();
+ }
+ if (outputStart >= outputEnd) {
+ return -1;
+ }
+ int bytes = Math.min(len, outputEnd-outputStart);
+ System.arraycopy(coder.output, outputStart, b, off, bytes);
+ outputStart += bytes;
+ return bytes;
+ }
+
+ /**
+ * Read data from the input stream into inputBuffer, then
+ * decode/encode it into the empty coder.output, and reset the
+ * outputStart and outputEnd pointers.
+ */
+ private void refill() throws IOException {
+ if (eof) return;
+ int bytesRead = in.read(inputBuffer);
+ boolean success;
+ if (bytesRead == -1) {
+ eof = true;
+ success = coder.process(EMPTY, 0, 0, true);
+ } else {
+ success = coder.process(inputBuffer, 0, bytesRead, false);
+ }
+ if (!success) {
+ throw new IOException("bad base-64");
+ }
+ outputEnd = coder.op;
+ outputStart = 0;
+ }
+}
diff --git a/core/java/android/util/Base64OutputStream.java b/core/java/android/util/Base64OutputStream.java
new file mode 100644
index 0000000..30d632d
--- /dev/null
+++ b/core/java/android/util/Base64OutputStream.java
@@ -0,0 +1,155 @@
+/*
+ * 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 java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream that does Base64 encoding on the data written to
+ * it, writing the resulting data to another OutputStream.
+ */
+public class Base64OutputStream extends FilterOutputStream {
+ private final Base64.Coder coder;
+ private final int flags;
+
+ private byte[] buffer = null;
+ private int bpos = 0;
+
+ private static byte[] EMPTY = new byte[0];
+
+ /**
+ * Performs Base64 encoding on the data written to the stream,
+ * writing the encoded data to another OutputStream.
+ *
+ * @param out the OutputStream to write the encoded data to
+ * @param flags bit flags for controlling the encoder; see the
+ * constants in {@link Base64}
+ */
+ public Base64OutputStream(OutputStream out, int flags) {
+ this(out, flags, true);
+ }
+
+ /**
+ * Performs Base64 encoding or decoding on the data written to the
+ * stream, writing the encoded/decoded data to another
+ * OutputStream.
+ *
+ * @param out the OutputStream to write the encoded data to
+ * @param flags bit flags for controlling the encoder; see the
+ * constants in {@link Base64}
+ * @param encode true to encode, false to decode
+ *
+ * @hide
+ */
+ public Base64OutputStream(OutputStream out, int flags, boolean encode) {
+ super(out);
+ this.flags = flags;
+ if (encode) {
+ coder = new Base64.Encoder(flags, null);
+ } else {
+ coder = new Base64.Decoder(flags, null);
+ }
+ }
+
+ public void write(int b) throws IOException {
+ // To avoid invoking the encoder/decoder routines for single
+ // bytes, we buffer up calls to write(int) in an internal
+ // byte array to transform them into writes of decently-sized
+ // arrays.
+
+ if (buffer == null) {
+ buffer = new byte[1024];
+ }
+ if (bpos >= buffer.length) {
+ // internal buffer full; write it out.
+ internalWrite(buffer, 0, bpos, false);
+ bpos = 0;
+ }
+ buffer[bpos++] = (byte) b;
+ }
+
+ /**
+ * Flush any buffered data from calls to write(int). Needed
+ * before doing a write(byte[], int, int) or a close().
+ */
+ private void flushBuffer() throws IOException {
+ if (bpos > 0) {
+ internalWrite(buffer, 0, bpos, false);
+ bpos = 0;
+ }
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) return;
+ flushBuffer();
+ internalWrite(b, off, len, false);
+ }
+
+ public void close() throws IOException {
+ IOException thrown = null;
+ try {
+ flushBuffer();
+ internalWrite(EMPTY, 0, 0, true);
+ } catch (IOException e) {
+ thrown = e;
+ }
+
+ try {
+ if ((flags & Base64.NO_CLOSE) == 0) {
+ out.close();
+ } else {
+ out.flush();
+ }
+ } catch (IOException e) {
+ if (thrown != null) {
+ thrown = e;
+ }
+ }
+
+ if (thrown != null) {
+ throw thrown;
+ }
+ }
+
+ /**
+ * Write the given bytes to the encoder/decoder.
+ *
+ * @param finish true if this is the last batch of input, to cause
+ * encoder/decoder state to be finalized.
+ */
+ private void internalWrite(byte[] b, int off, int len, boolean finish) throws IOException {
+ coder.output = embiggen(coder.output, coder.maxOutputSize(len));
+ if (!coder.process(b, off, len, finish)) {
+ throw new IOException("bad base-64");
+ }
+ out.write(coder.output, 0, coder.op);
+ }
+
+ /**
+ * If b.length is at least len, return b. Otherwise return a new
+ * byte array of length len.
+ */
+ private byte[] embiggen(byte[] b, int len) {
+ if (b == null || b.length < len) {
+ return new byte[len];
+ } else {
+ return b;
+ }
+ }
+}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 81dd96e..b596d32 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,134 +16,41 @@
package android.util;
-import com.google.android.collect.Lists;
-
+import java.io.BufferedReader;
+import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
- * {@hide}
- * Dynamically defined (in terms of event types), space efficient (i.e. "tight") event logging
- * to help instrument code for large scale stability and performance monitoring.
- *
- * Note that this class contains all static methods. This is done for efficiency reasons.
- *
- * Events for the event log are self-describing binary data structures. They start with a 20 byte
- * header (generated automatically) which contains all of the following in order:
- *
- * <ul>
- * <li> Payload length: 2 bytes - length of the non-header portion </li>
- * <li> Padding: 2 bytes - no meaning at this time </li>
- * <li> Timestamp:
- * <ul>
- * <li> Seconds: 4 bytes - seconds since Epoch </li>
- * <li> Nanoseconds: 4 bytes - plus extra nanoseconds </li>
- * </ul></li>
- * <li> Process ID: 4 bytes - matching {@link android.os.Process#myPid} </li>
- * <li> Thread ID: 4 bytes - matching {@link android.os.Process#myTid} </li>
- * </li>
- * </ul>
+ * Access to the system diagnostic event record. System diagnostic events are
+ * used to record certain system-level events (such as garbage collection,
+ * activity manager state, system watchdogs, and other low level activity),
+ * which may be automatically collected and analyzed during system development.
*
- * The above is followed by a payload, comprised of the following:
- * <ul>
- * <li> Tag: 4 bytes - unique integer used to identify a particular event. This number is also
- * used as a key to map to a string that can be displayed by log reading tools.
- * </li>
- * <li> Type: 1 byte - can be either {@link #INT}, {@link #LONG}, {@link #STRING},
- * or {@link #LIST}. </li>
- * <li> Event log value: the size and format of which is one of:
- * <ul>
- * <li> INT: 4 bytes </li>
- * <li> LONG: 8 bytes </li>
- * <li> STRING:
- * <ul>
- * <li> Size of STRING: 4 bytes </li>
- * <li> The string: n bytes as specified in the size fields above. </li>
- * </ul></li>
- * <li> {@link List LIST}:
- * <ul>
- * <li> Num items: 1 byte </li>
- * <li> N value payloads, where N is the number of items specified above. </li>
- * </ul></li>
- * </ul>
- * </li>
- * <li> '\n': 1 byte - an automatically generated newline, used to help detect and recover from log
- * corruption and enable standard unix tools like grep, tail and wc to operate
- * on event logs. </li>
- * </ul>
+ * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
+ * These diagnostic events are for system integrators, not application authors.
*
- * Note that all output is done in the endian-ness of the device (as determined
- * by {@link ByteOrder#nativeOrder()}).
+ * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
+ * They carry a payload of one or more int, long, or String values. The
+ * event-log-tags file defines the payload contents for each type code.
*/
-
public class EventLog {
+ private static final String TAG = "EventLog";
- // Value types
- public static final byte INT = 0;
- public static final byte LONG = 1;
- public static final byte STRING = 2;
- public static final byte LIST = 3;
+ private static final String TAGS_FILE = "/system/etc/event-log-tags";
+ private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
+ private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
+ private static HashMap<String, Integer> sTagCodes = null;
+ private static HashMap<Integer, String> sTagNames = null;
- /**
- * An immutable tuple used to log a heterogeneous set of loggable items.
- * The items can be Integer, Long, String, or {@link List}.
- * The maximum number of items is 127
- */
- public static final class List {
- private Object[] mItems;
-
- /**
- * Get a particular tuple item
- * @param pos The position of the item in the tuple
- */
- public final Object getItem(int pos) {
- return mItems[pos];
- }
-
- /**
- * Get the number of items in the tuple.
- */
- public final byte getNumItems() {
- return (byte) mItems.length;
- }
-
- /**
- * Create a new tuple.
- * @param items The items to create the tuple with, as varargs.
- * @throws IllegalArgumentException if the arguments are too few (0),
- * too many, or aren't loggable types.
- */
- public List(Object... items) throws IllegalArgumentException {
- if (items.length > Byte.MAX_VALUE) {
- throw new IllegalArgumentException(
- "A List must have fewer than "
- + Byte.MAX_VALUE + " items in it.");
- }
- for (int i = 0; i < items.length; i++) {
- final Object item = items[i];
- if (item == null) {
- // Would be nice to be able to write null strings...
- items[i] = "";
- } else if (!(item instanceof List ||
- item instanceof String ||
- item instanceof Integer ||
- item instanceof Long)) {
- throw new IllegalArgumentException(
- "Attempt to create a List with illegal item type.");
- }
- }
- this.mItems = items;
- }
- }
-
- /**
- * A previously logged event read from the logs.
- */
+ /** A previously logged event read from the logs. */
public static final class Event {
private final ByteBuffer mBuffer;
@@ -158,77 +65,84 @@ public class EventLog {
private static final int TAG_OFFSET = 20;
private static final int DATA_START = 24;
+ // Value types
+ private static final byte INT_TYPE = 0;
+ private static final byte LONG_TYPE = 1;
+ private static final byte STRING_TYPE = 2;
+ private static final byte LIST_TYPE = 3;
+
/** @param data containing event, read from the system */
- public Event(byte[] data) {
+ /*package*/ Event(byte[] data) {
mBuffer = ByteBuffer.wrap(data);
mBuffer.order(ByteOrder.nativeOrder());
}
+ /** @return the process ID which wrote the log entry */
public int getProcessId() {
return mBuffer.getInt(PROCESS_OFFSET);
}
+ /** @return the thread ID which wrote the log entry */
public int getThreadId() {
return mBuffer.getInt(THREAD_OFFSET);
}
+ /** @return the wall clock time when the entry was written */
public long getTimeNanos() {
return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
+ mBuffer.getInt(NANOSECONDS_OFFSET);
}
+ /** @return the type tag code of the entry */
public int getTag() {
return mBuffer.getInt(TAG_OFFSET);
}
- /** @return one of Integer, Long, String, or List. */
+ /** @return one of Integer, Long, String, null, or Object[] of same. */
public synchronized Object getData() {
- mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
- mBuffer.position(DATA_START); // Just after the tag.
- return decodeObject();
- }
-
- public byte[] getRawData() {
- return mBuffer.array();
+ try {
+ mBuffer.limit(PAYLOAD_START + mBuffer.getShort(LENGTH_OFFSET));
+ mBuffer.position(DATA_START); // Just after the tag.
+ return decodeObject();
+ } catch (IllegalArgumentException e) {
+ Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
+ return null;
+ } catch (BufferUnderflowException e) {
+ Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
+ return null;
+ }
}
/** @return the loggable item at the current position in mBuffer. */
private Object decodeObject() {
- if (mBuffer.remaining() < 1) return null;
- switch (mBuffer.get()) {
- case INT:
- if (mBuffer.remaining() < 4) return null;
+ byte type = mBuffer.get();
+ switch (type) {
+ case INT_TYPE:
return (Integer) mBuffer.getInt();
- case LONG:
- if (mBuffer.remaining() < 8) return null;
+ case LONG_TYPE:
return (Long) mBuffer.getLong();
- case STRING:
+ case STRING_TYPE:
try {
- if (mBuffer.remaining() < 4) return null;
int length = mBuffer.getInt();
- if (length < 0 || mBuffer.remaining() < length) return null;
int start = mBuffer.position();
mBuffer.position(start + length);
return new String(mBuffer.array(), start, length, "UTF-8");
} catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // UTF-8 is guaranteed.
+ Log.wtf(TAG, "UTF-8 is not supported", e);
+ return null;
}
- case LIST:
- if (mBuffer.remaining() < 1) return null;
+ case LIST_TYPE:
int length = mBuffer.get();
- if (length < 0) return null;
+ if (length < 0) length += 256; // treat as signed byte
Object[] array = new Object[length];
- for (int i = 0; i < length; ++i) {
- array[i] = decodeObject();
- if (array[i] == null) return null;
- }
- return new List(array);
+ for (int i = 0; i < length; ++i) array[i] = decodeObject();
+ return array;
default:
- return null;
+ throw new IllegalArgumentException("Unknown entry type: " + type);
}
}
}
@@ -236,46 +150,36 @@ public class EventLog {
// We assume that the native methods deal with any concurrency issues.
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, int value);
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param value A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, long value);
/**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param str A value to log
* @return The number of bytes written
*/
public static native int writeEvent(int tag, String str);
/**
- * Send an event log message.
- * @param tag An event identifer
- * @param list A {@link List} to log
- * @return The number of bytes written
- */
- public static native int writeEvent(int tag, List list);
-
- /**
- * Send an event log message.
- * @param tag An event identifer
+ * Record an event log message.
+ * @param tag The event type tag code
* @param list A list of values to log
* @return The number of bytes written
*/
- public static int writeEvent(int tag, Object... list) {
- return writeEvent(tag, new List(list));
- }
+ public static native int writeEvent(int tag, Object... list);
/**
* Read events from the log, filtered by type.
@@ -287,11 +191,65 @@ public class EventLog {
throws IOException;
/**
- * Read events from a file.
- * @param path to read from
- * @param output container to add events into
- * @throws IOException if something goes wrong reading events
+ * Get the name associated with an event type tag code.
+ * @param tag code to look up
+ * @return the name of the tag, or null if no tag has that number
*/
- public static native void readEvents(String path, Collection<Event> output)
- throws IOException;
+ public static String getTagName(int tag) {
+ readTagsFile();
+ return sTagNames.get(tag);
+ }
+
+ /**
+ * Get the event type tag code associated with an event name.
+ * @param name of event to look up
+ * @return the tag code, or -1 if no tag has that name
+ */
+ public static int getTagCode(String name) {
+ readTagsFile();
+ Integer code = sTagCodes.get(name);
+ return code != null ? code : -1;
+ }
+
+ /**
+ * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
+ */
+ private static synchronized void readTagsFile() {
+ if (sTagCodes != null && sTagNames != null) return;
+
+ sTagCodes = new HashMap<String, Integer>();
+ sTagNames = new HashMap<Integer, String>();
+
+ Pattern comment = Pattern.compile(COMMENT_PATTERN);
+ Pattern tag = Pattern.compile(TAG_PATTERN);
+ BufferedReader reader = null;
+ String line;
+
+ try {
+ reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
+ while ((line = reader.readLine()) != null) {
+ if (comment.matcher(line).matches()) continue;
+
+ Matcher m = tag.matcher(line);
+ if (!m.matches()) {
+ Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
+ continue;
+ }
+
+ try {
+ int num = Integer.parseInt(m.group(1));
+ String name = m.group(2);
+ sTagCodes.put(name, num);
+ sTagNames.put(num, name);
+ } catch (NumberFormatException e) {
+ Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
+ // Leave the maps existing but unpopulated
+ } finally {
+ try { if (reader != null) reader.close(); } catch (IOException e) {}
+ }
+ }
}
diff --git a/core/java/android/util/EventLogTags.java b/core/java/android/util/EventLogTags.java
index be905e3..5cf5332 100644
--- a/core/java/android/util/EventLogTags.java
+++ b/core/java/android/util/EventLogTags.java
@@ -25,16 +25,14 @@ import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-/** Parsed representation of /etc/event-log-tags. */
+/**
+ * @deprecated This class is no longer functional.
+ * Use {@link android.util.EventLog} instead.
+ */
public class EventLogTags {
- private final static String TAG = "EventLogTags";
-
- private final static String TAGS_FILE = "/etc/event-log-tags";
-
public static class Description {
public final int mTag;
public final String mName;
- // TODO: Parse parameter descriptions when anyone has a use for them.
Description(int tag, String name) {
mTag = tag;
@@ -42,49 +40,11 @@ public class EventLogTags {
}
}
- private final static Pattern COMMENT_PATTERN = Pattern.compile(
- "^\\s*(#.*)?$");
-
- private final static Pattern TAG_PATTERN = Pattern.compile(
- "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$");
+ public EventLogTags() throws IOException {}
- private final HashMap<String, Description> mNameMap =
- new HashMap<String, Description>();
-
- private final HashMap<Integer, Description> mTagMap =
- new HashMap<Integer, Description>();
-
- public EventLogTags() throws IOException {
- this(new BufferedReader(new FileReader(TAGS_FILE), 256));
- }
+ public EventLogTags(BufferedReader input) throws IOException {}
- public EventLogTags(BufferedReader input) throws IOException {
- String line;
- while ((line = input.readLine()) != null) {
- Matcher m = COMMENT_PATTERN.matcher(line);
- if (m.matches()) continue;
+ public Description get(String name) { return null; }
- m = TAG_PATTERN.matcher(line);
- if (m.matches()) {
- try {
- int tag = Integer.parseInt(m.group(1));
- Description d = new Description(tag, m.group(2));
- mNameMap.put(d.mName, d);
- mTagMap.put(d.mTag, d);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Error in event log tags entry: " + line, e);
- }
- } else {
- Log.e(TAG, "Can't parse event log tags entry: " + line);
- }
- }
- }
-
- public Description get(String name) {
- return mNameMap.get(name);
- }
-
- public Description get(int tag) {
- return mTagMap.get(tag);
- }
+ public Description get(int tag) { return null; }
}
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 2572679..e111669 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -81,6 +81,13 @@ public final class Log {
*/
public static final int ASSERT = 7;
+ /**
+ * Exception class used to capture a stack trace in {@link #wtf()}.
+ */
+ private static class TerribleFailure extends Exception {
+ TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
+ }
+
private Log() {
}
@@ -91,7 +98,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int v(String tag, String msg) {
- return println(VERBOSE, tag, msg);
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
/**
@@ -102,7 +109,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int v(String tag, String msg, Throwable tr) {
- return println(VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+ return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -112,7 +119,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int d(String tag, String msg) {
- return println(DEBUG, tag, msg);
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/**
@@ -123,7 +130,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int d(String tag, String msg, Throwable tr) {
- return println(DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+ return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -133,7 +140,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int i(String tag, String msg) {
- return println(INFO, tag, msg);
+ return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
/**
@@ -144,7 +151,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int i(String tag, String msg, Throwable tr) {
- return println(INFO, tag, msg + '\n' + getStackTraceString(tr));
+ return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
/**
@@ -154,7 +161,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int w(String tag, String msg) {
- return println(WARN, tag, msg);
+ return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
/**
@@ -165,29 +172,29 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, String msg, Throwable tr) {
- return println(WARN, tag, msg + '\n' + getStackTraceString(tr));
+ return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
/**
* Checks to see whether or not a log for the specified tag is loggable at the specified level.
- *
+ *
* The default level of any tag is set to INFO. This means that any level above and including
* INFO will be logged. Before you make any calls to a logging method you should check to see
* if your tag should be logged. You can change the default level by setting a system property:
* 'setprop log.tag.&lt;YOUR_LOG_TAG> &lt;LEVEL>'
- * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPRESS will
+ * Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will
* turn off all logging for your tag. You can also create a local.prop file that with the
* following in it:
* 'log.tag.&lt;YOUR_LOG_TAG>=&lt;LEVEL>'
* and place that in /data/local.prop.
- *
+ *
* @param tag The tag to check.
* @param level The level to check.
* @return Whether or not that this is allowed to be logged.
* @throws IllegalArgumentException is thrown if the tag.length() > 23.
*/
public static native boolean isLoggable(String tag, int level);
-
+
/*
* Send a {@link #WARN} log message and log the exception.
* @param tag Used to identify the source of a log message. It usually identifies
@@ -195,7 +202,7 @@ public final class Log {
* @param tr An exception to log
*/
public static int w(String tag, Throwable tr) {
- return println(WARN, tag, getStackTraceString(tr));
+ return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
/**
@@ -205,7 +212,7 @@ public final class Log {
* @param msg The message you would like logged.
*/
public static int e(String tag, String msg) {
- return println(ERROR, tag, msg);
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
/**
@@ -216,9 +223,44 @@ public final class Log {
* @param tr An exception to log
*/
public static int e(String tag, String msg, Throwable tr) {
- int r = println(ERROR, tag, msg + '\n' + getStackTraceString(tr));
- RuntimeInit.reportException(tag, tr, false); // asynchronous
- return r;
+ return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
+ }
+
+ /**
+ * What a Terrible Failure: Report a condition that should never happen.
+ * The error will always be logged at level ASSERT with the call stack.
+ * Depending on system configuration, a report may be added to the
+ * {@link android.os.DropBoxManager} and/or the process may be terminated
+ * immediately with an error dialog.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ */
+ public static int wtf(String tag, String msg) {
+ return wtf(tag, msg, null);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, String)}, with an exception to log.
+ * @param tag Used to identify the source of a log message.
+ * @param tr An exception to log.
+ */
+ public static int wtf(String tag, Throwable tr) {
+ return wtf(tag, tr.getMessage(), tr);
+ }
+
+ /**
+ * What a Terrible Failure: Report an exception that should never happen.
+ * Similar to {@link #wtf(String, Throwable)}, with a message as well.
+ * @param tag Used to identify the source of a log message.
+ * @param msg The message you would like logged.
+ * @param tr An exception to log. May be null.
+ */
+ public static int wtf(String tag, String msg, Throwable tr) {
+ tr = new TerribleFailure(msg, tr);
+ int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, getStackTraceString(tr));
+ RuntimeInit.wtf(tag, tr);
+ return bytes;
}
/**
@@ -243,5 +285,15 @@ public final class Log {
* @param msg The message you would like logged.
* @return The number of bytes written.
*/
- public static native int println(int priority, String tag, String msg);
+ public static int println(int priority, String tag, String msg) {
+ return println_native(LOG_ID_MAIN, priority, tag, msg);
+ }
+
+ /** @hide */ public static final int LOG_ID_MAIN = 0;
+ /** @hide */ public static final int LOG_ID_RADIO = 1;
+ /** @hide */ public static final int LOG_ID_EVENTS = 2;
+ /** @hide */ public static final int LOG_ID_SYSTEM = 3;
+
+ /** @hide */ public static native int println_native(int bufID,
+ int priority, String tag, String msg);
}
diff --git a/core/java/android/util/LogPrinter.java b/core/java/android/util/LogPrinter.java
index 643b8d3..68f64d0 100644
--- a/core/java/android/util/LogPrinter.java
+++ b/core/java/android/util/LogPrinter.java
@@ -23,6 +23,7 @@ package android.util;
public class LogPrinter implements Printer {
private final int mPriority;
private final String mTag;
+ private final int mBuffer;
/**
* Create a new Printer that sends to the log with the given priority
@@ -39,9 +40,20 @@ public class LogPrinter implements Printer {
public LogPrinter(int priority, String tag) {
mPriority = priority;
mTag = tag;
+ mBuffer = Log.LOG_ID_MAIN;
+ }
+
+ /**
+ * @hide
+ * Same as above, but buffer is one of the LOG_ID_ constants from android.util.Log.
+ */
+ public LogPrinter(int priority, String tag, int buffer) {
+ mPriority = priority;
+ mTag = tag;
+ mBuffer = buffer;
}
public void println(String x) {
- Log.println(mPriority, mTag, x);
+ Log.println_native(mBuffer, mPriority, mTag, x);
}
}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
index d90045f..1ec8be6 100644
--- a/core/java/android/util/LongSparseArray.java
+++ b/core/java/android/util/LongSparseArray.java
@@ -48,6 +48,28 @@ public class LongSparseArray<E> {
mValues = new Object[initialCapacity];
mSize = 0;
}
+
+ /**
+ * @return A copy of all keys contained in the sparse array.
+ */
+ public long[] getKeys() {
+ int length = mKeys.length;
+ long[] result = new long[length];
+ System.arraycopy(mKeys, 0, result, 0, length);
+ return result;
+ }
+
+ /**
+ * Sets all supplied keys to the given unique value.
+ * @param keys Keys to set
+ * @param uniqueValue Value to set all supplied keys to
+ */
+ public void setValues(long[] keys, E uniqueValue) {
+ int length = keys.length;
+ for (int i = 0; i < length; i++) {
+ put(keys[i], uniqueValue);
+ }
+ }
/**
* Gets the Object mapped from the specified key, or <code>null</code>
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
new file mode 100644
index 0000000..5cbfd29
--- /dev/null
+++ b/core/java/android/util/Patterns.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Commonly used regular expression patterns.
+ */
+public class Patterns {
+ /**
+ * Regular expression to match all IANA top-level domains.
+ * List accurate as of 2010/02/05. List taken from:
+ * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+ * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
+ */
+ public static final String TOP_LEVEL_DOMAIN_STR =
+ "((aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ + "|(biz|b[abdefghijmnorstvwyz])"
+ + "|(cat|com|coop|c[acdfghiklmnoruvxyz])"
+ + "|d[ejkmoz]"
+ + "|(edu|e[cegrstu])"
+ + "|f[ijkmor]"
+ + "|(gov|g[abdefghilmnpqrstuwy])"
+ + "|h[kmnrtu]"
+ + "|(info|int|i[delmnoqrst])"
+ + "|(jobs|j[emop])"
+ + "|k[eghimnprwyz]"
+ + "|l[abcikrstuvy]"
+ + "|(mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ + "|(name|net|n[acefgilopruz])"
+ + "|(org|om)"
+ + "|(pro|p[aefghklmnrstwy])"
+ + "|qa"
+ + "|r[eosuw]"
+ + "|s[abcdeghijklmnortuvyz]"
+ + "|(tel|travel|t[cdfghjklmnoprtvwz])"
+ + "|u[agksyz]"
+ + "|v[aceginu]"
+ + "|w[fs]"
+ + "|(xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)"
+ + "|y[etu]"
+ + "|z[amw])";
+
+ /**
+ * Regular expression pattern to match all IANA top-level domains.
+ */
+ public static final Pattern TOP_LEVEL_DOMAIN =
+ Pattern.compile(TOP_LEVEL_DOMAIN_STR);
+
+ /**
+ * Regular expression to match all IANA top-level domains for WEB_URL.
+ * List accurate as of 2010/02/05. List taken from:
+ * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+ * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
+ */
+ public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
+ "(?:"
+ + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ + "|(?:biz|b[abdefghijmnorstvwyz])"
+ + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ + "|d[ejkmoz]"
+ + "|(?:edu|e[cegrstu])"
+ + "|f[ijkmor]"
+ + "|(?:gov|g[abdefghilmnpqrstuwy])"
+ + "|h[kmnrtu]"
+ + "|(?:info|int|i[delmnoqrst])"
+ + "|(?:jobs|j[emop])"
+ + "|k[eghimnprwyz]"
+ + "|l[abcikrstuvy]"
+ + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ + "|(?:name|net|n[acefgilopruz])"
+ + "|(?:org|om)"
+ + "|(?:pro|p[aefghklmnrstwy])"
+ + "|qa"
+ + "|r[eosuw]"
+ + "|s[abcdeghijklmnortuvyz]"
+ + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ + "|u[agksyz]"
+ + "|v[aceginu]"
+ + "|w[fs]"
+ + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-zckzah)"
+ + "|y[etu]"
+ + "|z[amw]))";
+
+ /**
+ * Good characters for Internationalized Resource Identifiers (IRI).
+ * This comprises most common used Unicode characters allowed in IRI
+ * as detailed in RFC 3987.
+ * Specifically, those two byte Unicode characters are not included.
+ */
+ public static final String GOOD_IRI_CHAR =
+ "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+
+ /**
+ * Regular expression pattern to match most part of RFC 3987
+ * Internationalized URLs, aka IRIs. Commonly used Unicode characters are
+ * added.
+ */
+ public static final Pattern WEB_URL = Pattern.compile(
+ "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ + "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ + "|[1-9][0-9]|[0-9])))"
+ + "(?:\\:\\d{1,5})?)" // plus option port number
+ + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ + "(?:\\b|$)"); // and finally, a word boundary or end of
+ // input. This is to stop foo.sure from
+ // matching as foo.su
+
+ public static final Pattern IP_ADDRESS
+ = Pattern.compile(
+ "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+ + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+ + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ + "|[1-9][0-9]|[0-9]))");
+
+ public static final Pattern DOMAIN_NAME
+ = Pattern.compile(
+ "(((([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]*)*[" + GOOD_IRI_CHAR + "]\\.)+"
+ + TOP_LEVEL_DOMAIN + ")|"
+ + IP_ADDRESS + ")");
+
+ public static final Pattern EMAIL_ADDRESS
+ = Pattern.compile(
+ "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
+ "\\@" +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
+ "(" +
+ "\\." +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
+ ")+"
+ );
+
+ /**
+ * This pattern is intended for searching for things that look like they
+ * might be phone numbers in arbitrary text, not for validating whether
+ * something is in fact a phone number. It will miss many things that
+ * are legitimate phone numbers.
+ *
+ * <p> The pattern matches the following:
+ * <ul>
+ * <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
+ * may follow.
+ * <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
+ * <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
+ * </ul>
+ */
+ public static final Pattern PHONE
+ = Pattern.compile( // sdd = space, dot, or dash
+ "(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
+ + "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
+ + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+
+ /**
+ * Convenience method to take all of the non-null matching groups in a
+ * regex Matcher and return them as a concatenated string.
+ *
+ * @param matcher The Matcher object from which grouped text will
+ * be extracted
+ *
+ * @return A String comprising all of the non-null matched
+ * groups concatenated together
+ */
+ public static final String concatGroups(Matcher matcher) {
+ StringBuilder b = new StringBuilder();
+ final int numGroups = matcher.groupCount();
+
+ for (int i = 1; i <= numGroups; i++) {
+ String s = matcher.group(i);
+
+ System.err.println("Group(" + i + ") : " + s);
+
+ if (s != null) {
+ b.append(s);
+ }
+ }
+
+ return b.toString();
+ }
+
+ /**
+ * Convenience method to return only the digits and plus signs
+ * in the matching string.
+ *
+ * @param matcher The Matcher object from which digits and plus will
+ * be extracted
+ *
+ * @return A String comprising all of the digits and plus in
+ * the match
+ */
+ public static final String digitsAndPlusOnly(Matcher matcher) {
+ StringBuilder buffer = new StringBuilder();
+ String matchingRegion = matcher.group();
+
+ for (int i = 0, size = matchingRegion.length(); i < size; i++) {
+ char character = matchingRegion.charAt(i);
+
+ if (character == '+' || Character.isDigit(character)) {
+ buffer.append(character);
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Do not create this static utility class.
+ */
+ private Patterns() {}
+}
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
new file mode 100644
index 0000000..ecf5ea1
--- /dev/null
+++ b/core/java/android/util/Slog.java
@@ -0,0 +1,85 @@
+/*
+ * 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;
+
+import com.android.internal.os.RuntimeInit;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * @hide
+ */
+public final class Slog {
+
+ private Slog() {
+ }
+
+ public static int v(String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
+ }
+
+ public static int v(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int d(String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
+ }
+
+ public static int d(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int i(String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
+ }
+
+ public static int i(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int w(String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
+ }
+
+ public static int w(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int w(String tag, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, Log.getStackTraceString(tr));
+ }
+
+ public static int e(String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
+ }
+
+ public static int e(String tag, String msg, Throwable tr) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int println(int priority, String tag, String msg) {
+ return Log.println_native(Log.LOG_ID_SYSTEM, priority, tag, msg);
+ }
+}
+
diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java
index 12d6dd9..ecedbe1 100644
--- a/core/java/android/util/XmlPullAttributes.java
+++ b/core/java/android/util/XmlPullAttributes.java
@@ -19,6 +19,7 @@ package android.util;
import org.xmlpull.v1.XmlPullParser;
import android.util.AttributeSet;
+
import com.android.internal.util.XmlUtils;
/**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index b055d51..fabe5c8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -45,41 +45,76 @@ public class Display
}
/**
- * @return index of this display.
+ * Returns the index of this display. This is currently undefined; do
+ * not use.
*/
public int getDisplayId() {
return mDisplay;
}
/**
- * @return the number of displays connected to the device.
+ * Returns the number of displays connected to the device. This is
+ * currently undefined; do not use.
*/
native static int getDisplayCount();
/**
- * @return width of this display in pixels.
+ * Returns the raw width of the display, in pixels. Note that this
+ * should <em>not</em> generally be used for computing layouts, since
+ * a device will typically have screen decoration (such as a status bar)
+ * along the edges of the display that reduce the amount of application
+ * space available from the raw size returned here. This value is
+ * adjusted for you based on the current rotation of the display.
*/
native public int getWidth();
/**
- * @return height of this display in pixels.
+ * Returns the raw height of the display, in pixels. Note that this
+ * should <em>not</em> generally be used for computing layouts, since
+ * a device will typically have screen decoration (such as a status bar)
+ * along the edges of the display that reduce the amount of application
+ * space available from the raw size returned here. This value is
+ * adjusted for you based on the current rotation of the display.
*/
native public int getHeight();
/**
+ * Returns the rotation of the screen from its "natural" orientation.
+ * The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0}
+ * (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90},
+ * {@link Surface#ROTATION_180 Surface.ROTATION_180}, or
+ * {@link Surface#ROTATION_270 Surface.ROTATION_270}. For
+ * example, if a device has a naturally tall screen, and the user has
+ * turned it on its side to go into a landscape orientation, the value
+ * returned here may be either {@link Surface#ROTATION_90 Surface.ROTATION_90}
+ * or {@link Surface#ROTATION_270 Surface.ROTATION_270} depending on
+ * the direction it was turned. The angle is the rotation of the drawn
+ * graphics on the screen, which is the opposite direction of the physical
+ * rotation of the device. For example, if the device is rotated 90
+ * degrees counter-clockwise, to compensate rendering will be rotated by
+ * 90 degrees clockwise and thus the returned value here will be
+ * {@link Surface#ROTATION_90 Surface.ROTATION_90}.
+ */
+ public int getRotation() {
+ return getOrientation();
+ }
+
+ /**
+ * @deprecated use {@link #getRotation}
* @return orientation of this display.
*/
- native public int getOrientation();
+ @Deprecated native public int getOrientation();
/**
- * @return pixel format of this display.
+ * Return the native pixel format of the display. The returned value
+ * may be one of the constants int {@link android.graphics.PixelFormat}.
*/
public int getPixelFormat() {
return mPixelFormat;
}
/**
- * @return refresh rate of this display in frames per second.
+ * Return the refresh rate of this display in frames per second.
*/
public float getRefreshRate() {
return mRefreshRate;
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 1e558be..c1e1049 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -16,9 +16,10 @@
package android.view;
+import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
-import android.content.Context;
/**
* Detects various gestures and events using the supplied {@link MotionEvent}s.
@@ -231,6 +232,13 @@ public class GestureDetector {
private float mLastMotionX;
private boolean mIsLongpressEnabled;
+
+ /**
+ * True if we are at a target API level of >= Froyo or the developer can
+ * explicitly set it. If true, input events with > 1 pointer will be ignored
+ * so we can work side by side with multitouch gesture detectors.
+ */
+ private boolean mIgnoreMultitouch;
/**
* Determines speed during touch scrolling
@@ -336,6 +344,26 @@ public class GestureDetector {
* @throws NullPointerException if {@code listener} is null.
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
+ this(context, listener, handler, context != null &&
+ context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
+ }
+
+ /**
+ * Creates a GestureDetector with the supplied listener.
+ * You may only use this constructor from a UI thread (this is the usual situation).
+ * @see android.os.Handler#Handler()
+ *
+ * @param context the application's context
+ * @param listener the listener invoked for all the callbacks, this must
+ * not be null.
+ * @param handler the handler to use
+ * @param ignoreMultitouch whether events involving more than one pointer should
+ * be ignored.
+ *
+ * @throws NullPointerException if {@code listener} is null.
+ */
+ public GestureDetector(Context context, OnGestureListener listener, Handler handler,
+ boolean ignoreMultitouch) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
@@ -345,14 +373,15 @@ public class GestureDetector {
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
- init(context);
+ init(context, ignoreMultitouch);
}
- private void init(Context context) {
+ private void init(Context context, boolean ignoreMultitouch) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;
+ mIgnoreMultitouch = ignoreMultitouch;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop;
@@ -425,7 +454,26 @@ public class GestureDetector {
boolean handled = false;
- switch (action) {
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mIgnoreMultitouch) {
+ // Multitouch event - abort.
+ cancel();
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ // Ending a multitouch gesture and going back to 1 finger
+ if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
+ int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
+ mLastMotionX = ev.getX(index);
+ mLastMotionY = ev.getY(index);
+ mVelocityTracker.recycle();
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ break;
+
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
@@ -446,6 +494,9 @@ public class GestureDetector {
mLastMotionX = x;
mLastMotionY = y;
+ if (mCurrentDownEvent != null) {
+ mCurrentDownEvent.recycle();
+ }
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
@@ -462,7 +513,7 @@ public class GestureDetector {
break;
case MotionEvent.ACTION_MOVE:
- if (mInLongPress) {
+ if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) {
break;
}
final float scrollX = mLastMotionX - x;
@@ -514,10 +565,14 @@ public class GestureDetector {
if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)){
- handled = mListener.onFling(mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
+ handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
- mPreviousUpEvent = MotionEvent.obtain(ev);
+ if (mPreviousUpEvent != null) {
+ mPreviousUpEvent.recycle();
+ }
+ // Hold the event we obtained above - listeners may have changed the original.
+ mPreviousUpEvent = currentUpEvent;
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
@@ -525,21 +580,24 @@ public class GestureDetector {
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
- mHandler.removeMessages(SHOW_PRESS);
- mHandler.removeMessages(LONG_PRESS);
- mHandler.removeMessages(TAP);
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mIsDoubleTapping = false;
- mStillDown = false;
- if (mInLongPress) {
- mInLongPress = false;
- break;
- }
+ cancel();
}
return handled;
}
+ private void cancel() {
+ mHandler.removeMessages(SHOW_PRESS);
+ mHandler.removeMessages(LONG_PRESS);
+ mHandler.removeMessages(TAP);
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ mIsDoubleTapping = false;
+ mStillDown = false;
+ if (mInLongPress) {
+ mInLongPress = false;
+ }
+ }
+
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index e1f2823..8f40260 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -36,6 +36,11 @@ public class HapticFeedbackConstants {
public static final int VIRTUAL_KEY = 1;
/**
+ * The user has pressed a soft keyboard key.
+ */
+ public static final int KEYBOARD_TAP = 3;
+
+ /**
* This is a private constant. Feel free to renumber as desired.
* @hide
*/
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 71302cb..3b09808 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -17,6 +17,7 @@
package android.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -44,7 +45,7 @@ oneway interface IWindow {
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets,
- boolean reportDraw);
+ boolean reportDraw, in Configuration newConfig);
void dispatchKey(in KeyEvent event);
void dispatchPointer(in MotionEvent event, long eventTime, boolean callWhenDone);
void dispatchTrackball(in MotionEvent event, long eventTime, boolean callWhenDone);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0ebe360..9b7b2f4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -64,8 +64,6 @@ interface IWindowManager
void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen);
void setAppGroupId(IBinder token, int groupId);
- Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
- IBinder freezeThisOneIfNeeded);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
@@ -85,6 +83,13 @@ interface IWindowManager
void moveAppTokensToTop(in List<IBinder> tokens);
void moveAppTokensToBottom(in List<IBinder> tokens);
+ // Re-evaluate the current orientation from the caller's state.
+ // If there is a change, the new Configuration is returned and the
+ // caller must call setNewConfiguration() sometime later.
+ Configuration updateOrientationFromAppTokens(in Configuration currentConfig,
+ IBinder freezeThisOneIfNeeded);
+ void setNewConfiguration(in Configuration config);
+
// these require DISABLE_KEYGUARD permission
void disableKeyguard(IBinder token, String tag);
void reenableKeyguard(IBinder token);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index b6b009b..01f07d6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -17,6 +17,7 @@
package android.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -63,6 +64,9 @@ interface IWindowSession {
* contents to make sure the user can see it. This is different than
* <var>outContentInsets</var> in that these insets change transiently,
* so complex relayout of the window should not happen based on them.
+ * @param outConfiguration New configuration of window, if it is now
+ * becoming visible and the global configuration has changed since it
+ * was last displayed.
* @param outSurface Object in which is placed the new display surface.
*
* @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS},
@@ -71,7 +75,8 @@ interface IWindowSession {
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
boolean insetsPending, out Rect outFrame, out Rect outContentInsets,
- out Rect outVisibleInsets, out Surface outSurface);
+ out Rect outVisibleInsets, out Configuration outConfig,
+ out Surface outSurface);
/**
* Give the window manager a hint of the part of the window that is
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ca907af..d648e96 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -76,61 +76,81 @@ public final class MotionEvent implements Parcelable {
public static final int ACTION_POINTER_DOWN = 5;
/**
- * Synonym for {@link #ACTION_POINTER_DOWN} with
- * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone done.
+ * A non-primary pointer has gone up. The bits in
+ * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
*/
- public static final int ACTION_POINTER_1_DOWN = ACTION_POINTER_DOWN | 0x0000;
+ public static final int ACTION_POINTER_UP = 6;
/**
- * Synonym for {@link #ACTION_POINTER_DOWN} with
- * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone done.
+ * 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.
*/
- public static final int ACTION_POINTER_2_DOWN = ACTION_POINTER_DOWN | 0x0100;
+ public static final int ACTION_POINTER_INDEX_MASK = 0xff00;
/**
- * Synonym for {@link #ACTION_POINTER_DOWN} with
- * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone done.
+ * Bit shift for the action bits holding the pointer index as
+ * defined by {@link #ACTION_POINTER_INDEX_MASK}.
*/
- public static final int ACTION_POINTER_3_DOWN = ACTION_POINTER_DOWN | 0x0200;
+ public static final int ACTION_POINTER_INDEX_SHIFT = 8;
/**
- * A non-primary pointer has gone up. The bits in
- * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
*/
- public static final int ACTION_POINTER_UP = 6;
+ @Deprecated
+ public static final int ACTION_POINTER_1_DOWN = ACTION_POINTER_DOWN | 0x0000;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
+ */
+ @Deprecated
+ public static final int ACTION_POINTER_2_DOWN = ACTION_POINTER_DOWN | 0x0100;
/**
- * Synonym for {@link #ACTION_POINTER_UP} with
- * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone up.
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_DOWN}.
*/
+ @Deprecated
+ public static final int ACTION_POINTER_3_DOWN = ACTION_POINTER_DOWN | 0x0200;
+
+ /**
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
+ */
+ @Deprecated
public static final int ACTION_POINTER_1_UP = ACTION_POINTER_UP | 0x0000;
/**
- * Synonym for {@link #ACTION_POINTER_UP} with
- * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone up.
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
*/
+ @Deprecated
public static final int ACTION_POINTER_2_UP = ACTION_POINTER_UP | 0x0100;
/**
- * Synonym for {@link #ACTION_POINTER_UP} with
- * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone up.
+ * @deprecated Use {@link #ACTION_POINTER_INDEX_MASK} to retrieve the
+ * data index associated with {@link #ACTION_POINTER_UP}.
*/
+ @Deprecated
public static final int ACTION_POINTER_3_UP = ACTION_POINTER_UP | 0x0200;
/**
- * Bits in the action code that represent a pointer ID, used with
- * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}. Pointer IDs
- * start at 0, with 0 being the primary (first) pointer in the motion. Note
- * that this not <em>not</em> an index into the array of pointer values,
- * which is compacted to only contain pointers that are down; the pointer
- * ID for a particular index can be found with {@link #findPointerIndex}.
+ * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_MASK} to match
+ * the actual data contained in these bits.
*/
+ @Deprecated
public static final int ACTION_POINTER_ID_MASK = 0xff00;
/**
- * Bit shift for the action bits holding the pointer identifier as
- * defined by {@link #ACTION_POINTER_ID_MASK}.
+ * @deprecated Renamed to {@link #ACTION_POINTER_INDEX_SHIFT} to match
+ * the actual data contained in these bits.
*/
+ @Deprecated
public static final int ACTION_POINTER_ID_SHIFT = 8;
private static final boolean TRACK_RECYCLED_LOCATION = false;
@@ -618,13 +638,39 @@ public final class MotionEvent implements Parcelable {
/**
* Return the kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
- * {@link #ACTION_CANCEL}.
+ * {@link #ACTION_CANCEL}. Consider using {@link #getActionMasked}
+ * and {@link #getActionIndex} to retrieve the separate masked action
+ * and pointer index.
*/
public final int getAction() {
return mAction;
}
/**
+ * Return the masked action being performed, without pointer index
+ * information. May be any of the actions: {@link #ACTION_DOWN},
+ * {@link #ACTION_MOVE}, {@link #ACTION_UP}, {@link #ACTION_CANCEL},
+ * {@link #ACTION_POINTER_DOWN}, or {@link #ACTION_POINTER_UP}.
+ * Use {@link #getActionIndex} to return the index associated with
+ * pointer actions.
+ */
+ public final int getActionMasked() {
+ return mAction & ACTION_MASK;
+ }
+
+ /**
+ * For {@link #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP}
+ * as returned by {@link #getActionMasked}, this returns the associated
+ * pointer index. The index may be used with {@link #getPointerId(int)},
+ * {@link #getX(int)}, {@link #getY(int)}, {@link #getPressure(int)},
+ * and {@link #getSize(int)} to get information about the pointer that has
+ * gone down or up.
+ */
+ public final int getActionIndex() {
+ return (mAction & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ /**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
*/
diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
index 8b3cdd4..3bbfea8 100644
--- a/core/java/android/view/RawInputEvent.java
+++ b/core/java/android/view/RawInputEvent.java
@@ -1,6 +1,19 @@
-/**
- *
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
+
package android.view;
/**
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index f4215a8..ff34f4a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -18,6 +18,7 @@ package android.view;
import android.content.Context;
import android.util.DisplayMetrics;
+import android.util.FloatMath;
/**
* Detects transformation gestures involving more than one pointer ("multitouch")
@@ -33,7 +34,6 @@ import android.util.DisplayMetrics;
* {@link #onTouchEvent(MotionEvent)}. The methods defined in your
* callback will be executed when the events occur.
* </ul>
- * @hide Pending API approval
*/
public class ScaleGestureDetector {
/**
@@ -97,14 +97,16 @@ public class ScaleGestureDetector {
* A convenience class to extend when you only want to listen for a subset
* of scaling-related events. This implements all methods in
* {@link OnScaleGestureListener} but does nothing.
- * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and
- * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return
+ * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
+ * {@code false} so that a subclass can retrieve the accumulated scale
+ * factor in an overridden onScaleEnd.
+ * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
* {@code true}.
*/
public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
public boolean onScale(ScaleGestureDetector detector) {
- return true;
+ return false;
}
public boolean onScaleBegin(ScaleGestureDetector detector) {
@@ -116,10 +118,19 @@ public class ScaleGestureDetector {
}
}
+ /**
+ * This value is the threshold ratio between our previous combined pressure
+ * and the current combined pressure. We will only fire an onScale event if
+ * the computed ratio between the current and previous event pressures is
+ * greater than this value. When pressure decreases rapidly between events
+ * the position values can often be imprecise, as it usually indicates
+ * that the user is in the process of lifting a pointer off of the device.
+ * Its value was tuned experimentally.
+ */
private static final float PRESSURE_THRESHOLD = 0.67f;
- private Context mContext;
- private OnScaleGestureListener mListener;
+ private final Context mContext;
+ private final OnScaleGestureListener mListener;
private boolean mGestureInProgress;
private MotionEvent mPrevEvent;
@@ -138,7 +149,7 @@ public class ScaleGestureDetector {
private float mPrevPressure;
private long mTimeDelta;
- private float mEdgeSlop;
+ private final float mEdgeSlop;
private float mRightSlopEdge;
private float mBottomSlopEdge;
private boolean mSloppyGesture;
@@ -155,9 +166,8 @@ public class ScaleGestureDetector {
boolean handled = true;
if (!mGestureInProgress) {
- if ((action == MotionEvent.ACTION_POINTER_1_DOWN ||
- action == MotionEvent.ACTION_POINTER_2_DOWN) &&
- event.getPointerCount() >= 2) {
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_DOWN: {
// We have a new multi-finger gesture
// as orientation can change, query the metrics in touch down
@@ -190,7 +200,7 @@ public class ScaleGestureDetector {
boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
|| x1 > rightSlop || y1 > bottomSlop;
- if(p0sloppy && p1sloppy) {
+ if (p0sloppy && p1sloppy) {
mFocusX = -1;
mFocusY = -1;
mSloppyGesture = true;
@@ -205,54 +215,61 @@ public class ScaleGestureDetector {
} else {
mGestureInProgress = mListener.onScaleBegin(this);
}
- } else if (action == MotionEvent.ACTION_MOVE && mSloppyGesture) {
- // Initiate sloppy gestures if we've moved outside of the slop area.
- final float edgeSlop = mEdgeSlop;
- final float rightSlop = mRightSlopEdge;
- final float bottomSlop = mBottomSlopEdge;
- final float x0 = event.getRawX();
- final float y0 = event.getRawY();
- final float x1 = getRawX(event, 1);
- final float y1 = getRawY(event, 1);
-
- boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
- || x0 > rightSlop || y0 > bottomSlop;
- boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
- || x1 > rightSlop || y1 > bottomSlop;
-
- if(p0sloppy && p1sloppy) {
- mFocusX = -1;
- mFocusY = -1;
- } else if (p0sloppy) {
- mFocusX = event.getX(1);
- mFocusY = event.getY(1);
- } else if (p1sloppy) {
- mFocusX = event.getX(0);
- mFocusY = event.getY(0);
- } else {
- mSloppyGesture = false;
- mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mSloppyGesture) {
+ // Initiate sloppy gestures if we've moved outside of the slop area.
+ final float edgeSlop = mEdgeSlop;
+ final float rightSlop = mRightSlopEdge;
+ final float bottomSlop = mBottomSlopEdge;
+ final float x0 = event.getRawX();
+ final float y0 = event.getRawY();
+ final float x1 = getRawX(event, 1);
+ final float y1 = getRawY(event, 1);
+
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
+
+ if(p0sloppy && p1sloppy) {
+ mFocusX = -1;
+ mFocusY = -1;
+ } else if (p0sloppy) {
+ mFocusX = event.getX(1);
+ mFocusY = event.getY(1);
+ } else if (p1sloppy) {
+ mFocusX = event.getX(0);
+ mFocusY = event.getY(0);
+ } else {
+ mSloppyGesture = false;
+ mGestureInProgress = mListener.onScaleBegin(this);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mSloppyGesture) {
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
}
- } else if ((action == MotionEvent.ACTION_POINTER_1_UP
- || action == MotionEvent.ACTION_POINTER_2_UP)
- && mSloppyGesture) {
- // Set focus point to the remaining finger
- int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
- >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
- mFocusX = event.getX(id);
- mFocusY = event.getY(id);
+ break;
}
} else {
// Transform gesture in progress - attempt to handle it
- switch (action) {
- case MotionEvent.ACTION_POINTER_1_UP:
- case MotionEvent.ACTION_POINTER_2_UP:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_POINTER_UP:
// Gesture ended
setContext(event);
// Set focus point to the remaining finger
- int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
- >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ int id = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1 : 0;
mFocusX = event.getX(id);
mFocusY = event.getY(id);
@@ -405,7 +422,7 @@ public class ScaleGestureDetector {
if (mCurrLen == -1) {
final float cvx = mCurrFingerDiffX;
final float cvy = mCurrFingerDiffY;
- mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy);
+ mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
}
return mCurrLen;
}
@@ -420,7 +437,7 @@ public class ScaleGestureDetector {
if (mPrevLen == -1) {
final float pvx = mPrevFingerDiffX;
final float pvy = mPrevFingerDiffY;
- mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy);
+ mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
}
return mPrevLen;
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index b85667b..83ef8ba 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -28,6 +28,7 @@ import android.util.Log;
*/
public class Surface implements Parcelable {
private static final String LOG_TAG = "Surface";
+ private static final boolean DEBUG_RELEASE = false;
/* flags used in constructor (keep in sync with ISurfaceComposer.h) */
@@ -146,6 +147,7 @@ public class Surface implements Parcelable {
private int mSaveCount;
@SuppressWarnings("unused")
private Canvas mCanvas;
+ private String mName;
// The display metrics used to provide the pseudo canvas size for applications
// running in compatibility mode. This is set to null for non compatibility mode.
@@ -155,6 +157,9 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
+ @SuppressWarnings("unused")
+ private Exception mCreationStack;
+
/**
* Exception thrown when a surface couldn't be created or resized
*/
@@ -181,8 +186,26 @@ public class Surface implements Parcelable {
public Surface(SurfaceSession s,
int pid, int display, int w, int h, int format, int flags)
throws OutOfResourcesException {
+ if (DEBUG_RELEASE) {
+ mCreationStack = new Exception();
+ }
+ mCanvas = new CompatibleCanvas();
+ init(s,pid,null,display,w,h,format,flags);
+ }
+
+ /**
+ * create a surface with a name
+ * {@hide}
+ */
+ public Surface(SurfaceSession s,
+ int pid, String name, int display, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ if (DEBUG_RELEASE) {
+ mCreationStack = new Exception();
+ }
mCanvas = new CompatibleCanvas();
- init(s,pid,display,w,h,format,flags);
+ init(s,pid,name,display,w,h,format,flags);
+ mName = name;
}
/**
@@ -191,6 +214,9 @@ public class Surface implements Parcelable {
* {@hide}
*/
public Surface() {
+ if (DEBUG_RELEASE) {
+ mCreationStack = new Exception();
+ }
mCanvas = new CompatibleCanvas();
}
@@ -362,7 +388,7 @@ public class Surface implements Parcelable {
@Override
public String toString() {
- return "Surface(native-token=" + mSurfaceControl + ")";
+ return "Surface(name=" + mName + ", identity=" + getIdentity() + ")";
}
private Surface(Parcel source) throws OutOfResourcesException {
@@ -396,12 +422,23 @@ public class Surface implements Parcelable {
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
+ if (mSurface != 0 || mSurfaceControl != 0) {
+ if (DEBUG_RELEASE) {
+ Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
+ + mSurface + ", " + mSurfaceControl + ")", mCreationStack);
+ } else {
+ Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
+ + mSurface + ", " + mSurfaceControl + ")");
+ }
+ }
release();
}
private native void init(SurfaceSession s,
- int pid, int display, int w, int h, int format, int flags)
+ int pid, String name, int display, int w, int h, int format, int flags)
throws OutOfResourcesException;
private native void init(Parcel source);
+
+ private native int getIdentity();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ca5e1de..2a3f032 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -19,6 +19,7 @@ package android.view;
import com.android.internal.view.BaseIWindow;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.CompatibilityInfo.Translator;
import android.graphics.Canvas;
@@ -38,7 +39,6 @@ import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
-import java.lang.ref.WeakReference;
/**
* Provides a dedicated drawing surface embedded inside of a view hierarchy.
@@ -101,9 +101,11 @@ public class SurfaceView extends View {
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
final Rect mContentInsets = new Rect();
-
+ final Configuration mConfiguration = new Configuration();
+
static final int KEEP_SCREEN_ON_MSG = 1;
static final int GET_NEW_SURFACE_MSG = 2;
+ static final int UPDATE_WINDOW_MSG = 3;
int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@@ -119,10 +121,20 @@ public class SurfaceView extends View {
case GET_NEW_SURFACE_MSG: {
handleGetNewSurface();
} break;
+ case UPDATE_WINDOW_MSG: {
+ updateWindow(false);
+ } break;
}
}
};
+ final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+ = new ViewTreeObserver.OnScrollChangedListener() {
+ public void onScrollChanged() {
+ updateWindow(false);
+ }
+ };
+
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
boolean mViewVisibility = false;
@@ -144,6 +156,9 @@ public class SurfaceView extends View {
int mFormat = -1;
int mType = -1;
final Rect mSurfaceFrame = new Rect();
+ int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+ boolean mUpdateWindowNeeded;
+ boolean mReportDrawNeeded;
private Translator mTranslator;
public SurfaceView(Context context) {
@@ -179,6 +194,7 @@ public class SurfaceView extends View {
mLayout.token = getWindowToken();
mLayout.setTitle("SurfaceView");
mViewVisibility = getVisibility() == VISIBLE;
+ getViewTreeObserver().addOnScrollChangedListener(mScrollChangedListener);
}
@Override
@@ -199,6 +215,7 @@ public class SurfaceView extends View {
@Override
protected void onDetachedFromWindow() {
+ getViewTreeObserver().removeOnScrollChangedListener(mScrollChangedListener);
mRequestedVisible = false;
updateWindow(false);
mHaveFrame = false;
@@ -223,12 +240,6 @@ public class SurfaceView extends View {
}
@Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- super.onScrollChanged(l, t, oldl, oldt);
- updateWindow(false);
- }
-
- @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateWindow(false);
@@ -319,8 +330,14 @@ public class SurfaceView extends View {
* <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
*/
public void setZOrderOnTop(boolean onTop) {
- mWindowType = onTop ? WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
- : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ if (onTop) {
+ mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+ // ensures the surface is placed below the IME
+ mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
}
/**
@@ -359,7 +376,8 @@ public class SurfaceView extends View {
|| mNewSurfaceNeeded;
final boolean typeChanged = mType != mRequestedType;
if (force || creating || formatChanged || sizeChanged || visibleChanged
- || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]) {
+ || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
+ || mUpdateWindowNeeded || mReportDrawNeeded) {
if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
@@ -412,31 +430,50 @@ public class SurfaceView extends View {
if (visibleChanged && (!visible || mNewSurfaceNeeded)) {
reportSurfaceDestroyed();
}
-
+
mNewSurfaceNeeded = false;
- mSurfaceLock.lock();
- mDrawingStopped = !visible;
-
- final int relayoutResult = mSession.relayout(
- mWindow, mLayout, mWidth, mHeight,
- visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
- mVisibleInsets, mSurface);
-
- if (localLOGV) Log.i(TAG, "New surface: " + mSurface
- + ", vis=" + visible + ", frame=" + mWinFrame);
+ boolean realSizeChanged;
+ boolean reportDrawNeeded;
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (mTranslator == null) {
- mSurfaceFrame.right = mWinFrame.width();
- mSurfaceFrame.bottom = mWinFrame.height();
- } else {
- float appInvertedScale = mTranslator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
+ mSurfaceLock.lock();
+ try {
+ mUpdateWindowNeeded = false;
+ reportDrawNeeded = mReportDrawNeeded;
+ mReportDrawNeeded = false;
+ mDrawingStopped = !visible;
+
+ final int relayoutResult = mSession.relayout(
+ mWindow, mLayout, mWidth, mHeight,
+ visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
+ mVisibleInsets, mConfiguration, mSurface);
+ if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+ mReportDrawNeeded = true;
+ }
+
+ if (localLOGV) Log.i(TAG, "New surface: " + mSurface
+ + ", vis=" + visible + ", frame=" + mWinFrame);
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (mTranslator == null) {
+ mSurfaceFrame.right = mWinFrame.width();
+ mSurfaceFrame.bottom = mWinFrame.height();
+ } else {
+ float appInvertedScale = mTranslator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
+ }
+
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
}
- mSurfaceLock.unlock();
try {
if (visible) {
@@ -455,15 +492,17 @@ public class SurfaceView extends View {
}
}
if (creating || formatChanged || sizeChanged
- || visibleChanged) {
+ || visibleChanged || realSizeChanged) {
for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, mWidth, mHeight);
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
+ } else {
+ mSurface.release();
}
} finally {
mIsCreating = false;
- if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+ if (creating || reportDrawNeeded) {
mSession.finishDrawing(mWindow);
}
}
@@ -496,6 +535,17 @@ public class SurfaceView extends View {
updateWindow(false);
}
+ /**
+ * Check to see if the surface has fixed size dimensions or if the surface's
+ * dimensions are dimensions are dependent on its current layout.
+ *
+ * @return true if the surface has dimensions that are fixed in size
+ * @hide
+ */
+ public boolean isFixedSize() {
+ return (mRequestedWidth != -1 || mRequestedHeight != -1);
+ }
+
private static class MyWindow extends BaseIWindow {
private final WeakReference<SurfaceView> mSurfaceView;
@@ -504,23 +554,25 @@ public class SurfaceView extends View {
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
if (localLOGV) Log.v(
"SurfaceView", surfaceView + " got resized: w=" +
w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
- synchronized (this) {
- if (mCurWidth != w || mCurHeight != h) {
- mCurWidth = w;
- mCurHeight = h;
- }
+ surfaceView.mSurfaceLock.lock();
+ try {
if (reportDraw) {
- try {
- surfaceView.mSession.finishDrawing(surfaceView.mWindow);
- } catch (RemoteException e) {
- }
+ surfaceView.mUpdateWindowNeeded = true;
+ surfaceView.mReportDrawNeeded = true;
+ surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
+ } else if (surfaceView.mWinFrame.width() != w
+ || surfaceView.mWinFrame.height() != h) {
+ surfaceView.mUpdateWindowNeeded = true;
+ surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
}
+ } finally {
+ surfaceView.mSurfaceLock.unlock();
}
}
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 9a8ee02..aab76c4 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -100,6 +100,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
}
private VelocityTracker() {
+ clear();
}
/**
@@ -109,7 +110,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
final long[][] pastTime = mPastTime;
for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) {
for (int i = 0; i < NUM_PAST; i++) {
- pastTime[p][i] = 0;
+ pastTime[p][i] = Long.MIN_VALUE;
}
}
}
@@ -124,24 +125,37 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @param ev The MotionEvent you received and would like to track.
*/
public void addMovement(MotionEvent ev) {
- long time = ev.getEventTime();
final int N = ev.getHistorySize();
final int pointerCount = ev.getPointerCount();
- for (int p = 0; p < pointerCount; p++) {
- for (int i=0; i<N; i++) {
- addPoint(p, ev.getHistoricalX(p, i), ev.getHistoricalY(p, i),
- ev.getHistoricalEventTime(i));
+ int touchIndex = (mLastTouch + 1) % NUM_PAST;
+ for (int i=0; i<N; i++) {
+ for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
+ mPastTime[id][touchIndex] = Long.MIN_VALUE;
+ }
+ for (int p = 0; p < pointerCount; p++) {
+ int id = ev.getPointerId(p);
+ mPastX[id][touchIndex] = ev.getHistoricalX(p, i);
+ mPastY[id][touchIndex] = ev.getHistoricalY(p, i);
+ mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i);
}
- addPoint(p, ev.getX(p), ev.getY(p), time);
+
+ touchIndex = (touchIndex + 1) % NUM_PAST;
+ }
+
+ // During calculation any pointer values with a time of MIN_VALUE are treated
+ // as a break in input. Initialize all to MIN_VALUE for each new touch index.
+ for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
+ mPastTime[id][touchIndex] = Long.MIN_VALUE;
+ }
+ final long time = ev.getEventTime();
+ for (int p = 0; p < pointerCount; p++) {
+ int id = ev.getPointerId(p);
+ mPastX[id][touchIndex] = ev.getX(p);
+ mPastY[id][touchIndex] = ev.getY(p);
+ mPastTime[id][touchIndex] = time;
}
- }
- private void addPoint(int pos, float x, float y, long time) {
- final int lastTouch = (mLastTouch + 1) % NUM_PAST;
- mPastX[pos][lastTouch] = x;
- mPastY[pos][lastTouch] = y;
- mPastTime[pos][lastTouch] = time;
- mLastTouch = lastTouch;
+ mLastTouch = touchIndex;
}
/**
@@ -176,11 +190,13 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
// find oldest acceptable time
int oldestTouch = lastTouch;
- if (pastTime[lastTouch] > 0) { // cleared ?
- oldestTouch = (lastTouch + 1) % NUM_PAST;
+ if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ?
final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME;
- while (pastTime[oldestTouch] < acceptableTime) {
- oldestTouch = (oldestTouch + 1) % NUM_PAST;
+ int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
+ while (pastTime[nextOldestTouch] >= acceptableTime &&
+ nextOldestTouch != lastTouch) {
+ oldestTouch = nextOldestTouch;
+ nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
}
}
@@ -241,25 +257,21 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* Retrieve the last computed X velocity. You must first call
* {@link #computeCurrentVelocity(int)} before calling this function.
*
- * @param pos Which pointer's velocity to return.
+ * @param id Which pointer's velocity to return.
* @return The previously computed X velocity.
- *
- * @hide Pending API approval
*/
- public float getXVelocity(int pos) {
- return mXVelocity[pos];
+ public float getXVelocity(int id) {
+ return mXVelocity[id];
}
/**
* Retrieve the last computed Y velocity. You must first call
* {@link #computeCurrentVelocity(int)} before calling this function.
*
- * @param pos Which pointer's velocity to return.
+ * @param id Which pointer's velocity to return.
* @return The previously computed Y velocity.
- *
- * @hide Pending API approval
*/
- public float getYVelocity(int pos) {
- return mYVelocity[pos];
+ public float getYVelocity(int id) {
+ return mYVelocity[id];
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index da48f40..11e5ad1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20,6 +20,7 @@ import com.android.internal.R;
import com.android.internal.view.menu.MenuBuilder;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -379,7 +380,7 @@ import java.util.WeakHashMap;
* dimension, it can specify one of:
* <ul>
* <li> an exact number
- * <li>FILL_PARENT, which means the view wants to be as big as its parent
+ * <li>MATCH_PARENT, which means the view wants to be as big as its parent
* (minus padding)
* <li> WRAP_CONTENT, which means that the view wants to be just big enough to
* enclose its content (plus padding).
@@ -1494,6 +1495,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @hide
*/
static final int OPAQUE_MASK = 0x01800000;
+
+ /**
+ * Indicates a prepressed state;
+ * the short time between ACTION_DOWN and recognizing
+ * a 'real' press. Prepressed is used to recognize quick taps
+ * even when they are shorter than ViewConfiguration.getTapTimeout().
+ *
+ * @hide
+ */
+ private static final int PREPRESSED = 0x02000000;
+
+ /**
+ * Indicates whether the view is temporarily detached.
+ *
+ * @hide
+ */
+ static final int CANCEL_NEXT_UP_EVENT = 0x04000000;
+
+ /**
+ * Indicates that we should awaken scroll bars once attached
+ *
+ * @hide
+ */
+ private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
* The parent this view is attached to.
@@ -1722,6 +1747,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private int mNextFocusDownId = View.NO_ID;
private CheckForLongPress mPendingCheckForLongPress;
+ private CheckForTap mPendingCheckForTap = null;
+ private PerformClick mPerformClick;
+
private UnsetPressedState mUnsetPressedState;
/**
@@ -1762,6 +1790,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Special tree observer used when mAttachInfo is null.
*/
private ViewTreeObserver mFloatingTreeObserver;
+
+ /**
+ * Cache the touch slop from the context that created the view.
+ */
+ private int mTouchSlop;
// Used for debug only
static long sInstanceCount = 0;
@@ -1776,7 +1809,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mContext = context;
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
- ++sInstanceCount;
+ // Used for debug only
+ //++sInstanceCount;
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
@@ -2017,8 +2052,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mHandler = getContext().getClass().getMethod(handlerName,
View.class);
} catch (NoSuchMethodException e) {
+ int id = getId();
+ String idText = id == NO_ID ? "" : " with id '"
+ + getContext().getResources().getResourceEntryName(
+ id) + "'";
throw new IllegalStateException("Could not find a method " +
- handlerName + "(View) in the activity", e);
+ handlerName + "(View) in the activity "
+ + getContext().getClass() + " for onClick handler"
+ + " on view " + View.this.getClass() + idText, e);
}
}
@@ -2086,11 +2127,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
View() {
}
+ // Used for debug only
+ /*
@Override
protected void finalize() throws Throwable {
super.finalize();
--sInstanceCount;
}
+ */
/**
* <p>
@@ -2225,7 +2269,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
scrollabilityCache.scrollBar = new ScrollBarDrawable();
}
- final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, false);
+ final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
if (!fadeScrollbars) {
scrollabilityCache.state = ScrollabilityCache.ON;
@@ -2589,8 +2633,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @param gainFocus True if the View has focus; false otherwise.
* @param direction The direction focus has moved when requestFocus()
* is called to give this view focus. Values are
- * View.FOCUS_UP, View.FOCUS_DOWN, View.FOCUS_LEFT or
- * View.FOCUS_RIGHT. It may not always apply, in which
+ * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT} or
+ * {@link #FOCUS_RIGHT}. It may not always apply, in which
* case use the default.
* @param previouslyFocusedRect The rectangle, in this view's coordinate
* system, of the previously focused view. If applicable, this will be
@@ -2726,9 +2770,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
setPressed(false);
if (!mHasPerformedLongPress) {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
}
}
}
@@ -3006,6 +3048,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @param enabled True if this view is enabled, false otherwise.
*/
+ @RemotableViewMethod
public void setEnabled(boolean enabled) {
if (enabled == isEnabled()) return;
@@ -3604,14 +3647,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * This is called when a container is going to temporarily detach a child
- * that currently has focus, with
+ * @hide
+ */
+ public void dispatchStartTemporaryDetach() {
+ onStartTemporaryDetach();
+ }
+
+ /**
+ * This is called when a container is going to temporarily detach a child, with
* {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}.
* It will either be followed by {@link #onFinishTemporaryDetach()} or
- * {@link #onDetachedFromWindow()} when the container is done. Generally
- * this is currently only done ListView for a view with focus.
+ * {@link #onDetachedFromWindow()} when the container is done.
*/
public void onStartTemporaryDetach() {
+ removeUnsetPressCallback();
+ mPrivateFlags |= CANCEL_NEXT_UP_EVENT;
+ }
+
+ /**
+ * @hide
+ */
+ public void dispatchFinishTemporaryDetach() {
+ onFinishTemporaryDetach();
}
/**
@@ -3750,9 +3807,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
imm.focusOut(this);
}
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
onFocusLost();
} else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
imm.focusIn(this);
@@ -3771,6 +3826,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Dispatch a view visibility change down the view hierarchy.
+ * ViewGroups should override to route to their children.
+ * @param changedView The view whose visibility changed. Could be 'this' or
+ * an ancestor view.
+ * @param visibility The new visibility of changedView: {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
+ */
+ protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ onVisibilityChanged(changedView, visibility);
+ }
+
+ /**
+ * Called when the visibility of the view or an ancestor of the view is changed.
+ * @param changedView The view whose visibility changed. Could be 'this' or
+ * an ancestor view.
+ * @param visibility The new visibility of changedView: {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
+ */
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ if (visibility == VISIBLE) {
+ if (mAttachInfo != null) {
+ initialAwakenScrollBars();
+ } else {
+ mPrivateFlags |= AWAKEN_SCROLL_BARS_ON_ATTACH;
+ }
+ }
+ }
+
+ /**
+ * Dispatch a hint about whether this view is displayed. For instance, when
+ * a View moves out of the screen, it might receives a display hint indicating
+ * the view is not displayed. Applications should not <em>rely</em> on this hint
+ * as there is no guarantee that they will receive one.
+ *
+ * @param hint A hint about whether or not this view is displayed:
+ * {@link #VISIBLE} or {@link #INVISIBLE}.
+ */
+ public void dispatchDisplayHint(int hint) {
+ onDisplayHint(hint);
+ }
+
+ /**
+ * Gives this view a hint about whether is displayed or not. For instance, when
+ * a View moves out of the screen, it might receives a display hint indicating
+ * the view is not displayed. Applications should not <em>rely</em> on this hint
+ * as there is no guarantee that they will receive one.
+ *
+ * @param hint A hint about whether or not this view is displayed:
+ * {@link #VISIBLE} or {@link #INVISIBLE}.
+ */
+ protected void onDisplayHint(int hint) {
+ }
+
+ /**
* Dispatch a window visibility change down the view hierarchy.
* ViewGroups should override to route to their children.
*
@@ -3793,6 +3902,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @param visibility The new visibility of the window.
*/
protected void onWindowVisibilityChanged(int visibility) {
+ if (visibility == VISIBLE) {
+ initialAwakenScrollBars();
+ }
}
/**
@@ -3843,6 +3955,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Dispatch a notification about a resource configuration change down
+ * the view hierarchy.
+ * ViewGroups should override to route to their children.
+ *
+ * @param newConfig The new resource configuration.
+ *
+ * @see #onConfigurationChanged
+ */
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Called when the current configuration of the resources being used
+ * by the application have changed. You can use this to decide when
+ * to reload resources that can changed based on orientation and other
+ * configuration characterstics. You only need to use this if you are
+ * not relying on the normal {@link android.app.Activity} mechanism of
+ * recreating the activity instance upon a configuration change.
+ *
+ * @param newConfig The new resource configuration.
+ */
+ protected void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ /**
* Private function to aggregate all per-view attributes in to the view
* root.
*/
@@ -3935,7 +4073,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(event.getRepeatCount() == 0)) {
setPressed(true);
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick();
+ postCheckForLongClick(0);
}
return true;
}
@@ -3978,9 +4116,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
result = performClick();
}
@@ -4160,7 +4296,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
- if ((mPrivateFlags & PRESSED) != 0) {
+ boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
+ if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
@@ -4170,13 +4307,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
- performClick();
+ // Use a Runnable and post this rather than calling
+ // performClick directly. This lets other visual state
+ // of the view update before click actions start.
+ if (mPerformClick == null) {
+ mPerformClick = new PerformClick();
+ }
+ if (!post(mPerformClick)) {
+ performClick();
+ }
}
}
@@ -4184,24 +4327,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mUnsetPressedState = new UnsetPressedState();
}
- if (!post(mUnsetPressedState)) {
+ if (prepressed) {
+ mPrivateFlags |= PRESSED;
+ refreshDrawableState();
+ postDelayed(mUnsetPressedState,
+ ViewConfiguration.getPressedStateDuration());
+ } else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
+ removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
- postCheckForLongClick();
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
}
+ mPrivateFlags |= PREPRESSED;
+ mHasPerformedLongPress = false;
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
+ removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
@@ -4209,27 +4360,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
- int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
+ removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
- // Remove any future long press checks
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ // Remove any future long press/tap checks
+ removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
- } else {
- // Inside button
- if ((mPrivateFlags & PRESSED) == 0) {
- // Need to switch from not pressed to pressed
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- }
}
break;
}
@@ -4240,15 +4383,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Remove the longpress detection timer.
+ */
+ private void removeLongPressCallback() {
+ if (mPendingCheckForLongPress != null) {
+ removeCallbacks(mPendingCheckForLongPress);
+ }
+ }
+
+ /**
+ * Remove the prepress detection timer.
+ */
+ private void removeUnsetPressCallback() {
+ if ((mPrivateFlags & PRESSED) != 0 && mUnsetPressedState != null) {
+ setPressed(false);
+ removeCallbacks(mUnsetPressedState);
+ }
+ }
+
+ /**
+ * Remove the tap detection timer.
+ */
+ private void removeTapCallback() {
+ if (mPendingCheckForTap != null) {
+ mPrivateFlags &= ~PREPRESSED;
+ removeCallbacks(mPendingCheckForTap);
+ }
+ }
+
+ /**
* Cancels a pending long press. Your subclass can use this if you
* want the context menu to come up if the user presses and holds
* at the same place, but you don't want it to come up if they press
* and then move around enough to cause scrolling.
*/
public void cancelLongPress() {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ removeLongPressCallback();
+
+ /*
+ * The prepressed state handled by the tap callback is a display
+ * construct, but the tap callback will post a long press callback
+ * less its own timeout. Remove it here.
+ */
+ removeTapCallback();
}
/**
@@ -4349,6 +4526,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
+ if ((changed & VISIBILITY_MASK) != 0) {
+ dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK));
+ }
+
if ((changed & WILL_NOT_CACHE_DRAWING) != 0) {
destroyDrawingCache();
}
@@ -4745,6 +4926,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Trigger the scrollbars to draw.
+ * This method differs from awakenScrollBars() only in its default duration.
+ * initialAwakenScrollBars() will show the scroll bars for longer than
+ * usual to give the user more of a chance to notice them.
+ *
+ * @return true if the animation is played, false otherwise.
+ */
+ private boolean initialAwakenScrollBars() {
+ return mScrollCache != null &&
+ awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade * 4, true);
+ }
+
+ /**
* <p>
* Trigger the scrollbars to draw. When invoked this method starts an
* animation to fade the scrollbars out after a fixed delay. If a subclass
@@ -5527,7 +5721,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* of the thumb within the scrollbar's track.</p>
*
* <p>The range is expressed in arbitrary units that must be the same as the
- * units used by {@link #computeHorizontalScrollRange()} and
+ * units used by {@link #computeVerticalScrollRange()} and
* {@link #computeVerticalScrollOffset()}.</p>
*
* <p>The default extent is the drawing height of this view.</p>
@@ -5736,6 +5930,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) {
mParent.requestTransparentRegion(this);
}
+ if ((mPrivateFlags & AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+ initialAwakenScrollBars();
+ mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH;
+ }
}
/**
@@ -5745,9 +5943,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @see #onAttachedToWindow()
*/
protected void onDetachedFromWindow() {
- if (mPendingCheckForLongPress != null) {
- removeCallbacks(mPendingCheckForLongPress);
- }
+ mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ removeUnsetPressCallback();
+ removeLongPressCallback();
destroyDrawingCache();
}
@@ -5961,7 +6159,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
- throw new IllegalArgumentException("Wrong state class -- expecting View State");
+ throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ + "received " + state.getClass().toString() + " instead. This usually happens "
+ + "when two views of different type have the same id in the same hierarchy. "
+ + "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ + "other views do not use the same id.");
}
}
@@ -6124,7 +6326,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @see #getDrawingCache()
*/
public void setDrawingCacheBackgroundColor(int color) {
- mDrawingCacheBackgroundColor = color;
+ if (color != mDrawingCacheBackgroundColor) {
+ mDrawingCacheBackgroundColor = color;
+ mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ }
}
/**
@@ -6310,7 +6515,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Create a snapshot of the view into a bitmap. We should probably make
* some form of this public, but should think about the API.
*/
- Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor) {
+ Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
int width = mRight - mLeft;
int height = mBottom - mTop;
@@ -7166,6 +7371,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Sets the background color for this view.
* @param color the color of the background
*/
+ @RemotableViewMethod
public void setBackgroundColor(int color) {
setBackgroundDrawable(new ColorDrawable(color));
}
@@ -7176,6 +7382,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @param resid The identifier of the resource.
* @attr ref android.R.styleable#View_background
*/
+ @RemotableViewMethod
public void setBackgroundResource(int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
@@ -8407,14 +8614,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
}
- private void postCheckForLongClick() {
+ private void postCheckForLongClick(int delayOffset) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
- postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
+ postDelayed(mPendingCheckForLongPress,
+ ViewConfiguration.getLongPressTimeout() - delayOffset);
}
private static int[] stateSetUnion(final int[] stateSet1,
@@ -8591,6 +8799,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
+
+ private final class CheckForTap implements Runnable {
+ public void run() {
+ mPrivateFlags &= ~PREPRESSED;
+ mPrivateFlags |= PRESSED;
+ refreshDrawableState();
+ if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+ postCheckForLongClick(ViewConfiguration.getTapTimeout());
+ }
+ }
+ }
+
+ private final class PerformClick implements Runnable {
+ public void run() {
+ performClick();
+ }
+ }
/**
* Interface definition for a callback to be invoked when a key event is
@@ -8896,12 +9121,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
boolean mRecomputeGlobalAttributes;
/**
- * Set to true when attributes (like mKeepScreenOn) need to be
- * recomputed.
- */
- boolean mAttributesChanged;
-
- /**
* Set during a traveral if any views want to keep the screen on.
*/
boolean mKeepScreenOn;
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 993048f..acdfc28 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -49,7 +49,7 @@ public class ViewConfiguration {
* Defines the duration in milliseconds of the pressed state in child
* components.
*/
- private static final int PRESSED_STATE_DURATION = 85;
+ private static final int PRESSED_STATE_DURATION = 125;
/**
* Defines the duration in milliseconds before a press turns into
@@ -69,7 +69,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 = 100;
+ private static final int TAP_TIMEOUT = 115;
/**
* Defines the duration in milliseconds we will wait to see if a touch event
@@ -102,6 +102,12 @@ public class ViewConfiguration {
private static final int TOUCH_SLOP = 16;
/**
+ * Distance a touch can wander before we think the user is attempting a paged scroll
+ * (in dips)
+ */
+ private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;
+
+ /**
* Distance between the first touch and second touch to still be considered a double tap
*/
private static final int DOUBLE_TAP_SLOP = 100;
@@ -140,6 +146,7 @@ public class ViewConfiguration {
private final int mMaximumFlingVelocity;
private final int mScrollbarSize;
private final int mTouchSlop;
+ private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
@@ -158,6 +165,7 @@ public class ViewConfiguration {
mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
mScrollbarSize = SCROLL_BAR_SIZE;
mTouchSlop = TOUCH_SLOP;
+ mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
@@ -184,6 +192,7 @@ public class ViewConfiguration {
mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f);
+ mPagingTouchSlop = (int) (density * PAGING_TOUCH_SLOP + 0.5f);
mDoubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (density * WINDOW_TOUCH_SLOP + 0.5f);
@@ -339,6 +348,14 @@ public class ViewConfiguration {
public int getScaledTouchSlop() {
return mTouchSlop;
}
+
+ /**
+ * @return Distance a touch can wander before we think the user is scrolling a full page
+ * in dips
+ */
+ public int getScaledPagingTouchSlop() {
+ return mPagingTouchSlop;
+ }
/**
* @return Distance between the first touch and second touch to still be
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 4baf612..d2563a8 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -23,9 +23,12 @@ import android.content.res.Resources;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.os.Environment;
import android.os.Debug;
+import android.os.RemoteException;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.BufferedWriter;
import java.io.FileWriter;
@@ -90,6 +93,15 @@ 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
*/
@@ -336,6 +348,7 @@ public class ViewDebug {
private static final String REMOTE_COMMAND_INVALIDATE = "INVALIDATE";
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 HashMap<Class<?>, Field[]> sFieldsForClasses;
private static HashMap<Class<?>, Method[]> sMethodsForClasses;
@@ -367,7 +380,6 @@ public class ViewDebug {
BIND_VIEW,
RECYCLE_FROM_ACTIVE_HEAP,
RECYCLE_FROM_SCRAP_HEAP,
- MOVE_TO_ACTIVE_HEAP,
MOVE_TO_SCRAP_HEAP,
MOVE_FROM_ACTIVE_TO_SCRAP_HEAP
}
@@ -385,6 +397,21 @@ 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.
@@ -519,6 +546,9 @@ public class ViewDebug {
recyclerDump = new File(Environment.getExternalStorageDirectory(), "view-recycler/");
recyclerDump = new File(recyclerDump, sRecyclerTracePrefix + ".traces");
try {
+ if (recyclerDump.exists()) {
+ recyclerDump.delete();
+ }
final FileOutputStream file = new FileOutputStream(recyclerDump);
final DataOutputStream out = new DataOutputStream(file);
@@ -675,6 +705,146 @@ 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 {
@@ -683,6 +853,8 @@ public class ViewDebug {
if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
dump(view, clientStream);
+ } else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
+ captureLayers(view, new DataOutputStream(clientStream));
} else {
final String[] params = parameters.split(" ");
if (REMOTE_COMMAND_CAPTURE.equalsIgnoreCase(command)) {
@@ -702,7 +874,7 @@ public class ViewDebug {
if (parameter.indexOf('@') != -1) {
final String[] ids = parameter.split("@");
final String className = ids[0];
- final int hashCode = Integer.parseInt(ids[1], 16);
+ final int hashCode = (int) Long.parseLong(ids[1], 16);
View view = root.getRootView();
if (view instanceof ViewGroup) {
@@ -854,24 +1026,111 @@ public class ViewDebug {
return duration[0];
}
+ private static void captureLayers(View root, final DataOutputStream clientStream)
+ throws IOException {
+
+ try {
+ Rect outRect = new Rect();
+ try {
+ root.mAttachInfo.mSession.getDisplayFrame(root.mAttachInfo.mWindow, outRect);
+ } catch (RemoteException e) {
+ // Ignore
+ }
+
+ clientStream.writeInt(outRect.width());
+ clientStream.writeInt(outRect.height());
+
+ captureViewLayer(root, clientStream, true);
+
+ clientStream.write(2);
+ } finally {
+ clientStream.close();
+ }
+ }
+
+ private static void captureViewLayer(View view, DataOutputStream clientStream, boolean visible)
+ throws IOException {
+
+ final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
+
+ if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) {
+ final int id = view.getId();
+ String name = view.getClass().getSimpleName();
+ if (id != View.NO_ID) {
+ name = resolveId(view.getContext(), id).toString();
+ }
+
+ clientStream.write(1);
+ clientStream.writeUTF(name);
+ clientStream.writeByte(localVisible ? 1 : 0);
+
+ int[] position = new int[2];
+ // XXX: Should happen on the UI thread
+ view.getLocationInWindow(position);
+
+ clientStream.writeInt(position[0]);
+ clientStream.writeInt(position[1]);
+ clientStream.flush();
+
+ Bitmap b = performViewCapture(view, true);
+ if (b != null) {
+ ByteArrayOutputStream arrayOut = new ByteArrayOutputStream(b.getWidth() *
+ b.getHeight() * 2);
+ b.compress(Bitmap.CompressFormat.PNG, 100, arrayOut);
+ clientStream.writeInt(arrayOut.size());
+ arrayOut.writeTo(clientStream);
+ }
+ clientStream.flush();
+ }
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ int count = group.getChildCount();
+
+ for (int i = 0; i < count; i++) {
+ captureViewLayer(group.getChildAt(i), clientStream, localVisible);
+ }
+ }
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
final View captureView = findView(root, parameter);
+ Bitmap b = performViewCapture(captureView, false);
+
+ if (b != null) {
+ BufferedOutputStream out = null;
+ try {
+ out = new BufferedOutputStream(clientStream, 32 * 1024);
+ b.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ b.recycle();
+ }
+ } else {
+ Log.w("View", "Failed to create capture bitmap!");
+ clientStream.close();
+ }
+ }
+ private static Bitmap performViewCapture(final View captureView, final boolean skpiChildren) {
if (captureView != null) {
final CountDownLatch latch = new CountDownLatch(1);
final Bitmap[] cache = new Bitmap[1];
- root.post(new Runnable() {
+ captureView.post(new Runnable() {
public void run() {
try {
cache[0] = captureView.createSnapshot(
- Bitmap.Config.ARGB_8888, 0);
+ Bitmap.Config.ARGB_8888, 0, skpiChildren);
} catch (OutOfMemoryError e) {
try {
cache[0] = captureView.createSnapshot(
- Bitmap.Config.ARGB_4444, 0);
+ Bitmap.Config.ARGB_4444, 0, skpiChildren);
} catch (OutOfMemoryError e2) {
Log.w("View", "Out of memory for bitmap");
}
@@ -883,34 +1142,20 @@ public class ViewDebug {
try {
latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
-
- if (cache[0] != null) {
- BufferedOutputStream out = null;
- try {
- out = new BufferedOutputStream(clientStream, 32 * 1024);
- cache[0].compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- } finally {
- if (out != null) {
- out.close();
- }
- cache[0].recycle();
- }
- } else {
- Log.w("View", "Failed to create capture bitmap!");
- clientStream.close();
- }
+ return cache[0];
} catch (InterruptedException e) {
Log.w("View", "Could not complete the capture of the view " + captureView);
Thread.currentThread().interrupt();
}
}
+
+ return null;
}
private static void dump(View root, OutputStream clientStream) throws IOException {
BufferedWriter out = null;
try {
- out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
+ out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
View view = root.getRootView();
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
@@ -1300,7 +1545,7 @@ public class ViewDebug {
}
}
- private static Object resolveId(Context context, int id) {
+ static Object resolveId(Context context, int id) {
Object fieldValue;
final Resources resources = context.getResources();
if (id >= 0) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e2f15c7..eca583f 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,7 @@ package android.view;
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -679,6 +680,32 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void dispatchDisplayHint(int hint) {
+ super.dispatchDisplayHint(hint);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchDisplayHint(hint);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void dispatchVisibilityChanged(View changedView, int visibility) {
+ super.dispatchVisibilityChanged(changedView, visibility);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchVisibilityChanged(changedView, visibility);
+ }
+ }
/**
* {@inheritDoc}
@@ -696,6 +723,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*/
+ @Override
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ super.dispatchConfigurationChanged(newConfig);
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchConfigurationChanged(newConfig);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public void recomputeViewAttributes(View child) {
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
@@ -819,6 +859,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
+ child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;
@@ -849,6 +890,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
+ if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ }
return super.dispatchTouchEvent(ev);
}
@@ -857,6 +902,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
+ mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
@@ -881,6 +927,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
+ if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ mMotionTarget = null;
+ }
+
return target.dispatchTouchEvent(ev);
}
@@ -1025,6 +1077,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
return false;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchStartTemporaryDetach() {
+ super.dispatchStartTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchStartTemporaryDetach();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ super.dispatchFinishTemporaryDetach();
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < count; i++) {
+ children[i].dispatchFinishTemporaryDetach();
+ }
+ }
/**
* {@inheritDoc}
@@ -1190,6 +1272,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
+ int count = mChildrenCount;
+ int[] visibilities = null;
+
+ if (skipChildren) {
+ visibilities = new int[count];
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ visibilities[i] = child.getVisibility();
+ if (visibilities[i] == View.VISIBLE) {
+ child.setVisibility(INVISIBLE);
+ }
+ }
+ }
+
+ Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren);
+
+ if (skipChildren) {
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setVisibility(visibilities[i]);
+ }
+ }
+
+ return b;
+ }
+
/**
* {@inheritDoc}
*/
@@ -2255,7 +2364,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
addInArray(child, index);
child.mParent = this;
- child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) | DRAWN;
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
@@ -3057,7 +3166,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
@@ -3075,7 +3184,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
@@ -3094,7 +3203,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
- } else if (childDimension == LayoutParams.FILL_PARENT) {
+ } else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
@@ -3348,11 +3457,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* The base LayoutParams class just describes how big the view wants to be
* for both width and height. For each dimension, it can specify one of:
* <ul>
- * <li> an exact number
- * <li>FILL_PARENT, which means the view wants to be as big as its parent
- * (minus padding)
+ * <li>FILL_PARENT (renamed MATCH_PARENT in API Level 8 and higher), which
+ * means that the view wants to be as big as its parent (minus padding)
* <li> WRAP_CONTENT, which means that the view wants to be just big enough
* to enclose its content (plus padding)
+ * <li> an exact number
* </ul>
* There are subclasses of LayoutParams for different subclasses of
* ViewGroup. For example, AbsoluteLayout has its own subclass of
@@ -3364,34 +3473,46 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public static class LayoutParams {
/**
* Special value for the height or width requested by a View.
- * FILL_PARENT means that the view wants to fill the available space
- * within the parent, taking the parent's padding into account.
+ * FILL_PARENT means that the view wants to be as big as its parent,
+ * minus the parent's padding, if any. This value is deprecated
+ * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
*/
+ @SuppressWarnings({"UnusedDeclaration"})
+ @Deprecated
public static final int FILL_PARENT = -1;
/**
* Special value for the height or width requested by a View.
+ * MATCH_PARENT means that the view wants to be as big as its parent,
+ * minus the parent's padding, if any. Introduced in API Level 8.
+ */
+ public static final int MATCH_PARENT = -1;
+
+ /**
+ * Special value for the height or width requested by a View.
* WRAP_CONTENT means that the view wants to be just large enough to fit
* its own internal content, taking its own padding into account.
*/
public static final int WRAP_CONTENT = -2;
/**
- * Information about how wide the view wants to be. Can be an exact
- * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+ * Information about how wide the view wants to be. Can be one of the
+ * constants FILL_PARENT (replaced by MATCH_PARENT ,
+ * in API Level 8) or WRAP_CONTENT. or an exact size.
*/
@ViewDebug.ExportedProperty(mapping = {
- @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int width;
/**
- * Information about how tall the view wants to be. Can be an exact
- * size, or one of the constants FILL_PARENT or WRAP_CONTENT.
+ * Information about how tall the view wants to be. Can be one of the
+ * constants FILL_PARENT (replaced by MATCH_PARENT ,
+ * in API Level 8) or WRAP_CONTENT. or an exact size.
*/
@ViewDebug.ExportedProperty(mapping = {
- @ViewDebug.IntToString(from = FILL_PARENT, to = "FILL_PARENT"),
+ @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
public int height;
@@ -3408,9 +3529,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* <ul>
* <li><code>layout_width</code>: the width, either an exact value,
- * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+ * {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+ * {@link #MATCH_PARENT} in API Level 8)</li>
* <li><code>layout_height</code>: the height, either an exact value,
- * {@link #WRAP_CONTENT} or {@link #FILL_PARENT}</li>
+ * {@link #WRAP_CONTENT}, or {@link #FILL_PARENT} (replaced by
+ * {@link #MATCH_PARENT} in API Level 8)</li>
* </ul>
*
* @param c the application environment
@@ -3429,10 +3552,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Creates a new set of layout parameters with the specified width
* and height.
*
- * @param width the width, either {@link #FILL_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
- * {@link #WRAP_CONTENT} or a fixed size in pixels
+ * @param width the width, either {@link #WRAP_CONTENT},
+ * {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+ * API Level 8), or a fixed size in pixels
+ * @param height the height, either {@link #WRAP_CONTENT},
+ * {@link #FILL_PARENT} (replaced by {@link #MATCH_PARENT} in
+ * API Level 8), or a fixed size in pixels
*/
public LayoutParams(int width, int height) {
this.width = width;
@@ -3494,8 +3619,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (size == WRAP_CONTENT) {
return "wrap-content";
}
- if (size == FILL_PARENT) {
- return "fill-parent";
+ if (size == MATCH_PARENT) {
+ return "match-parent";
}
return String.valueOf(size);
}
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 4e12250..03efea9 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -41,7 +41,9 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.app.ActivityManagerNative;
import android.Manifest;
@@ -68,6 +70,7 @@ public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
private static final String TAG = "ViewRoot";
private static final boolean DBG = false;
+ private static final boolean SHOW_FPS = false;
@SuppressWarnings({"ConstantConditionalExpression"})
private static final boolean LOCAL_LOGV = false ? Config.LOGD : Config.LOGV;
/** @noinspection PointlessBooleanExpression*/
@@ -77,6 +80,7 @@ public final class ViewRoot extends Handler implements ViewParent,
private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
+ private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean WATCH_POINTER = false;
private static final boolean MEASURE_LATENCY = false;
@@ -97,6 +101,12 @@ public final class ViewRoot extends Handler implements ViewParent,
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
+ static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
+ static boolean sFirstDrawComplete = false;
+
+ static final ArrayList<ComponentCallbacks> sConfigCallbacks
+ = new ArrayList<ComponentCallbacks>();
+
private static int sDrawTime;
long mLastTrackballTime = 0;
@@ -167,6 +177,15 @@ public final class ViewRoot extends Handler implements ViewParent,
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
= new ViewTreeObserver.InternalInsetsInfo();
+ final Configuration mLastConfiguration = new Configuration();
+ final Configuration mPendingConfiguration = new Configuration();
+
+ class ResizedInfo {
+ Rect coveredInsets;
+ Rect visibleInsets;
+ Configuration newConfig;
+ }
+
boolean mScrollMayChange;
int mSoftInputMode;
View mLastScrolledFocus;
@@ -215,7 +234,8 @@ public final class ViewRoot extends Handler implements ViewParent,
lt = new LatencyTimer(100, 1000);
}
- ++sInstanceCount;
+ // For debug only
+ //++sInstanceCount;
// Initialize the statics when this class is first instantiated. This is
// done here instead of in the static block because Zygote does not
@@ -243,16 +263,33 @@ public final class ViewRoot extends Handler implements ViewParent,
mDensity = context.getResources().getDisplayMetrics().densityDpi;
}
+ // For debug only
+ /*
@Override
protected void finalize() throws Throwable {
super.finalize();
--sInstanceCount;
}
+ */
public static long getInstanceCount() {
return sInstanceCount;
}
+ public static void addFirstDrawHandler(Runnable callback) {
+ synchronized (sFirstDrawHandlers) {
+ if (!sFirstDrawComplete) {
+ sFirstDrawHandlers.add(callback);
+ }
+ }
+ }
+
+ public static void addConfigCallback(ComponentCallbacks callback) {
+ synchronized (sConfigCallbacks) {
+ sConfigCallbacks.add(callback);
+ }
+ }
+
// FIXME for perf testing only
private boolean mProfile = false;
@@ -686,6 +723,7 @@ public final class ViewRoot extends Handler implements ViewParent,
attachInfo.mRecomputeGlobalAttributes = false;
attachInfo.mKeepScreenOn = false;
viewVisibilityChanged = false;
+ mLastConfiguration.setTo(host.getResources().getConfiguration());
host.dispatchAttachedToWindow(attachInfo, 0);
//Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
@@ -814,7 +852,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent
- && (mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight);
+ && ((mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight)
+ || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
+ frame.width() < desiredWindowWidth && frame.width() != mWidth)
+ || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
+ frame.height() < desiredWindowHeight && frame.height() != mHeight));
final boolean computesInternalInsets =
attachInfo.mTreeObserver.hasComputeInternalInsetsListeners();
@@ -871,6 +913,13 @@ public final class ViewRoot extends Handler implements ViewParent,
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " surface=" + mSurface);
+ if (mPendingConfiguration.seq != 0) {
+ if (DEBUG_CONFIGURATION) Log.v(TAG, "Visible with new config: "
+ + mPendingConfiguration);
+ updateConfiguration(mPendingConfiguration, !mFirst);
+ mPendingConfiguration.seq = 0;
+ }
+
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
visibleInsetsChanged = !mPendingVisibleInsets.equals(
@@ -1166,7 +1215,7 @@ public final class ViewRoot extends Handler implements ViewParent,
int measureSpec;
switch (rootDimension) {
- case ViewGroup.LayoutParams.FILL_PARENT:
+ case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
@@ -1188,6 +1237,15 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
+ if (!sFirstDrawComplete) {
+ synchronized (sFirstDrawHandlers) {
+ sFirstDrawComplete = true;
+ for (int i=0; i<sFirstDrawHandlers.size(); i++) {
+ post(sFirstDrawHandlers.get(i));
+ }
+ }
+ }
+
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
@@ -1244,7 +1302,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@@ -1356,7 +1414,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
}
- if (Config.DEBUG && ViewDebug.showFps) {
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
nativeShowFPS(canvas, now - sDrawTime);
@@ -1446,8 +1504,10 @@ public final class ViewRoot extends Handler implements ViewParent,
focus.getFocusedRect(mTempRect);
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Focus " + focus
+ ": focusRect=" + mTempRect.toShortString());
- ((ViewGroup) mView).offsetDescendantRectToMyCoords(
- focus, mTempRect);
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focus, mTempRect);
+ }
if (DEBUG_INPUT_RESIZE) Log.v(TAG,
"Focus in window: focusRect="
+ mTempRect.toShortString()
@@ -1585,6 +1645,30 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
+ void updateConfiguration(Configuration config, boolean force) {
+ if (DEBUG_CONFIGURATION) Log.v(TAG,
+ "Applying new config to window "
+ + mWindowAttributes.getTitle()
+ + ": " + config);
+ synchronized (sConfigCallbacks) {
+ for (int i=sConfigCallbacks.size()-1; i>=0; i--) {
+ sConfigCallbacks.get(i).onConfigurationChanged(config);
+ }
+ }
+ if (mView != null) {
+ // At this point the resources have been updated to
+ // have the most recent config, whatever that is. Use
+ // the on in them which may be newer.
+ if (mView != null) {
+ config = mView.getResources().getConfiguration();
+ }
+ if (force || mLastConfiguration.diff(config) != 0) {
+ mLastConfiguration.setTo(config);
+ mView.dispatchConfigurationChanged(config);
+ }
+ }
+ }
+
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
@@ -1597,6 +1681,16 @@ public final class ViewRoot extends Handler implements ViewParent,
return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
}
+ private static void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
public final static int DO_TRAVERSAL = 1000;
public final static int DIE = 1001;
@@ -1761,26 +1855,34 @@ public final class ViewRoot extends Handler implements ViewParent,
handleGetNewSurface();
break;
case RESIZED:
- Rect coveredInsets = ((Rect[])msg.obj)[0];
- Rect visibleInsets = ((Rect[])msg.obj)[1];
+ ResizedInfo ri = (ResizedInfo)msg.obj;
if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
- && mPendingContentInsets.equals(coveredInsets)
- && mPendingVisibleInsets.equals(visibleInsets)) {
+ && mPendingContentInsets.equals(ri.coveredInsets)
+ && mPendingVisibleInsets.equals(ri.visibleInsets)
+ && ((ResizedInfo)msg.obj).newConfig == null) {
break;
}
// fall through...
case RESIZED_REPORT:
if (mAdded) {
+ Configuration config = ((ResizedInfo)msg.obj).newConfig;
+ if (config != null) {
+ updateConfiguration(config, false);
+ }
mWinFrame.left = 0;
mWinFrame.right = msg.arg1;
mWinFrame.top = 0;
mWinFrame.bottom = msg.arg2;
- mPendingContentInsets.set(((Rect[])msg.obj)[0]);
- mPendingVisibleInsets.set(((Rect[])msg.obj)[1]);
+ mPendingContentInsets.set(((ResizedInfo)msg.obj).coveredInsets);
+ mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets);
if (msg.what == RESIZED_REPORT) {
mReportNextDraw = true;
}
+
+ if (mView != null) {
+ forceLayout(mView);
+ }
requestLayout();
}
break;
@@ -2189,7 +2291,8 @@ public final class ViewRoot extends Handler implements ViewParent,
* leaving touch mode alone is considered the event).
*/
private boolean checkForLeavingTouchModeAndConsume(KeyEvent event) {
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ final int action = event.getAction();
+ if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_MULTIPLE) {
return false;
}
if ((event.getFlags()&KeyEvent.FLAG_KEEP_TOUCH_MODE) != 0) {
@@ -2395,8 +2498,12 @@ public final class ViewRoot extends Handler implements ViewParent,
// of previous focused into the coord system of
// newly focused view
focused.getFocusedRect(mTempRect);
- ((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect);
- ((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect);
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focused, mTempRect);
+ ((ViewGroup) mView).offsetRectIntoDescendantCoords(
+ v, mTempRect);
+ }
focusPassed = v.requestFocus(direction, mTempRect);
}
@@ -2447,12 +2554,14 @@ public final class ViewRoot extends Handler implements ViewParent,
if (params != null) {
if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
}
+ mPendingConfiguration.seq = 0;
int relayoutResult = sWindowSession.relayout(
mWindow, params,
(int) (mView.mMeasuredWidth * appScale + 0.5f),
(int) (mView.mMeasuredHeight * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
- mPendingContentInsets, mPendingVisibleInsets, mSurface);
+ mPendingContentInsets, mPendingVisibleInsets,
+ mPendingConfiguration, mSurface);
if (restore) {
params.restore();
}
@@ -2471,27 +2580,33 @@ public final class ViewRoot extends Handler implements ViewParent,
public void playSoundEffect(int effectId) {
checkThread();
- final AudioManager audioManager = getAudioManager();
+ try {
+ final AudioManager audioManager = getAudioManager();
- switch (effectId) {
- case SoundEffectConstants.CLICK:
- audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
- return;
- case SoundEffectConstants.NAVIGATION_DOWN:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
- return;
- case SoundEffectConstants.NAVIGATION_LEFT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
- return;
- case SoundEffectConstants.NAVIGATION_RIGHT:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
- return;
- case SoundEffectConstants.NAVIGATION_UP:
- audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
- return;
- default:
- throw new IllegalArgumentException("unknown effect id " + effectId +
- " not defined in " + SoundEffectConstants.class.getCanonicalName());
+ switch (effectId) {
+ case SoundEffectConstants.CLICK:
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ return;
+ case SoundEffectConstants.NAVIGATION_DOWN:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+ return;
+ case SoundEffectConstants.NAVIGATION_LEFT:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+ return;
+ case SoundEffectConstants.NAVIGATION_RIGHT:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+ return;
+ case SoundEffectConstants.NAVIGATION_UP:
+ audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+ return;
+ default:
+ throw new IllegalArgumentException("unknown effect id " + effectId +
+ " not defined in " + SoundEffectConstants.class.getCanonicalName());
+ }
+ } catch (IllegalStateException e) {
+ // Exception thrown by getAudioManager() when mView is null
+ Log.e(TAG, "FATAL EXCEPTION when attempting to play sound effect: " + e);
+ e.printStackTrace();
}
}
@@ -2566,7 +2681,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
public void dispatchResized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w
+ " h=" + h + " coveredInsets=" + coveredInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
@@ -2580,7 +2695,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
msg.arg1 = w;
msg.arg2 = h;
- msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) };
+ ResizedInfo ri = new ResizedInfo();
+ ri.coveredInsets = new Rect(coveredInsets);
+ ri.visibleInsets = new Rect(visibleInsets);
+ ri.newConfig = newConfig;
+ msg.obj = ri;
sendMessage(msg);
}
@@ -2781,11 +2900,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
viewRoot.dispatchResized(w, h, coveredInsets,
- visibleInsets, reportDraw);
+ visibleInsets, reportDraw, newConfig);
}
}
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
index 703a38f..d5e9af4 100644
--- a/core/java/android/view/ViewStub.java
+++ b/core/java/android/view/ViewStub.java
@@ -207,9 +207,11 @@ public final class ViewStub extends View {
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
- } else if (visibility == VISIBLE || visibility == INVISIBLE) {
+ } else {
super.setVisibility(visibility);
- inflate();
+ if (visibility == VISIBLE || visibility == INVISIBLE) {
+ inflate();
+ }
}
}
@@ -244,7 +246,7 @@ public final class ViewStub extends View {
parent.addView(view, index);
}
- mInflatedViewRef = new WeakReference(view);
+ mInflatedViewRef = new WeakReference<View>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 1932765..7dd5085 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -484,7 +484,7 @@ public abstract class Window {
/**
* Set the width and height layout parameters of the window. The default
- * for both of these is FILL_PARENT; you can change them to WRAP_CONTENT to
+ * for both of these is MATCH_PARENT; you can change them to WRAP_CONTENT to
* make a window that is not full-screen.
*
* @param width The desired layout width of the window.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6696533..0aa1fde 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -142,6 +142,29 @@ public interface WindowManager extends ViewManager {
* @see #TYPE_INPUT_METHOD
* @see #TYPE_INPUT_METHOD_DIALOG
*/
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = TYPE_BASE_APPLICATION, to = "TYPE_BASE_APPLICATION"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION, to = "TYPE_APPLICATION"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING, to = "TYPE_APPLICATION_STARTING"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL, to = "TYPE_APPLICATION_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA, to = "TYPE_APPLICATION_MEDIA"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL, to = "TYPE_APPLICATION_SUB_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG, to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_STATUS_BAR, to = "TYPE_STATUS_BAR"),
+ @ViewDebug.IntToString(from = TYPE_SEARCH_BAR, to = "TYPE_SEARCH_BAR"),
+ @ViewDebug.IntToString(from = TYPE_PHONE, to = "TYPE_PHONE"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT, to = "TYPE_SYSTEM_ALERT"),
+ @ViewDebug.IntToString(from = TYPE_KEYGUARD, to = "TYPE_KEYGUARD"),
+ @ViewDebug.IntToString(from = TYPE_TOAST, to = "TYPE_TOAST"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY, to = "TYPE_SYSTEM_OVERLAY"),
+ @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE, to = "TYPE_PRIORITY_PHONE"),
+ @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL, to = "TYPE_STATUS_BAR_PANEL"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG, to = "TYPE_SYSTEM_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG, to = "TYPE_KEYGUARD_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR, to = "TYPE_SYSTEM_ERROR"),
+ @ViewDebug.IntToString(from = TYPE_INPUT_METHOD, to = "TYPE_INPUT_METHOD"),
+ @ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG, to = "TYPE_INPUT_METHOD_DIALOG")
+ })
public int type;
/**
@@ -367,8 +390,46 @@ public interface WindowManager extends ViewManager {
* @see #FLAG_FORCE_NOT_FULLSCREEN
* @see #FLAG_IGNORE_CHEEK_PRESSES
*/
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
+ name = "FLAG_BLUR_BEHIND"),
+ @ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
+ name = "FLAG_DIM_BEHIND"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
+ name = "FLAG_NOT_FOCUSABLE"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
+ name = "FLAG_NOT_TOUCHABLE"),
+ @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
+ name = "FLAG_NOT_TOUCH_MODAL"),
+ @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
+ name = "FLAG_LAYOUT_IN_SCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
+ name = "FLAG_DITHER"),
+ @ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
+ name = "FLAG_TURN_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
+ name = "FLAG_KEEP_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
+ name = "FLAG_SHOW_WHEN_LOCKED"),
+ @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
+ name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+ @ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
+ name = "FLAG_DISMISS_KEYGUARD"),
+ @ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
+ name = "FLAG_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN,
+ equals = FLAG_FORCE_NOT_FULLSCREEN, name = "FLAG_FORCE_NOT_FULLSCREEN"),
+ @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES,
+ equals = FLAG_IGNORE_CHEEK_PRESSES, name = "FLAG_IGNORE_CHEEK_PRESSES")
+ })
public int flags;
+ /** Window flag: as long as this window is visible to the user, allow
+ * the lock screen to activate while the screen is on.
+ * This can be used independently, or in combination with
+ * {@link #FLAG_KEEP_SCREEN_ON} and/or {@link #FLAG_SHOW_WHEN_LOCKED} */
+ public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
+
/** Window flag: everything behind this window will be dimmed.
* Use {@link #dimAmount} to control the amount of dim. */
public static final int FLAG_DIM_BEHIND = 0x00000002;
@@ -649,7 +710,28 @@ public interface WindowManager extends ViewManager {
* be cleared automatically after the window is displayed.
*/
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;
-
+
+ /**
+ * Default value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the brightness value is not overridden for this window
+ * and normal brightness policy should be used.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the lowest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
+
+ /**
+ * Value for {@link #screenBrightness} and {@link #buttonBrightness}
+ * indicating that the screen or button backlight brightness should be set
+ * to the hightest value when this window is in front.
+ */
+ public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
+
/**
* Desired operating mode for any soft input area. May any combination
* of:
@@ -717,9 +799,17 @@ public interface WindowManager extends ViewManager {
* preferred screen brightness. 0 to 1 adjusts the brightness from
* dark to full bright.
*/
- public float screenBrightness = -1.0f;
+ public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
/**
+ * This can be used to override the standard behavior of the button and
+ * keyboard backlights. A value of less than 0, the default, means to
+ * use the standard backlight behavior. 0 to 1 adjusts the brightness
+ * from dark to full bright.
+ */
+ public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -742,26 +832,26 @@ public interface WindowManager extends ViewManager {
public LayoutParams() {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type, int _flags) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = PixelFormat.OPAQUE;
}
public LayoutParams(int _type, int _flags, int _format) {
- super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = _type;
flags = _flags;
format = _format;
@@ -816,6 +906,7 @@ public interface WindowManager extends ViewManager {
out.writeFloat(alpha);
out.writeFloat(dimAmount);
out.writeFloat(screenBrightness);
+ out.writeFloat(buttonBrightness);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
@@ -851,6 +942,7 @@ public interface WindowManager extends ViewManager {
alpha = in.readFloat();
dimAmount = in.readFloat();
screenBrightness = in.readFloat();
+ buttonBrightness = in.readFloat();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -870,6 +962,8 @@ public interface WindowManager extends ViewManager {
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ /** {@hide} */
+ public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<12;
// internal buffer to backup/restore parameters under compatibility mode.
private int[] mCompatibilityParamsBackup = null;
@@ -971,6 +1065,10 @@ public interface WindowManager extends ViewManager {
screenBrightness = o.screenBrightness;
changes |= SCREEN_BRIGHTNESS_CHANGED;
}
+ if (buttonBrightness != o.buttonBrightness) {
+ buttonBrightness = o.buttonBrightness;
+ changes |= BUTTON_BRIGHTNESS_CHANGED;
+ }
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
@@ -999,9 +1097,9 @@ public interface WindowManager extends ViewManager {
sb.append(',');
sb.append(y);
sb.append(")(");
- sb.append((width==FILL_PARENT?"fill":(width==WRAP_CONTENT?"wrap":width)));
+ sb.append((width== MATCH_PARENT ?"fill":(width==WRAP_CONTENT?"wrap":width)));
sb.append('x');
- sb.append((height==FILL_PARENT?"fill":(height==WRAP_CONTENT?"wrap":height)));
+ sb.append((height== MATCH_PARENT ?"fill":(height==WRAP_CONTENT?"wrap":height)));
sb.append(")");
if (softInputMode != 0) {
sb.append(" sim=#");
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 083793b..b39cb9d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -237,25 +237,6 @@ public interface WindowManagerPolicy {
public boolean hasAppShownWindows();
/**
- * Return true if the application token has been asked to display an
- * app starting icon as the application is starting up.
- *
- * @return Returns true if setAppStartingIcon() was called for this
- * window's token.
- */
- public boolean hasAppStartingIcon();
-
- /**
- * Return the Window that is being displayed as this window's
- * application token is being started.
- *
- * @return Returns the currently displayed starting window, or null if
- * it was not requested, has not yet been displayed, or has
- * been removed.
- */
- public WindowState getAppStartingWindow();
-
- /**
* Is this window visible? It is not visible if there is no
* surface, or we are in the process of running an exit animation
* that will remove the surface.
@@ -376,12 +357,17 @@ public interface WindowManagerPolicy {
* previous activity, and both are on top of he wallpaper. */
public final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+ // NOTE: screen off reasons are in order of significance, with more
+ // important ones lower than less important ones.
+
+ /** Screen turned off because of a device admin */
+ public final int OFF_BECAUSE_OF_ADMIN = 1;
/** Screen turned off because of power button */
- public final int OFF_BECAUSE_OF_USER = 1;
+ public final int OFF_BECAUSE_OF_USER = 2;
/** Screen turned off because of timeout */
- public final int OFF_BECAUSE_OF_TIMEOUT = 2;
+ public final int OFF_BECAUSE_OF_TIMEOUT = 3;
/** Screen turned off because of proximity sensor */
- public final int OFF_BECAUSE_OF_PROX_SENSOR = 3;
+ public final int OFF_BECAUSE_OF_PROX_SENSOR = 4;
/**
* Magic constant to {@link IWindowManager#setRotation} to not actually
@@ -641,8 +627,9 @@ public interface WindowManagerPolicy {
* returned, all windows given to layoutWindow() <em>must</em> have had a
* frame assigned.
*
- * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT}
- * and {@link #FINISH_LAYOUT_REDO_CONFIG}.
+ * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link #FINISH_LAYOUT_REDO_ANIM}.
*/
public int finishLayoutLw();
@@ -652,6 +639,8 @@ public interface WindowManagerPolicy {
static final int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
/** Wallpaper may need to move */
static final int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
+ /** Need to recompute animations */
+ static final int FINISH_LAYOUT_REDO_ANIM = 0x0008;
/**
* Called when animation of the windows is about to start.
@@ -675,12 +664,21 @@ public interface WindowManagerPolicy {
* something that may have modified the animation state of another window,
* be sure to return true in order to perform another animation frame.
*
- * @return Return true if animation state may have changed (so that another
- * frame of animation will be run).
+ * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
+ * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
+ * or {@link #FINISH_LAYOUT_REDO_ANIM}.
*/
- public boolean finishAnimationLw();
+ public int finishAnimationLw();
/**
+ * Return true if it is okay to perform animations for an app transition
+ * that is about to occur. You may return false for this if, for example,
+ * the lock screen is currently displayed so the switch should happen
+ * immediately.
+ */
+ public boolean allowAppAnimationsLw();
+
+ /**
* Called after the screen turns off.
*
* @param why {@link #OFF_BECAUSE_OF_USER} or
@@ -694,6 +692,11 @@ public interface WindowManagerPolicy {
public void screenTurnedOn();
/**
+ * Return whether the screen is currently on.
+ */
+ public boolean isScreenOn();
+
+ /**
* Perform any initial processing of a low-level input event before the
* window manager handles special keys and generates a high-level event
* that is dispatched to the application.
@@ -795,11 +798,6 @@ public interface WindowManagerPolicy {
void exitKeyguardSecurely(OnKeyguardExitResult callback);
/**
- * Return if keyguard is currently showing.
- */
- public boolean keyguardIsShowingTq();
-
- /**
* inKeyguardRestrictedKeyInputMode
*
* if keyguard screen is showing or in restricted key input mode (i.e. in
@@ -833,6 +831,12 @@ public interface WindowManagerPolicy {
public void systemReady();
/**
+ * Called when userActivity is signalled in the power manager.
+ * This is safe to call from any thread, with any window manager locks held or not.
+ */
+ public void userActivity();
+
+ /**
* Called when we have finished booting and can now display the home
* screen to the user. This wilWl happen after systemReady(), and at
* this point the display is active.
@@ -851,6 +855,11 @@ public interface WindowManagerPolicy {
*/
public boolean isCheekPressedAgainstScreen(MotionEvent ev);
+ /**
+ * Called every time the window manager is dispatching a pointer event.
+ */
+ public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY);
+
public void setCurrentOrientationLw(int newOrientation);
/**
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 13606e7..25df1f4 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -33,13 +33,12 @@ import android.util.Log;
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ private static final boolean localLOGV = DEBUG || Config.DEBUG;
private SensorManager mSensorManager;
private boolean mEnabled = false;
private int mRate;
private Sensor mSensor;
- private SensorEventListener mSensorEventListener;
- private int mSensorRotation = -1;
+ private SensorEventListenerImpl mSensorEventListener;
/**
* Creates a new WindowOrientationListener.
@@ -58,8 +57,12 @@ public abstract class WindowOrientationListener {
* {@link android.hardware.SensorManager SensorManager}). Use the default
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
+ *
+ * This constructor is private since no one uses it and making it public would complicate
+ * things, since the lowpass filtering code depends on the actual sampling period, and there's
+ * no way to get the period from SensorManager based on the rate constant.
*/
- public WindowOrientationListener(Context context, int rate) {
+ private WindowOrientationListener(Context context, int rate) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
@@ -80,7 +83,6 @@ public abstract class WindowOrientationListener {
}
if (mEnabled == false) {
if (localLOGV) Log.d(TAG, "WindowOrientationListener enabled");
- mSensorRotation = -1;
mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
mEnabled = true;
}
@@ -96,111 +98,189 @@ public abstract class WindowOrientationListener {
}
if (mEnabled == true) {
if (localLOGV) Log.d(TAG, "WindowOrientationListener disabled");
- mSensorRotation = -1;
mSensorManager.unregisterListener(mSensorEventListener);
mEnabled = false;
}
}
public int getCurrentRotation() {
- return mSensorRotation;
+ if (mEnabled) {
+ return mSensorEventListener.getCurrentRotation();
+ }
+ return -1;
}
class SensorEventListenerImpl implements SensorEventListener {
+ // We work with all angles in degrees in this class.
+ private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
+
+ // Indices into SensorEvent.values
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
- // Angle around x-axis thats considered almost perfect vertical to hold
- // the device
- private static final int PIVOT = 20;
- // Angle around x-asis that's considered almost too vertical. Beyond
- // this angle will not result in any orientation changes. f phone faces uses,
- // the device is leaning backward.
- private static final int PIVOT_UPPER = 65;
- // Angle about x-axis that's considered negative vertical. Beyond this
- // angle will not result in any orientation changes. If phone faces uses,
- // the device is leaning forward.
- private static final int PIVOT_LOWER = -10;
- // Upper threshold limit for switching from portrait to landscape
- private static final int PL_UPPER = 295;
- // Lower threshold limit for switching from landscape to portrait
- private static final int LP_LOWER = 320;
- // Lower threshold limt for switching from portrait to landscape
- private static final int PL_LOWER = 270;
- // Upper threshold limit for switching from landscape to portrait
- private static final int LP_UPPER = 359;
- // Minimum angle which is considered landscape
- private static final int LANDSCAPE_LOWER = 235;
- // Minimum angle which is considered portrait
- private static final int PORTRAIT_LOWER = 60;
-
- // Internal value used for calculating linear variant
- private static final float PL_LF_UPPER =
- ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT_UPPER-PIVOT));
- private static final float PL_LF_LOWER =
- ((float)(PL_UPPER-PL_LOWER))/((float)(PIVOT-PIVOT_LOWER));
- // Internal value used for calculating linear variant
- private static final float LP_LF_UPPER =
- ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT_UPPER-PIVOT));
- private static final float LP_LF_LOWER =
- ((float)(LP_UPPER - LP_LOWER))/((float)(PIVOT-PIVOT_LOWER));
-
- public void onSensorChanged(SensorEvent event) {
- float[] values = event.values;
- float X = values[_DATA_X];
- float Y = values[_DATA_Y];
- float Z = values[_DATA_Z];
- float OneEightyOverPi = 57.29577957855f;
- float gravity = (float) Math.sqrt(X*X+Y*Y+Z*Z);
- float zyangle = (float)Math.asin(Z/gravity)*OneEightyOverPi;
- int rotation = -1;
- if ((zyangle <= PIVOT_UPPER) && (zyangle >= PIVOT_LOWER)) {
- // Check orientation only if the phone is flat enough
- // Don't trust the angle if the magnitude is small compared to the y value
- float angle = (float)Math.atan2(Y, -X) * OneEightyOverPi;
- int orientation = 90 - (int)Math.round(angle);
- // normalize to 0 - 359 range
- while (orientation >= 360) {
- orientation -= 360;
- }
- while (orientation < 0) {
- orientation += 360;
- }
- // Orientation values between LANDSCAPE_LOWER and PL_LOWER
- // are considered landscape.
- // Ignore orientation values between 0 and LANDSCAPE_LOWER
- // For orientation values between LP_UPPER and PL_LOWER,
- // the threshold gets set linearly around PIVOT.
- if ((orientation >= PL_LOWER) && (orientation <= LP_UPPER)) {
- float threshold;
- float delta = zyangle - PIVOT;
- if (mSensorRotation == Surface.ROTATION_90) {
- if (delta < 0) {
- // Delta is negative
- threshold = LP_LOWER - (LP_LF_LOWER * delta);
- } else {
- threshold = LP_LOWER + (LP_LF_UPPER * delta);
- }
- rotation = (orientation >= threshold) ? Surface.ROTATION_0 : Surface.ROTATION_90;
- } else {
- if (delta < 0) {
- // Delta is negative
- threshold = PL_UPPER+(PL_LF_LOWER * delta);
- } else {
- threshold = PL_UPPER-(PL_LF_UPPER * delta);
- }
- rotation = (orientation <= threshold) ? Surface.ROTATION_90: Surface.ROTATION_0;
- }
- } else if ((orientation >= LANDSCAPE_LOWER) && (orientation < LP_LOWER)) {
- rotation = Surface.ROTATION_90;
- } else if ((orientation >= PL_UPPER) || (orientation <= PORTRAIT_LOWER)) {
- rotation = Surface.ROTATION_0;
- }
- if ((rotation != -1) && (rotation != mSensorRotation)) {
- mSensorRotation = rotation;
- onOrientationChanged(mSensorRotation);
+
+ // Internal aliases for the four orientation states. ROTATION_0 = default portrait mode,
+ // ROTATION_90 = right side of device facing the sky, etc.
+ private static final int ROTATION_0 = 0;
+ private static final int ROTATION_90 = 1;
+ private static final int ROTATION_270 = 2;
+
+ // Current orientation state
+ private int mRotation = ROTATION_0;
+
+ // Mapping our internal aliases into actual Surface rotation values
+ private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90,
+ Surface.ROTATION_270};
+
+ // Threshold ranges of orientation angle to transition into other orientation states.
+ // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
+ // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
+ // in sync with this.
+ // The thresholds are nearly regular -- we generally transition about the halfway point
+ // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180,
+ // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180.
+ private final int[][][] THRESHOLDS = new int[][][] {
+ {{60, 180}, {180, 300}},
+ {{0, 45}, {45, 165}, {330, 360}},
+ {{0, 30}, {195, 315}, {315, 360}}
+ };
+
+ // See THRESHOLDS
+ private final int[][] ROTATE_TO = new int[][] {
+ {ROTATION_270, ROTATION_90},
+ {ROTATION_0, ROTATION_270, ROTATION_0},
+ {ROTATION_0, ROTATION_90, ROTATION_0}
+ };
+
+ // Maximum absolute tilt angle at which to consider orientation changes. Beyond this (i.e.
+ // when screen is facing the sky or ground), we refuse to make any orientation changes.
+ private static final int MAX_TILT = 65;
+
+ // Additional limits on tilt angle to transition to each new orientation. We ignore all
+ // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a
+ // particular orientation here.
+ private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT};
+
+ // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
+ // with a higher time constant, making us less sensitive to change. This primarily helps
+ // prevent momentary orientation changes when placing a device on a table from the side (or
+ // picking one up).
+ private static final int PARTIAL_TILT = 45;
+
+ // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
+ // in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust
+ // the sensor data. However, under constantly vibrating conditions (think car mount), we
+ // still want to pick up changes, so rather than ignore the data, we filter it with a very
+ // high time constant.
+ private static final int MAX_DEVIATION_FROM_GRAVITY = 1;
+
+ // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no
+ // way to get this information from SensorManager.
+ // Note the actual period is generally 3-30ms larger than this depending on the device, but
+ // that's not enough to significantly skew our results.
+ private static final int SAMPLING_PERIOD_MS = 200;
+
+ // The following time constants are all used in low-pass filtering the accelerometer output.
+ // See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
+ // background.
+
+ // When device is near-vertical (screen approximately facing the horizon)
+ private static final int DEFAULT_TIME_CONSTANT_MS = 200;
+ // When device is partially tilted towards the sky or ground
+ private static final int TILTED_TIME_CONSTANT_MS = 600;
+ // When device is under external acceleration, i.e. not just gravity. We heavily distrust
+ // such readings.
+ private static final int ACCELERATING_TIME_CONSTANT_MS = 5000;
+
+ private static final float DEFAULT_LOWPASS_ALPHA =
+ (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ private static final float TILTED_LOWPASS_ALPHA =
+ (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ private static final float ACCELERATING_LOWPASS_ALPHA =
+ (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+
+ // The low-pass filtered accelerometer data
+ private float[] mFilteredVector = new float[] {0, 0, 0};
+
+ int getCurrentRotation() {
+ return SURFACE_ROTATIONS[mRotation];
+ }
+
+ private void calculateNewRotation(int orientation, int tiltAngle) {
+ if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
+ int thresholdRanges[][] = THRESHOLDS[mRotation];
+ int row = -1;
+ for (int i = 0; i < thresholdRanges.length; i++) {
+ if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
+ row = i;
+ break;
}
}
+ if (row == -1) return; // no matching transition
+
+ int rotation = ROTATE_TO[mRotation][row];
+ if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
+ // tilted too far flat to go to this rotation
+ return;
+ }
+
+ if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
+ mRotation = rotation;
+ onOrientationChanged(SURFACE_ROTATIONS[rotation]);
+ }
+
+ private float lowpassFilter(float newValue, float oldValue, float alpha) {
+ return alpha * newValue + (1 - alpha) * oldValue;
+ }
+
+ private float vectorMagnitude(float x, float y, float z) {
+ return (float) Math.sqrt(x*x + y*y + z*z);
+ }
+
+ /**
+ * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90].
+ * 90 degrees = screen facing the sky or ground.
+ */
+ private float tiltAngle(float z, float magnitude) {
+ return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ }
+
+ public void onSensorChanged(SensorEvent event) {
+ // the vector given in the SensorEvent points straight up (towards the sky) under ideal
+ // conditions (the phone is not accelerating). i'll call this upVector elsewhere.
+ float x = event.values[_DATA_X];
+ float y = event.values[_DATA_Y];
+ float z = event.values[_DATA_Z];
+ float magnitude = vectorMagnitude(x, y, z);
+ float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
+ float tiltAngle = tiltAngle(z, magnitude);
+
+ float alpha = DEFAULT_LOWPASS_ALPHA;
+ if (tiltAngle > MAX_TILT) {
+ return;
+ } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+ alpha = ACCELERATING_LOWPASS_ALPHA;
+ } else if (tiltAngle > PARTIAL_TILT) {
+ alpha = TILTED_LOWPASS_ALPHA;
+ }
+
+ x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha);
+ y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha);
+ z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha);
+ magnitude = vectorMagnitude(x, y, z);
+ tiltAngle = tiltAngle(z, magnitude);
+
+ // Angle between the x-y projection of upVector and the +y-axis, increasing
+ // counter-clockwise.
+ // 0 degrees = speaker end towards the sky
+ // 90 degrees = left edge of device towards the sky
+ float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES;
+ int orientation = Math.round(orientationAngle);
+ // atan2 returns (-180, 180]; normalize to [0, 360)
+ if (orientation < 0) {
+ orientation += 360;
+ }
+ calculateNewRotation(orientation, Math.round(tiltAngle));
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
@@ -214,13 +294,12 @@ public abstract class WindowOrientationListener {
public boolean canDetectOrientation() {
return mSensor != null;
}
-
+
/**
* Called when the rotation view of the device has changed.
- * Can be either Surface.ROTATION_90 or Surface.ROTATION_0.
- * @param rotation The new orientation of the device.
*
- * @see #ORIENTATION_UNKNOWN
+ * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
+ * @see Surface
*/
abstract public void onOrientationChanged(int rotation);
}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 11727b1..349b7e5 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -265,7 +265,6 @@ public abstract class Animation implements Cloneable {
* @see #reset()
* @see #start()
* @see #startNow()
- * @hide
*/
public void cancel() {
if (mStarted && !mEnded) {
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 176169e..2709cff 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -28,11 +28,11 @@ import android.util.AttributeSet;
public class DecelerateInterpolator implements Interpolator {
public DecelerateInterpolator() {
}
-
+
/**
* Constructor
*
- * @param factor Degree to which the animation should be eased. Seting factor to 1.0f produces
+ * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
* an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
* ease-out effect (i.e., it starts even faster and ends evens slower)
*/
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 7393737..6ac1633 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -328,6 +328,11 @@ public class BaseInputConnection implements InputConnection {
b = tmp;
}
+ // Guard against the case where the cursor has not been positioned yet.
+ if (b < 0) {
+ b = 0;
+ }
+
if (b + length > content.length()) {
length = content.length() - b;
}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c718bac..b98dcd2 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.view.inputmethod;
import android.os.Bundle;
@@ -111,7 +127,15 @@ public class EditorInfo implements InputType, Parcelable {
* flag for you on multi-line text views.
*/
public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000;
-
+
+ /**
+ * Flag of {@link #imeOptions}: used to request that the IME never go
+ * into fullscreen mode. Applications need to be aware that the flag is not
+ * a guarantee, and not all IMEs will respect it.
+ * @hide
+ */
+ public static final int IME_FLAG_NO_FULLSCREEN = 0x80000000;
+
/**
* Generic unspecified type for {@link #imeOptions}.
*/
diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java
index c2851d6..662ba3f 100644
--- a/core/java/android/view/inputmethod/ExtractedText.java
+++ b/core/java/android/view/inputmethod/ExtractedText.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.view.inputmethod;
import android.os.Parcel;
diff --git a/core/java/android/view/inputmethod/ExtractedTextRequest.java b/core/java/android/view/inputmethod/ExtractedTextRequest.java
index e84b094..f658b87 100644
--- a/core/java/android/view/inputmethod/ExtractedTextRequest.java
+++ b/core/java/android/view/inputmethod/ExtractedTextRequest.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.view.inputmethod;
import android.os.Parcel;
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index a5e0e94..2ddf5f8 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -16,6 +16,8 @@
package android.view.inputmethod;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.ResultReceiver;
@@ -54,9 +56,12 @@ public interface InputMethod {
/**
* This is the interface name that a service implementing an input
* method should say that it supports -- that is, this is the action it
- * uses for its intent filter. (Note: this name is used because this
- * interface should be moved to the view package.)
+ * uses for its intent filter.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission so
+ * that other applications can not abuse it.
*/
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE = "android.view.InputMethod";
/**
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 316bcd6..357cb5f 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -25,6 +25,8 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -92,6 +94,8 @@ public final class InputMethodInfo implements Parcelable {
+ InputMethod.SERVICE_META_DATA + " meta-data");
}
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -105,13 +109,16 @@ public final class InputMethodInfo implements Parcelable {
"Meta-data does not start with input-method tag");
}
- TypedArray sa = context.getResources().obtainAttributes(attrs,
+ TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.InputMethod);
settingsActivityComponent = sa.getString(
com.android.internal.R.styleable.InputMethod_settingsActivity);
isDefaultResId = sa.getResourceId(
com.android.internal.R.styleable.InputMethod_isDefault, 0);
sa.recycle();
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException(
+ "Unable to create context for: " + si.packageName);
} finally {
if (parser != null) parser.close();
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 9456ae1..219a469 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -17,20 +17,31 @@
package android.webkit;
import android.app.ActivityManager;
+import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ParseException;
+import android.net.Uri;
import android.net.WebAddress;
import android.net.http.SslCertificate;
import android.os.Handler;
import android.os.Message;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.TypedValue;
+import android.view.Surface;
+import android.view.ViewRoot;
+import android.view.WindowManager;
import junit.framework.Assert;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
import java.net.URLEncoder;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
@@ -56,6 +67,10 @@ class BrowserFrame extends Handler {
private int mLoadType;
private boolean mFirstLayoutDone = true;
private boolean mCommitted = true;
+ // Flag for blocking messages. This is used during destroy() so
+ // that if the UI thread posts any messages after the message
+ // queue has been cleared,they are ignored.
+ private boolean mBlockMessages = false;
// Is this frame the main frame?
private boolean mIsMainFrame;
@@ -66,6 +81,8 @@ class BrowserFrame extends Handler {
// message ids
// a message posted when a frame loading is completed
static final int FRAME_COMPLETED = 1001;
+ // orientation change message
+ static final int ORIENTATION_CHANGED = 1002;
// a message posted when the user decides the policy
static final int POLICY_FUNCTION = 1003;
@@ -90,6 +107,70 @@ class BrowserFrame extends Handler {
// requests from WebCore.
static JWebCoreJavaBridge sJavaBridge;
+ private static class ConfigCallback implements ComponentCallbacks {
+ private final ArrayList<WeakReference<Handler>> mHandlers =
+ new ArrayList<WeakReference<Handler>>();
+ private final WindowManager mWindowManager;
+
+ ConfigCallback(WindowManager wm) {
+ mWindowManager = wm;
+ }
+
+ public synchronized void addHandler(Handler h) {
+ // No need to ever remove a Handler. If the BrowserFrame is
+ // destroyed, it will be collected and the WeakReference set to
+ // null. If it happens to still be around during a configuration
+ // change, the message will be ignored.
+ mHandlers.add(new WeakReference<Handler>(h));
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mHandlers.size() == 0) {
+ return;
+ }
+ int orientation =
+ mWindowManager.getDefaultDisplay().getOrientation();
+ switch (orientation) {
+ case Surface.ROTATION_90:
+ orientation = 90;
+ break;
+ case Surface.ROTATION_180:
+ orientation = 180;
+ break;
+ case Surface.ROTATION_270:
+ orientation = -90;
+ break;
+ case Surface.ROTATION_0:
+ orientation = 0;
+ break;
+ default:
+ break;
+ }
+ synchronized (this) {
+ // Create a list of handlers to remove. Go ahead and make it
+ // the same size to avoid resizing.
+ ArrayList<WeakReference> handlersToRemove =
+ new ArrayList<WeakReference>(mHandlers.size());
+ for (WeakReference<Handler> wh : mHandlers) {
+ Handler h = wh.get();
+ if (h != null) {
+ h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
+ orientation, 0));
+ } else {
+ handlersToRemove.add(wh);
+ }
+ }
+ // Now remove all the null references.
+ for (WeakReference weak : handlersToRemove) {
+ mHandlers.remove(weak);
+ }
+ }
+ }
+
+ public void onLowMemory() {}
+ }
+ static ConfigCallback sConfigCallback;
+
/**
* Create a new BrowserFrame to be used in an application.
* @param context An application context to use when retrieving assets.
@@ -101,10 +182,13 @@ class BrowserFrame extends Handler {
*/
public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
WebSettings settings, Map<String, Object> javascriptInterfaces) {
+
+ Context appContext = context.getApplicationContext();
+
// Create a global JWebCoreJavaBridge to handle timers and
// cookies in the WebCore thread.
if (sJavaBridge == null) {
- sJavaBridge = new JWebCoreJavaBridge(context);
+ sJavaBridge = new JWebCoreJavaBridge();
// set WebCore native cache size
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
@@ -114,18 +198,27 @@ class BrowserFrame extends Handler {
sJavaBridge.setCacheSize(4 * 1024 * 1024);
}
// initialize CacheManager
- CacheManager.init(context);
+ CacheManager.init(appContext);
// create CookieSyncManager with current Context
- CookieSyncManager.createInstance(context);
+ CookieSyncManager.createInstance(appContext);
// create PluginManager with current Context
- PluginManager.getInstance(context);
+ PluginManager.getInstance(appContext);
}
+
+ if (sConfigCallback == null) {
+ sConfigCallback = new ConfigCallback(
+ (WindowManager) context.getSystemService(
+ Context.WINDOW_SERVICE));
+ ViewRoot.addConfigCallback(sConfigCallback);
+ }
+ sConfigCallback.addHandler(this);
+
mJSInterfaceMap = javascriptInterfaces;
mSettings = settings;
mContext = context;
mCallbackProxy = proxy;
- mDatabase = WebViewDatabase.getInstance(context);
+ mDatabase = WebViewDatabase.getInstance(appContext);
mWebViewCore = w;
AssetManager am = context.getAssets();
@@ -138,18 +231,21 @@ class BrowserFrame extends Handler {
/**
* Load a url from the network or the filesystem into the main frame.
- * Following the same behaviour as Safari, javascript: URLs are not
- * passed to the main frame, instead they are evaluated immediately.
+ * Following the same behaviour as Safari, javascript: URLs are not passed
+ * to the main frame, instead they are evaluated immediately.
* @param url The url to load.
+ * @param extraHeaders The extra headers sent with this url. This should not
+ * include the common headers like "user-agent". If it does, it
+ * will be replaced by the intrinsic value of the WebView.
*/
- public void loadUrl(String url) {
+ public void loadUrl(String url, Map<String, String> extraHeaders) {
mLoadInitFromJava = true;
if (URLUtil.isJavaScriptUrl(url)) {
// strip off the scheme and evaluate the string
stringByEvaluatingJavaScriptFromString(
url.substring("javascript:".length()));
} else {
- nativeLoadUrl(url);
+ nativeLoadUrl(url, extraHeaders);
}
mLoadInitFromJava = false;
}
@@ -167,21 +263,19 @@ class BrowserFrame extends Handler {
/**
* Load the content as if it was loaded by the provided base URL. The
- * failUrl is used as the history entry for the load data. If null or
- * an empty string is passed for the failUrl, then no history entry is
- * created.
+ * historyUrl is used as the history entry for the load data.
*
* @param baseUrl Base URL used to resolve relative paths in the content
* @param data Content to render in the browser
* @param mimeType Mimetype of the data being passed in
* @param encoding Character set encoding of the provided data.
- * @param failUrl URL to use if the content fails to load or null.
+ * @param historyUrl URL to use as the history entry.
*/
public void loadData(String baseUrl, String data, String mimeType,
- String encoding, String failUrl) {
+ String encoding, String historyUrl) {
mLoadInitFromJava = true;
- if (failUrl == null) {
- failUrl = "";
+ if (historyUrl == null || historyUrl.length() == 0) {
+ historyUrl = "about:blank";
}
if (data == null) {
data = "";
@@ -195,7 +289,7 @@ class BrowserFrame extends Handler {
if (mimeType == null || mimeType.length() == 0) {
mimeType = "text/html";
}
- nativeLoadData(baseUrl, data, mimeType, encoding, failUrl);
+ nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
mLoadInitFromJava = false;
}
@@ -300,6 +394,7 @@ class BrowserFrame extends Handler {
// loadType is not used yet
if (isMainFrame) {
mCommitted = true;
+ mWebViewCore.getWebView().mViewManager.postResetStateAll();
}
}
@@ -339,6 +434,7 @@ class BrowserFrame extends Handler {
*/
public void destroy() {
nativeDestroyFrame();
+ mBlockMessages = true;
removeCallbacksAndMessages(null);
}
@@ -348,6 +444,9 @@ class BrowserFrame extends Handler {
*/
@Override
public void handleMessage(Message msg) {
+ if (mBlockMessages) {
+ return;
+ }
switch (msg.what) {
case FRAME_COMPLETED: {
if (mSettings.getSavePassword() && hasPasswordField()) {
@@ -363,7 +462,8 @@ class BrowserFrame extends Handler {
}
}
}
- CacheManager.trimCacheIfNeeded();
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_TRIM_CACHE);
break;
}
@@ -372,6 +472,11 @@ class BrowserFrame extends Handler {
break;
}
+ case ORIENTATION_CHANGED: {
+ nativeOrientationChanged(msg.arg1);
+ break;
+ }
+
default:
break;
}
@@ -463,6 +568,55 @@ class BrowserFrame extends Handler {
}
/**
+ * Called by JNI. Given a URI, find the associated file and return its size
+ * @param uri A String representing the URI of the desired file.
+ * @return int The size of the given file.
+ */
+ private int getFileSize(String uri) {
+ int size = 0;
+ try {
+ InputStream stream = mContext.getContentResolver()
+ .openInputStream(Uri.parse(uri));
+ size = stream.available();
+ stream.close();
+ } catch (Exception e) {}
+ return size;
+ }
+
+ /**
+ * Called by JNI. Given a URI, a buffer, and an offset into the buffer,
+ * copy the resource into buffer.
+ * @param uri A String representing the URI of the desired file.
+ * @param buffer The byte array to copy the data into.
+ * @param offset The offet into buffer to place the data.
+ * @param expectedSize The size that the buffer has allocated for this file.
+ * @return int The size of the given file, or zero if it fails.
+ */
+ private int getFile(String uri, byte[] buffer, int offset,
+ int expectedSize) {
+ int size = 0;
+ try {
+ InputStream stream = mContext.getContentResolver()
+ .openInputStream(Uri.parse(uri));
+ size = stream.available();
+ if (size <= expectedSize && buffer != null
+ && buffer.length - offset >= size) {
+ stream.read(buffer, offset, size);
+ } else {
+ size = 0;
+ }
+ stream.close();
+ } catch (java.io.FileNotFoundException e) {
+ Log.e(LOGTAG, "FileNotFoundException:" + e);
+ size = 0;
+ } catch (java.io.IOException e2) {
+ Log.e(LOGTAG, "IOException: " + e2);
+ size = 0;
+ }
+ return size;
+ }
+
+ /**
* Start loading a resource.
* @param loaderHandle The native ResourceLoader that is the target of the
* data.
@@ -471,7 +625,10 @@ class BrowserFrame extends Handler {
* @param headers The http headers.
* @param postData If the method is "POST" postData is sent as the request
* body. Is null when empty.
- * @param cacheMode The cache mode to use when loading this resource.
+ * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
+ * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
+ * @param mainResource True if the this resource is the main request, not a supporting resource
+ * @param userGesture
* @param synchronous True if the load is synchronous.
* @return A newly created LoadListener object.
*/
@@ -480,8 +637,13 @@ class BrowserFrame extends Handler {
String method,
HashMap headers,
byte[] postData,
+ long postDataIdentifier,
int cacheMode,
- boolean synchronous) {
+ boolean mainResource,
+ boolean userGesture,
+ boolean synchronous,
+ String username,
+ String password) {
PerfChecker checker = new PerfChecker();
if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
@@ -547,12 +709,14 @@ class BrowserFrame extends Handler {
if (DebugFlags.BROWSER_FRAME) {
Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
+ method + ", postData=" + postData + ", isMainFramePage="
- + isMainFramePage);
+ + isMainFramePage + ", mainResource=" + mainResource
+ + ", userGesture=" + userGesture);
}
// Create a LoadListener
- LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url,
- loaderHandle, synchronous, isMainFramePage);
+ LoadListener loadListener = LoadListener.getLoadListener(mContext,
+ this, url, loaderHandle, synchronous, isMainFramePage,
+ mainResource, userGesture, postDataIdentifier, username, password);
mCallbackProxy.onLoadResource(url);
@@ -675,10 +839,13 @@ class BrowserFrame extends Handler {
return mSettings.getUserAgentString();
}
- // these ids need to be in sync with enum RAW_RES_ID in WebFrame
+ // 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;
+ private static final int FILE_UPLOAD_LABEL = 4;
+ private static final int RESET_LABEL = 5;
+ private static final int SUBMIT_LABEL = 6;
String getRawResFilename(int id) {
int resid;
@@ -696,6 +863,18 @@ class BrowserFrame extends Handler {
resid = com.android.internal.R.drawable.btn_check_off;
break;
+ case FILE_UPLOAD_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.upload_file);
+
+ case RESET_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.reset);
+
+ case SUBMIT_LABEL:
+ return mContext.getResources().getString(
+ com.android.internal.R.string.submit);
+
default:
Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
return "";
@@ -781,12 +960,12 @@ class BrowserFrame extends Handler {
/**
* Returns false if the url is bad.
*/
- private native void nativeLoadUrl(String url);
+ private native void nativeLoadUrl(String url, Map<String, String> headers);
private native void nativePostUrl(String url, byte[] postData);
private native void nativeLoadData(String baseUrl, String data,
- String mimeType, String encoding, String failUrl);
+ String mimeType, String encoding, String historyUrl);
/**
* Stop loading the current page.
@@ -830,4 +1009,6 @@ class BrowserFrame extends Handler {
* returns null.
*/
private native HashMap getFormTextData();
+
+ private native void nativeOrientationChanged(int orientation);
}
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 145411c..334526b 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -16,6 +16,8 @@
package android.webkit;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
import java.util.LinkedList;
import java.util.ListIterator;
@@ -23,47 +25,37 @@ import java.util.ListIterator;
them back out. It does not optimize for returning the result in a
single array, though this is supported in the API. It is fastest
if the retrieval can be done via iterating through chunks.
-
- Things to add:
- - consider dynamically increasing our min_capacity,
- as we see mTotalSize increase
*/
class ByteArrayBuilder {
private static final int DEFAULT_CAPACITY = 8192;
- private LinkedList<Chunk> mChunks;
-
- /** free pool */
- private LinkedList<Chunk> mPool;
+ // Global pool of chunks to be used by other ByteArrayBuilders.
+ private static final LinkedList<SoftReference<Chunk>> sPool =
+ new LinkedList<SoftReference<Chunk>>();
+ // Reference queue for processing gc'd entries.
+ private static final ReferenceQueue<Chunk> sQueue =
+ new ReferenceQueue<Chunk>();
- private int mMinCapacity;
+ private LinkedList<Chunk> mChunks;
public ByteArrayBuilder() {
- init(0);
- }
-
- public ByteArrayBuilder(int minCapacity) {
- init(minCapacity);
- }
-
- private void init(int minCapacity) {
mChunks = new LinkedList<Chunk>();
- mPool = new LinkedList<Chunk>();
-
- if (minCapacity <= 0) {
- minCapacity = DEFAULT_CAPACITY;
- }
- mMinCapacity = minCapacity;
- }
-
- public void append(byte[] array) {
- append(array, 0, array.length);
}
public synchronized void append(byte[] array, int offset, int length) {
while (length > 0) {
- Chunk c = appendChunk(length);
+ Chunk c = null;
+ if (mChunks.isEmpty()) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ } else {
+ c = mChunks.getLast();
+ if (c.mLength == c.mArray.length) {
+ c = obtainChunk(length);
+ mChunks.addLast(c);
+ }
+ }
int amount = Math.min(length, c.mArray.length - c.mLength);
System.arraycopy(array, offset, c.mArray, c.mLength, amount);
c.mLength += amount;
@@ -75,7 +67,7 @@ class ByteArrayBuilder {
/**
* The fastest way to retrieve the data is to iterate through the
* chunks. This returns the first chunk. Note: this pulls the
- * chunk out of the queue. The caller must call releaseChunk() to
+ * chunk out of the queue. The caller must call Chunk.release() to
* dispose of it.
*/
public synchronized Chunk getFirstChunk() {
@@ -83,23 +75,11 @@ class ByteArrayBuilder {
return mChunks.removeFirst();
}
- /**
- * recycles chunk
- */
- public synchronized void releaseChunk(Chunk c) {
- c.mLength = 0;
- mPool.addLast(c);
- }
-
- public boolean isEmpty() {
+ public synchronized boolean isEmpty() {
return mChunks.isEmpty();
}
- public int size() {
- return mChunks.size();
- }
-
- public int getByteSize() {
+ public synchronized int getByteSize() {
int total = 0;
ListIterator<Chunk> it = mChunks.listIterator(0);
while (it.hasNext()) {
@@ -112,37 +92,40 @@ class ByteArrayBuilder {
public synchronized void clear() {
Chunk c = getFirstChunk();
while (c != null) {
- releaseChunk(c);
+ c.release();
c = getFirstChunk();
}
}
- private Chunk appendChunk(int length) {
- if (length < mMinCapacity) {
- length = mMinCapacity;
- }
-
- Chunk c;
- if (mChunks.isEmpty()) {
- c = obtainChunk(length);
- } else {
- c = mChunks.getLast();
- if (c.mLength == c.mArray.length) {
- c = obtainChunk(length);
+ // Must be called with lock held on sPool.
+ private void processPoolLocked() {
+ while (true) {
+ SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
+ if (entry == null) {
+ break;
}
+ sPool.remove(entry);
}
- return c;
}
private Chunk obtainChunk(int length) {
- Chunk c;
- if (mPool.isEmpty()) {
- c = new Chunk(length);
- } else {
- c = mPool.removeFirst();
+ // Correct a small length.
+ if (length < DEFAULT_CAPACITY) {
+ length = DEFAULT_CAPACITY;
+ }
+ synchronized (sPool) {
+ // Process any queued references and remove them from the pool.
+ processPoolLocked();
+ if (!sPool.isEmpty()) {
+ Chunk c = sPool.removeFirst().get();
+ // The first item may have been queued after processPoolLocked
+ // so check for null.
+ if (c != null) {
+ return c;
+ }
+ }
+ return new Chunk(length);
}
- mChunks.addLast(c);
- return c;
}
public static class Chunk {
@@ -153,5 +136,19 @@ class ByteArrayBuilder {
mArray = new byte[length];
mLength = 0;
}
+
+ /**
+ * Release the chunk and make it available for reuse.
+ */
+ public void release() {
+ mLength = 0;
+ synchronized (sPool) {
+ // Add the chunk back to the pool as a SoftReference so it can
+ // be gc'd if needed.
+ sPool.offer(new SoftReference<Chunk>(this, sQueue));
+ sPool.notifyAll();
+ }
+ }
+
}
}
diff --git a/core/java/android/webkit/CacheLoader.java b/core/java/android/webkit/CacheLoader.java
index de8f888..05c02b0 100644
--- a/core/java/android/webkit/CacheLoader.java
+++ b/core/java/android/webkit/CacheLoader.java
@@ -43,7 +43,7 @@ class CacheLoader extends StreamLoader {
protected boolean setupStreamAndSendStatus() {
mDataStream = mCacheResult.inStream;
mContentLength = mCacheResult.contentLength;
- mHandler.status(1, 1, mCacheResult.httpStatusCode, "OK");
+ mLoadListener.status(1, 1, mCacheResult.httpStatusCode, "OK");
return true;
}
@@ -67,5 +67,9 @@ class CacheLoader extends StreamLoader {
if (!TextUtils.isEmpty(mCacheResult.contentdisposition)) {
headers.setContentDisposition(mCacheResult.contentdisposition);
}
+
+ if (!TextUtils.isEmpty(mCacheResult.crossDomain)) {
+ headers.setXPermittedCrossDomainPolicies(mCacheResult.crossDomain);
+ }
}
}
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index c74092e..d5058b0 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.content.Context;
+import android.net.http.AndroidHttpClient;
import android.net.http.Headers;
import android.os.FileUtils;
import android.util.Log;
@@ -24,12 +25,14 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
@@ -52,10 +55,14 @@ public final class CacheManager {
private static final String NO_STORE = "no-store";
private static final String NO_CACHE = "no-cache";
private static final String MAX_AGE = "max-age";
+ private static final String MANIFEST_MIME = "text/cache-manifest";
private static long CACHE_THRESHOLD = 6 * 1024 * 1024;
private static long CACHE_TRIM_AMOUNT = 2 * 1024 * 1024;
+ // Limit the maximum cache file size to half of the normal capacity
+ static long CACHE_MAX_SIZE = (CACHE_THRESHOLD - CACHE_TRIM_AMOUNT) / 2;
+
private static boolean mDisabled;
// Reference count the enable/disable transaction
@@ -92,6 +99,7 @@ public final class CacheManager {
String location;
String encoding;
String contentdisposition;
+ String crossDomain;
// these fields are NOT saved to the database
InputStream inStream;
@@ -167,7 +175,7 @@ public final class CacheManager {
* @param context The application context.
*/
static void init(Context context) {
- mDataBase = WebViewDatabase.getInstance(context);
+ mDataBase = WebViewDatabase.getInstance(context.getApplicationContext());
mBaseDir = new File(context.getCacheDir(), "webviewCache");
if (createCacheDirectory() && mClearCacheOnInit) {
removeAllCacheFiles();
@@ -194,9 +202,9 @@ public final class CacheManager {
// the cache database. The directory could be recreated
// because the system flushed all the data/cache directories
// to free up disk space.
- WebViewCore.endCacheTransaction();
- mDataBase.clearCache();
- WebViewCore.startCacheTransaction();
+ // delete rows in the cache database
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_CLEAR_CACHE);
return true;
}
return false;
@@ -217,7 +225,6 @@ public final class CacheManager {
*
* @param disabled true to disable the cache
*/
- // only called from WebCore thread
static void setCacheDisabled(boolean disabled) {
if (disabled == mDisabled) {
return;
@@ -237,7 +244,7 @@ public final class CacheManager {
return mDisabled;
}
- // only called from WebCore thread
+ // only called from WebViewWorkerThread
// make sure to call enableTransaction/disableTransaction in pair
static boolean enableTransaction() {
if (++mRefCount == 1) {
@@ -247,12 +254,9 @@ public final class CacheManager {
return false;
}
- // only called from WebCore thread
+ // only called from WebViewWorkerThread
// make sure to call enableTransaction/disableTransaction in pair
static boolean disableTransaction() {
- if (mRefCount == 0) {
- Log.e(LOGTAG, "disableTransaction is out of sync");
- }
if (--mRefCount == 0) {
mDataBase.endCacheTransaction();
return true;
@@ -260,15 +264,15 @@ public final class CacheManager {
return false;
}
- // only called from WebCore thread
- // make sure to call startCacheTransaction/endCacheTransaction in pair
- public static boolean startCacheTransaction() {
+ // only called from WebViewWorkerThread
+ // make sure to call startTransaction/endTransaction in pair
+ static boolean startTransaction() {
return mDataBase.startCacheTransaction();
}
- // only called from WebCore thread
- // make sure to call startCacheTransaction/endCacheTransaction in pair
- public static boolean endCacheTransaction() {
+ // only called from WebViewWorkerThread
+ // make sure to call startTransaction/endTransaction in pair
+ static boolean endTransaction() {
boolean ret = mDataBase.endCacheTransaction();
if (++mTrimCacheCount >= TRIM_CACHE_INTERVAL) {
mTrimCacheCount = 0;
@@ -277,6 +281,26 @@ public final class CacheManager {
return ret;
}
+ // only called from WebCore Thread
+ // make sure to call startCacheTransaction/endCacheTransaction in pair
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public static boolean startCacheTransaction() {
+ return false;
+ }
+
+ // only called from WebCore Thread
+ // make sure to call startCacheTransaction/endCacheTransaction in pair
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public static boolean endCacheTransaction() {
+ return false;
+ }
+
/**
* Given a url, returns the CacheResult if exists. Otherwise returns null.
* If headers are provided and a cache needs validation,
@@ -285,19 +309,25 @@ public final class CacheManager {
*
* @return the CacheResult for a given url
*/
- // only called from WebCore thread
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
+ return getCacheFile(url, 0, headers);
+ }
+
+ static CacheResult getCacheFile(String url, long postIdentifier,
+ Map<String, String> headers) {
if (mDisabled) {
return null;
}
- CacheResult result = mDataBase.getCache(url);
+ String databaseKey = getDatabaseKey(url, postIdentifier);
+
+ CacheResult result = mDataBase.getCache(databaseKey);
if (result != null) {
if (result.contentLength == 0) {
if (!checkCacheRedirect(result.httpStatusCode)) {
// this should not happen. If it does, remove it.
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
} else {
@@ -309,7 +339,7 @@ public final class CacheManager {
} catch (FileNotFoundException e) {
// the files in the cache directory can be removed by the
// system. If it is gone, clean up the database
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
}
@@ -354,17 +384,25 @@ public final class CacheManager {
* @hide - hide createCacheFile since it has a parameter of type headers, which is
* in a hidden package.
*/
- // only called from WebCore thread
public static CacheResult createCacheFile(String url, int statusCode,
Headers headers, String mimeType, boolean forceCache) {
+ return createCacheFile(url, statusCode, headers, mimeType, 0,
+ forceCache);
+ }
+
+ static CacheResult createCacheFile(String url, int statusCode,
+ Headers headers, String mimeType, long postIdentifier,
+ boolean forceCache) {
if (!forceCache && mDisabled) {
return null;
}
+ String databaseKey = getDatabaseKey(url, postIdentifier);
+
// according to the rfc 2616, the 303 response MUST NOT be cached.
if (statusCode == 303) {
// remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
@@ -372,7 +410,7 @@ public final class CacheManager {
// header.
if (checkCacheRedirect(statusCode) && !headers.getSetCookie().isEmpty()) {
// remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
return null;
}
@@ -380,9 +418,9 @@ public final class CacheManager {
if (ret == null) {
// this should only happen if the headers has "no-store" in the
// cache-control. remove the saved cache if there is any
- mDataBase.removeCache(url);
+ mDataBase.removeCache(databaseKey);
} else {
- setupFiles(url, ret);
+ setupFiles(databaseKey, ret);
try {
ret.outStream = new FileOutputStream(ret.outFile);
} catch (FileNotFoundException e) {
@@ -411,8 +449,12 @@ public final class CacheManager {
* Save the info of a cache file for a given url to the CacheMap so that it
* can be reused later
*/
- // only called from WebCore thread
public static void saveCacheFile(String url, CacheResult cacheRet) {
+ saveCacheFile(url, 0, cacheRet);
+ }
+
+ static void saveCacheFile(String url, long postIdentifier,
+ CacheResult cacheRet) {
try {
cacheRet.outStream.close();
} catch (IOException e) {
@@ -424,7 +466,6 @@ public final class CacheManager {
return;
}
- cacheRet.contentLength = cacheRet.outFile.length();
boolean redirect = checkCacheRedirect(cacheRet.httpStatusCode);
if (redirect) {
// location is in database, no need to keep the file
@@ -439,19 +480,27 @@ public final class CacheManager {
return;
}
- mDataBase.addCache(url, cacheRet);
+ mDataBase.addCache(getDatabaseKey(url, postIdentifier), cacheRet);
if (DebugFlags.CACHE_MANAGER) {
Log.v(LOGTAG, "saveCacheFile for url " + url);
}
}
+ static boolean cleanupCacheFile(CacheResult cacheRet) {
+ try {
+ cacheRet.outStream.close();
+ } catch (IOException e) {
+ return false;
+ }
+ return cacheRet.outFile.delete();
+ }
+
/**
* remove all cache files
*
* @return true if it succeeds
*/
- // only called from WebCore thread
static boolean removeAllCacheFiles() {
// Note, this is called before init() when the database is
// created or upgraded.
@@ -461,7 +510,10 @@ public final class CacheManager {
mClearCacheOnInit = true;
return true;
}
- // delete cache in a separate thread to not block UI.
+ // delete rows in the cache database
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_CLEAR_CACHE);
+ // delete cache files in a separate thread to not block UI.
final Runnable clearCache = new Runnable() {
public void run() {
// delete all cache files
@@ -479,8 +531,6 @@ public final class CacheManager {
} catch (SecurityException e) {
// Ignore SecurityExceptions.
}
- // delete database
- mDataBase.clearCache();
}
};
new Thread(clearCache).start();
@@ -490,15 +540,13 @@ public final class CacheManager {
/**
* Return true if the cache is empty.
*/
- // only called from WebCore thread
static boolean cacheEmpty() {
return mDataBase.hasCache();
}
- // only called from WebCore thread
static void trimCacheIfNeeded() {
if (mDataBase.getCacheTotalSize() > CACHE_THRESHOLD) {
- ArrayList<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
+ List<String> pathList = mDataBase.trimCache(CACHE_TRIM_AMOUNT);
int size = pathList.size();
for (int i = 0; i < size; i++) {
File f = new File(mBaseDir, pathList.get(i));
@@ -506,9 +554,34 @@ public final class CacheManager {
Log.e(LOGTAG, f.getPath() + " delete failed.");
}
}
+ // remove the unreferenced files in the cache directory
+ final List<String> fileList = mDataBase.getAllCacheFileNames();
+ if (fileList == null) return;
+ String[] toDelete = mBaseDir.list(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ if (fileList.contains(filename)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ });
+ if (toDelete == null) return;
+ size = toDelete.length;
+ for (int i = 0; i < size; i++) {
+ File f = new File(mBaseDir, toDelete[i]);
+ if (!f.delete()) {
+ Log.e(LOGTAG, f.getPath() + " delete failed.");
+ }
+ }
}
}
+ static void clearCache() {
+ // delete database
+ mDataBase.clearCache();
+ }
+
private static boolean checkCacheRedirect(int statusCode) {
if (statusCode == 301 || statusCode == 302 || statusCode == 307) {
// as 303 can't be cached, we do not return true
@@ -518,6 +591,11 @@ public final class CacheManager {
}
}
+ private static String getDatabaseKey(String url, long postIdentifier) {
+ if (postIdentifier == 0) return url;
+ return postIdentifier + url;
+ }
+
@SuppressWarnings("deprecation")
private static void setupFiles(String url, CacheResult cacheRet) {
if (true) {
@@ -615,6 +693,18 @@ public final class CacheManager {
private static CacheResult parseHeaders(int statusCode, Headers headers,
String mimeType) {
+ // if the contentLength is already larger than CACHE_MAX_SIZE, skip it
+ if (headers.getContentLength() > CACHE_MAX_SIZE) return null;
+
+ // The HTML 5 spec, section 6.9.4, step 7.3 of the application cache
+ // process states that HTTP caching rules are ignored for the
+ // purposes of the application cache download process.
+ // At this point we can't tell that if a file is part of this process,
+ // except for the manifest, which has its own mimeType.
+ // TODO: work out a way to distinguish all responses that are part of
+ // the application download process and skip them.
+ if (MANIFEST_MIME.equals(mimeType)) return null;
+
// TODO: if authenticated or secure, return null
CacheResult ret = new CacheResult();
ret.httpStatusCode = statusCode;
@@ -626,7 +716,7 @@ public final class CacheManager {
ret.expiresString = headers.getExpires();
if (ret.expiresString != null) {
try {
- ret.expires = HttpDateTime.parse(ret.expiresString);
+ ret.expires = AndroidHttpClient.parseDate(ret.expiresString);
} catch (IllegalArgumentException ex) {
// Take care of the special "-1" and "0" cases
if ("-1".equals(ret.expiresString)
@@ -644,11 +734,20 @@ public final class CacheManager {
ret.contentdisposition = contentDisposition;
}
+ String crossDomain = headers.getXPermittedCrossDomainPolicies();
+ if (crossDomain != null) {
+ ret.crossDomain = crossDomain;
+ }
+
+ // lastModified and etag may be set back to http header. So they can't
+ // be empty string.
String lastModified = headers.getLastModified();
- if (lastModified != null) ret.lastModified = lastModified;
+ if (lastModified != null && lastModified.length() > 0) {
+ ret.lastModified = lastModified;
+ }
String etag = headers.getEtag();
- if (etag != null) ret.etag = etag;
+ if (etag != null && etag.length() > 0) ret.etag = etag;
String cacheControl = headers.getCacheControl();
if (cacheControl != null) {
@@ -732,7 +831,7 @@ public final class CacheManager {
// 24 * 60 * 60 * 1000
long lastmod = System.currentTimeMillis() + 86400000;
try {
- lastmod = HttpDateTime.parse(ret.lastModified);
+ lastmod = AndroidHttpClient.parseDate(ret.lastModified);
} catch (IllegalArgumentException ex) {
Log.e(LOGTAG, "illegal lastModified: " + ret.lastModified);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index e9afcb6..8af2492 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -69,6 +69,8 @@ class CallbackProxy extends Handler {
private volatile int mLatestProgress = 100;
// Back/Forward list
private final WebBackForwardList mBackForwardList;
+ // Back/Forward list client
+ private volatile WebBackForwardListClient mWebBackForwardListClient;
// Used to call startActivity during url override.
private final Context mContext;
@@ -90,7 +92,6 @@ class CallbackProxy extends Handler {
private static final int JS_PROMPT = 114;
private static final int JS_UNLOAD = 115;
private static final int ASYNC_KEYEVENTS = 116;
- private static final int TOO_MANY_REDIRECTS = 117;
private static final int DOWNLOAD_FILE = 118;
private static final int REPORT_ERROR = 119;
private static final int RESEND_POST_DATA = 120;
@@ -107,6 +108,10 @@ class CallbackProxy extends Handler {
private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
private static final int RECEIVED_TOUCH_ICON_URL = 132;
private static final int GET_VISITED_HISTORY = 133;
+ private static final int OPEN_FILE_CHOOSER = 134;
+ private static final int ADD_HISTORY_ITEM = 135;
+ private static final int HISTORY_INDEX_CHANGED = 136;
+ private static final int AUTH_CREDENTIALS = 137;
// Message triggered by the client to resume execution
private static final int NOTIFY = 200;
@@ -137,7 +142,7 @@ class CallbackProxy extends Handler {
// Used to start a default activity.
mContext = context;
mWebView = w;
- mBackForwardList = new WebBackForwardList();
+ mBackForwardList = new WebBackForwardList(this);
}
/**
@@ -149,6 +154,16 @@ class CallbackProxy extends Handler {
}
/**
+ * Get the WebViewClient.
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mWebViewClient;
+ }
+
+ /**
* Set the WebChromeClient.
* @param client An implementation of WebChromeClient.
*/
@@ -180,6 +195,14 @@ class CallbackProxy extends Handler {
return mBackForwardList;
}
+ void setWebBackForwardListClient(WebBackForwardListClient client) {
+ mWebBackForwardListClient = client;
+ }
+
+ WebBackForwardListClient getWebBackForwardListClient() {
+ return mWebBackForwardListClient;
+ }
+
/**
* Called by the UI side. Calling overrideUrlLoading from the WebCore
* side will post a message to call this method.
@@ -230,6 +253,13 @@ class CallbackProxy extends Handler {
// 32-bit reads and writes.
switch (msg.what) {
case PAGE_STARTED:
+ // every time we start a new page, we want to reset the
+ // WebView certificate:
+ // if the new site is secure, we will reload it and get a
+ // new certificate set;
+ // if the new site is not secure, the certificate must be
+ // null, and that will be the case
+ mWebView.setCertificate(null);
if (mWebViewClient != null) {
mWebViewClient.onPageStarted(mWebView,
msg.getData().getString("url"),
@@ -238,8 +268,10 @@ class CallbackProxy extends Handler {
break;
case PAGE_FINISHED:
+ String finishedUrl = (String) msg.obj;
+ mWebView.onPageFinished(finishedUrl);
if (mWebViewClient != null) {
- mWebViewClient.onPageFinished(mWebView, (String) msg.obj);
+ mWebViewClient.onPageFinished(mWebView, finishedUrl);
}
break;
@@ -263,19 +295,6 @@ class CallbackProxy extends Handler {
}
break;
- case TOO_MANY_REDIRECTS:
- Message cancelMsg =
- (Message) msg.getData().getParcelable("cancelMsg");
- Message continueMsg =
- (Message) msg.getData().getParcelable("continueMsg");
- if (mWebViewClient != null) {
- mWebViewClient.onTooManyRedirects(mWebView, cancelMsg,
- continueMsg);
- } else {
- cancelMsg.sendToTarget();
- }
- break;
-
case REPORT_ERROR:
if (mWebViewClient != null) {
int reasonCode = msg.arg1;
@@ -379,6 +398,7 @@ class CallbackProxy extends Handler {
notify();
}
}
+ mWebView.dismissZoomControl();
}
break;
@@ -652,7 +672,42 @@ class CallbackProxy extends Handler {
String message = msg.getData().getString("message");
String sourceID = msg.getData().getString("sourceID");
int lineNumber = msg.getData().getInt("lineNumber");
- mWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
+ int msgLevel = msg.getData().getInt("msgLevel");
+ int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
+ // Sanity bounds check as we'll index an array with msgLevel
+ if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
+ msgLevel = 0;
+ }
+
+ ConsoleMessage.MessageLevel messageLevel =
+ ConsoleMessage.MessageLevel.values()[msgLevel];
+
+ if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
+ lineNumber, messageLevel))) {
+ // If false was returned the user did not provide their own console function so
+ // we should output some default messages to the system log.
+ String logTag = "Web Console";
+ String logMessage = message + " at " + sourceID + ":" + lineNumber;
+
+ switch (messageLevel) {
+ case TIP:
+ Log.v(logTag, logMessage);
+ break;
+ case LOG:
+ Log.i(logTag, logMessage);
+ break;
+ case WARNING:
+ Log.w(logTag, logMessage);
+ break;
+ case ERROR:
+ Log.e(logTag, logMessage);
+ break;
+ case DEBUG:
+ Log.d(logTag, logMessage);
+ break;
+ }
+ }
+
break;
case GET_VISITED_HISTORY:
@@ -660,6 +715,34 @@ class CallbackProxy extends Handler {
mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
}
break;
+
+ case OPEN_FILE_CHOOSER:
+ if (mWebChromeClient != null) {
+ mWebChromeClient.openFileChooser((UploadFile) msg.obj);
+ }
+ break;
+
+ case ADD_HISTORY_ITEM:
+ if (mWebBackForwardListClient != null) {
+ mWebBackForwardListClient.onNewHistoryItem(
+ (WebHistoryItem) msg.obj);
+ }
+ break;
+
+ case HISTORY_INDEX_CHANGED:
+ if (mWebBackForwardListClient != null) {
+ mWebBackForwardListClient.onIndexChanged(
+ (WebHistoryItem) msg.obj, msg.arg1);
+ }
+ break;
+ case AUTH_CREDENTIALS:
+ String host = msg.getData().getString("host");
+ String realm = msg.getData().getString("realm");
+ username = msg.getData().getString("username");
+ password = msg.getData().getString("password");
+ mWebView.setHttpAuthUsernamePassword(
+ host, realm, username, password);
+ break;
}
}
@@ -758,11 +841,6 @@ class CallbackProxy extends Handler {
}
public void onPageFinished(String url) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- return;
- }
// Performance probe
if (PERF_PROBE) {
// un-comment this if PERF_PROBE is true
@@ -776,19 +854,10 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
+ // Because this method is public and because CallbackProxy is mistakenly
+ // party of the public classes, we cannot remove this method.
public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
- // Do an unsynchronized quick check to avoid posting if no callback has
- // been set.
- if (mWebViewClient == null) {
- cancelMsg.sendToTarget();
- return;
- }
-
- Message msg = obtainMessage(TOO_MANY_REDIRECTS);
- Bundle bundle = msg.getData();
- bundle.putParcelable("cancelMsg", cancelMsg);
- bundle.putParcelable("continueMsg", continueMsg);
- sendMessage(msg);
+ // deprecated.
}
public void onReceivedError(int errorCode, String description,
@@ -857,6 +926,7 @@ class CallbackProxy extends Handler {
msg.getData().putString("realm", realmName);
sendMessage(msg);
}
+
/**
* @hide - hide this because it contains a parameter of type SslError.
* SslError is located in a hidden package.
@@ -993,6 +1063,16 @@ class CallbackProxy extends Handler {
return false;
}
+ public void onReceivedHttpAuthCredentials(String host, String realm,
+ String username, String password) {
+ Message msg = obtainMessage(AUTH_CREDENTIALS);
+ msg.getData().putString("host", host);
+ msg.getData().putString("realm", realm);
+ msg.getData().putString("username", username);
+ msg.getData().putString("password", password);
+ sendMessage(msg);
+ }
+
//--------------------------------------------------------------------------
// WebChromeClient methods
//--------------------------------------------------------------------------
@@ -1000,10 +1080,10 @@ class CallbackProxy extends Handler {
public void onProgressChanged(int newProgress) {
// Synchronize so that mLatestProgress is up-to-date.
synchronized (this) {
- mLatestProgress = newProgress;
- if (mWebChromeClient == null) {
+ if (mWebChromeClient == null || mLatestProgress == newProgress) {
return;
}
+ mLatestProgress = newProgress;
if (!mProgressUpdatePending) {
sendEmptyMessage(PROGRESS);
mProgressUpdatePending = true;
@@ -1079,7 +1159,7 @@ class CallbackProxy extends Handler {
// for null.
WebHistoryItem i = mBackForwardList.getCurrentItem();
if (i != null) {
- if (precomposed || i.getTouchIconUrl() != null) {
+ if (precomposed || i.getTouchIconUrl() == null) {
i.setTouchIconUrl(url);
}
}
@@ -1295,8 +1375,10 @@ class CallbackProxy extends Handler {
* occurred.
* @param sourceID The filename of the source file in which the error
* occurred.
+ * @param msgLevel The message level, corresponding to the MessageLevel enum in
+ * WebCore/page/Console.h
*/
- public void addMessageToConsole(String message, int lineNumber, String sourceID) {
+ public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
if (mWebChromeClient == null) {
return;
}
@@ -1305,6 +1387,7 @@ class CallbackProxy extends Handler {
msg.getData().putString("message", message);
msg.getData().putString("sourceID", sourceID);
msg.getData().putInt("lineNumber", lineNumber);
+ msg.getData().putInt("msgLevel", msgLevel);
sendMessage(msg);
}
@@ -1335,4 +1418,56 @@ class CallbackProxy extends Handler {
msg.obj = callback;
sendMessage(msg);
}
+
+ private class UploadFile implements ValueCallback<Uri> {
+ private Uri mValue;
+ public void onReceiveValue(Uri value) {
+ mValue = value;
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+ public Uri getResult() {
+ return mValue;
+ }
+ }
+
+ /**
+ * Called by WebViewCore to open a file chooser.
+ */
+ /* package */ Uri openFileChooser() {
+ if (mWebChromeClient == null) {
+ return null;
+ }
+ Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
+ UploadFile uploadFile = new UploadFile();
+ myMessage.obj = uploadFile;
+ synchronized (this) {
+ sendMessage(myMessage);
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(LOGTAG,
+ "Caught exception while waiting for openFileChooser");
+ Log.e(LOGTAG, Log.getStackTraceString(e));
+ }
+ }
+ return uploadFile.getResult();
+ }
+
+ void onNewHistoryItem(WebHistoryItem item) {
+ if (mWebBackForwardListClient == null) {
+ return;
+ }
+ Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
+ sendMessage(msg);
+ }
+
+ void onIndexChanged(WebHistoryItem item, int index) {
+ if (mWebBackForwardListClient == null) {
+ return;
+ }
+ Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
+ sendMessage(msg);
+ }
}
diff --git a/core/java/android/webkit/ConsoleMessage.java b/core/java/android/webkit/ConsoleMessage.java
new file mode 100644
index 0000000..a9c351a
--- /dev/null
+++ b/core/java/android/webkit/ConsoleMessage.java
@@ -0,0 +1,64 @@
+/*
+ * 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.webkit;
+
+/**
+ * Public class representing a JavaScript console message from WebCore. This could be a issued
+ * by a call to one of the <code>console</code> logging functions (e.g.
+ * <code>console.log('...')</code>) or a JavaScript error on the page. To receive notifications
+ * of these messages, override the
+ * {@link WebChromeClient#onConsoleMessage(ConsoleMessage)} function.
+ */
+public class ConsoleMessage {
+
+ // This must be kept in sync with the WebCore enum in WebCore/page/Console.h
+ public enum MessageLevel {
+ TIP,
+ LOG,
+ WARNING,
+ ERROR,
+ DEBUG
+ };
+
+ private MessageLevel mLevel;
+ private String mMessage;
+ private String mSourceId;
+ private int mLineNumber;
+
+ public ConsoleMessage(String message, String sourceId, int lineNumber, MessageLevel msgLevel) {
+ mMessage = message;
+ mSourceId = sourceId;
+ mLineNumber = lineNumber;
+ mLevel = msgLevel;
+ }
+
+ public MessageLevel messageLevel() {
+ return mLevel;
+ }
+
+ public String message() {
+ return mMessage;
+ }
+
+ public String sourceId() {
+ return mSourceId;
+ }
+
+ public int lineNumber() {
+ return mLineNumber;
+ }
+};
diff --git a/core/java/android/webkit/ContentLoader.java b/core/java/android/webkit/ContentLoader.java
index 19aa087..d13210a 100644
--- a/core/java/android/webkit/ContentLoader.java
+++ b/core/java/android/webkit/ContentLoader.java
@@ -16,14 +16,10 @@
package android.webkit;
-import android.content.Context;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.net.Uri;
-import java.io.File;
-import java.io.FileInputStream;
-
/**
* This class is a concrete implementation of StreamLoader that loads
* "content:" URIs
@@ -31,7 +27,6 @@ import java.io.FileInputStream;
class ContentLoader extends StreamLoader {
private String mUrl;
- private Context mContext;
private String mContentType;
/**
@@ -40,11 +35,9 @@ class ContentLoader extends StreamLoader {
* @param rawUrl "content:" url pointing to content to be loaded. This url
* is the same url passed in to the WebView.
* @param loadListener LoadListener to pass the content to
- * @param context Context to use to access the asset.
*/
- ContentLoader(String rawUrl, LoadListener loadListener, Context context) {
+ ContentLoader(String rawUrl, LoadListener loadListener) {
super(loadListener);
- mContext = context;
/* strip off mimetype */
int mimeIndex = rawUrl.lastIndexOf('?');
@@ -71,7 +64,7 @@ class ContentLoader extends StreamLoader {
protected boolean setupStreamAndSendStatus() {
Uri uri = Uri.parse(mUrl);
if (uri == null) {
- mHandler.error(
+ mLoadListener.error(
EventHandler.FILE_NOT_FOUND_ERROR,
mContext.getString(
com.android.internal.R.string.httpErrorBadUrl) +
@@ -81,18 +74,14 @@ class ContentLoader extends StreamLoader {
try {
mDataStream = mContext.getContentResolver().openInputStream(uri);
- mHandler.status(1, 1, 0, "OK");
+ mLoadListener.status(1, 1, 200, "OK");
} catch (java.io.FileNotFoundException ex) {
- mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
- return false;
-
- } catch (java.io.IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
return false;
} catch (RuntimeException ex) {
// readExceptionWithFileNotFoundExceptionFromParcel in DatabaseUtils
// can throw a serial of RuntimeException. Catch them all here.
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
return false;
}
return true;
@@ -106,18 +95,4 @@ class ContentLoader extends StreamLoader {
// content can change, we don't want WebKit to cache it
headers.setCacheControl("no-store, no-cache");
}
-
- /**
- * Construct a ContentLoader and instruct it to start loading.
- *
- * @param url "content:" url pointing to content to be loaded
- * @param loadListener LoadListener to pass the content to
- * @param context Context to use to access the asset.
- */
- public static void requestUrl(String url, LoadListener loadListener,
- Context context) {
- ContentLoader loader = new ContentLoader(url, loadListener, context);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index fca591f..ac20791 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -18,8 +18,10 @@ package android.webkit;
import android.net.ParseException;
import android.net.WebAddress;
+import android.net.http.AndroidHttpClient;
import android.util.Log;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -148,8 +150,13 @@ public final class CookieManager {
}
boolean exactMatch(Cookie in) {
+ // An exact match means that domain, path, and name are equal. If
+ // both values are null, the cookies match. If both values are
+ // non-null, the cookies match. If one value is null and the other
+ // is non-null, the cookies do not match (i.e. "foo=;" and "foo;")
+ boolean valuesMatch = !((value == null) ^ (in.value == null));
return domain.equals(in.domain) && path.equals(in.path) &&
- name.equals(in.name);
+ name.equals(in.name) && valuesMatch;
}
boolean domainMatch(String urlHost) {
@@ -204,17 +211,27 @@ public final class CookieManager {
// As Set is not modified if the two objects are same, we do want to
// assign different value for each cookie.
int diff = cookie2.path.length() - cookie1.path.length();
- if (diff == 0) {
- diff = cookie2.domain.length() - cookie1.domain.length();
- if (diff == 0) {
- diff = cookie2.name.hashCode() - cookie1.name.hashCode();
- if (diff == 0) {
- Log.w(LOGTAG, "Found two cookies with the same value." +
- "cookie1=" + cookie1 + " , cookie2=" + cookie2);
- }
+ if (diff != 0) return diff;
+
+ diff = cookie2.domain.length() - cookie1.domain.length();
+ if (diff != 0) return diff;
+
+ // If cookie2 has a null value, it should come later in
+ // the list.
+ if (cookie2.value == null) {
+ // If both cookies have null values, fall back to using the name
+ // difference.
+ if (cookie1.value != null) {
+ return -1;
}
+ } else if (cookie1.value == null) {
+ // Now we know that cookie2 does not have a null value, if
+ // cookie1 has a null value, place it later in the list.
+ return 1;
}
- return diff;
+
+ // Fallback to comparing the name to ensure consistent order.
+ return cookie1.name.compareTo(cookie2.name);
}
}
@@ -232,7 +249,7 @@ public final class CookieManager {
* first.
*
* @return CookieManager
-= */
+ */
public static synchronized CookieManager getInstance() {
if (sRef == null) {
sRef = new CookieManager();
@@ -457,8 +474,10 @@ public final class CookieManager {
}
ret.append(cookie.name);
- ret.append(EQUAL);
- ret.append(cookie.value);
+ if (cookie.value != null) {
+ ret.append(EQUAL);
+ ret.append(cookie.value);
+ }
}
if (ret.length() > 0) {
@@ -632,7 +651,10 @@ public final class CookieManager {
byteCount += cookie.domain.length()
+ cookie.path.length()
+ cookie.name.length()
- + cookie.value.length() + 14;
+ + (cookie.value != null
+ ? cookie.value.length()
+ : 0)
+ + 14;
count++;
}
} else {
@@ -671,8 +693,17 @@ public final class CookieManager {
*/
private String[] getHostAndPath(WebAddress uri) {
if (uri.mHost != null && uri.mPath != null) {
+
+ /*
+ * The domain (i.e. host) portion of the cookie is supposed to be
+ * case-insensitive. We will consistently return the domain in lower
+ * case, which allows us to do the more efficient equals comparison
+ * instead of equalIgnoreCase.
+ *
+ * See: http://www.ieft.org/rfc/rfc2965.txt (Section 3.3.3)
+ */
String[] ret = new String[2];
- ret[0] = uri.mHost;
+ ret[0] = uri.mHost.toLowerCase();
ret[1] = uri.mPath;
int index = ret[0].indexOf(PERIOD);
@@ -706,6 +737,7 @@ public final class CookieManager {
if (index != -1) {
ret[1] = ret[1].substring(0, index);
}
+
return ret;
} else
return null;
@@ -777,38 +809,52 @@ public final class CookieManager {
*/
int semicolonIndex = cookieString.indexOf(SEMICOLON, index);
int equalIndex = cookieString.indexOf(EQUAL, index);
- if (equalIndex == -1) {
- // bad format, force return
- break;
- }
- if (semicolonIndex > -1 && semicolonIndex < equalIndex) {
- // empty cookie, like "; path=/", return
- break;
- }
cookie = new Cookie(host, path);
- cookie.name = cookieString.substring(index, equalIndex);
- if (cookieString.charAt(equalIndex + 1) == QUOTATION) {
- index = cookieString.indexOf(QUOTATION, equalIndex + 2);
- if (index == -1) {
- // bad format, force return
- break;
+
+ // Cookies like "testcookie; path=/;" are valid and used
+ // (lovefilm.se).
+ // Look for 2 cases:
+ // 1. "foo" or "foo;" where equalIndex is -1
+ // 2. "foo; path=..." where the first semicolon is before an equal
+ // and a semicolon exists.
+ if ((semicolonIndex != -1 && (semicolonIndex < equalIndex)) ||
+ equalIndex == -1) {
+ // Fix up the index in case we have a string like "testcookie"
+ if (semicolonIndex == -1) {
+ semicolonIndex = length;
}
- }
- semicolonIndex = cookieString.indexOf(SEMICOLON, index);
- if (semicolonIndex == -1) {
- semicolonIndex = length;
- }
- if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
- // cookie is too big, trim it
- cookie.value = cookieString.substring(equalIndex + 1,
- equalIndex + MAX_COOKIE_LENGTH);
- } else if (equalIndex + 1 == semicolonIndex
- || semicolonIndex < equalIndex) {
- // these are unusual case like foo=; and foo; path=/
- cookie.value = "";
+ cookie.name = cookieString.substring(index, semicolonIndex);
+ cookie.value = null;
} else {
- cookie.value = cookieString.substring(equalIndex + 1,
- semicolonIndex);
+ cookie.name = cookieString.substring(index, equalIndex);
+ // Make sure we do not throw an exception if the cookie is like
+ // "foo="
+ if ((equalIndex < length - 1) &&
+ (cookieString.charAt(equalIndex + 1) == QUOTATION)) {
+ index = cookieString.indexOf(QUOTATION, equalIndex + 2);
+ if (index == -1) {
+ // bad format, force return
+ break;
+ }
+ }
+ // Get the semicolon index again in case it was contained within
+ // the quotations.
+ semicolonIndex = cookieString.indexOf(SEMICOLON, index);
+ if (semicolonIndex == -1) {
+ semicolonIndex = length;
+ }
+ if (semicolonIndex - equalIndex > MAX_COOKIE_LENGTH) {
+ // cookie is too big, trim it
+ cookie.value = cookieString.substring(equalIndex + 1,
+ equalIndex + 1 + MAX_COOKIE_LENGTH);
+ } else if (equalIndex + 1 == semicolonIndex
+ || semicolonIndex < equalIndex) {
+ // this is an unusual case like "foo=;" or "foo="
+ cookie.value = "";
+ } else {
+ cookie.value = cookieString.substring(equalIndex + 1,
+ semicolonIndex);
+ }
}
// get attributes
index = semicolonIndex;
@@ -893,7 +939,7 @@ public final class CookieManager {
}
if (name.equals(EXPIRES)) {
try {
- cookie.expires = HttpDateTime.parse(value);
+ cookie.expires = AndroidHttpClient.parseDate(value);
} catch (IllegalArgumentException ex) {
Log.e(LOGTAG,
"illegal format for expires: " + value);
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index 14375d2..abe9178 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -93,7 +93,7 @@ public final class CookieSyncManager extends WebSyncManager {
public static synchronized CookieSyncManager createInstance(
Context context) {
if (sRef == null) {
- sRef = new CookieSyncManager(context);
+ sRef = new CookieSyncManager(context.getApplicationContext());
}
return sRef;
}
diff --git a/core/java/android/webkit/DataLoader.java b/core/java/android/webkit/DataLoader.java
index 6c5d10d..235dc5be 100644
--- a/core/java/android/webkit/DataLoader.java
+++ b/core/java/android/webkit/DataLoader.java
@@ -16,6 +16,10 @@
package android.webkit;
+import android.net.http.EventHandler;
+
+import com.android.internal.R;
+
import java.io.ByteArrayInputStream;
import org.apache.harmony.luni.util.Base64;
@@ -49,29 +53,25 @@ class DataLoader extends StreamLoader {
} else {
data = url.getBytes();
}
- mDataStream = new ByteArrayInputStream(data);
- mContentLength = data.length;
+ if (data != null) {
+ mDataStream = new ByteArrayInputStream(data);
+ mContentLength = data.length;
+ }
}
@Override
protected boolean setupStreamAndSendStatus() {
- mHandler.status(1, 1, 0, "OK");
- return true;
+ if (mDataStream != null) {
+ mLoadListener.status(1, 1, 200, "OK");
+ return true;
+ } else {
+ mLoadListener.error(EventHandler.ERROR,
+ mContext.getString(R.string.httpError));
+ return false;
+ }
}
@Override
protected void buildHeaders(android.net.http.Headers h) {
}
-
- /**
- * Construct a DataLoader and instruct it to start loading.
- *
- * @param url data: URL string optionally containing a mimetype
- * @param loadListener LoadListener to pass the content to
- */
- public static void requestUrl(String url, LoadListener loadListener) {
- DataLoader loader = new DataLoader(url, loadListener);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index c46702e..0e8ad7e 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -26,7 +26,7 @@ import java.util.Date;
* Sorts dates into the following groups:
* Today
* Yesterday
- * five days ago
+ * seven days ago
* one month ago
* older than a month ago
*/
@@ -38,10 +38,10 @@ public class DateSorter {
/** must be >= 3 */
public static final int DAY_COUNT = 5;
- private long [] mBins = new long[DAY_COUNT];
+ private long [] mBins = new long[DAY_COUNT-1];
private String [] mLabels = new String[DAY_COUNT];
- private static final int NUM_DAYS_AGO = 5;
+ private static final int NUM_DAYS_AGO = 7;
/**
* @param context Application context
@@ -54,27 +54,24 @@ public class DateSorter {
// Create the bins
mBins[0] = c.getTimeInMillis(); // Today
- c.roll(Calendar.DAY_OF_YEAR, -1);
+ c.add(Calendar.DAY_OF_YEAR, -1);
mBins[1] = c.getTimeInMillis(); // Yesterday
- c.roll(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
+ c.add(Calendar.DAY_OF_YEAR, -(NUM_DAYS_AGO - 1));
mBins[2] = c.getTimeInMillis(); // Five days ago
- c.roll(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
- c.roll(Calendar.MONTH, -1);
+ c.add(Calendar.DAY_OF_YEAR, NUM_DAYS_AGO); // move back to today
+ c.add(Calendar.MONTH, -1);
mBins[3] = c.getTimeInMillis(); // One month ago
- c.roll(Calendar.MONTH, -1);
- mBins[4] = c.getTimeInMillis(); // Over one month ago
// build labels
mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
- int resId = com.android.internal.R.plurals.num_days_ago;
+ int resId = com.android.internal.R.plurals.last_num_days;
String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
mLabels[2] = String.format(format, NUM_DAYS_AGO);
- mLabels[3] = context.getText(com.android.internal.R.string.oneMonthDurationPast).toString();
- mLabels[4] = context.getText(com.android.internal.R.string.beforeOneMonthDurationPast)
- .toString();
+ mLabels[3] = context.getString(com.android.internal.R.string.last_month);
+ mLabels[4] = context.getString(com.android.internal.R.string.older);
}
/**
@@ -84,11 +81,11 @@ public class DateSorter {
* date bin this date belongs to
*/
public int getIndex(long time) {
- // Lame linear search
- for (int i = 0; i < DAY_COUNT; i++) {
+ int lastDay = DAY_COUNT - 1;
+ for (int i = 0; i < lastDay; i++) {
if (time > mBins[i]) return i;
}
- return DAY_COUNT - 1;
+ return lastDay;
}
/**
@@ -96,6 +93,7 @@ public class DateSorter {
* @return string label suitable for display to user
*/
public String getLabel(int index) {
+ if (index < 0 || index >= DAY_COUNT) return "";
return mLabels[index];
}
@@ -105,17 +103,22 @@ public class DateSorter {
* @return date boundary at given index
*/
public long getBoundary(int index) {
+ int lastDay = DAY_COUNT - 1;
+ // Error case
+ if (index < 0 || index > lastDay) index = 0;
+ // Since this provides a lower boundary on dates that will be included
+ // in the given bin, provide the smallest value
+ if (index == lastDay) return Long.MIN_VALUE;
return mBins[index];
}
/**
* Calcuate 12:00am by zeroing out hour, minute, second, millisecond
*/
- private Calendar beginningOfDay(Calendar c) {
+ private void beginningOfDay(Calendar c) {
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
- return c;
}
}
diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags
new file mode 100644
index 0000000..082a437
--- /dev/null
+++ b/core/java/android/webkit/EventLogTags.logtags
@@ -0,0 +1,11 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.webkit;
+
+# browser stats for diary study
+70101 browser_zoom_level_change (start level|1|5),(end level|1|5),(time|2|3)
+70102 browser_double_tap_duration (duration|1|3),(time|2|3)
+# 70103- used by the browser app itself
+
+70150 browser_snap_center
+70151 browser_text_size_change (oldSize|1|5), (newSize|1|5)
diff --git a/core/java/android/webkit/FileLoader.java b/core/java/android/webkit/FileLoader.java
index 085f1f4..e21e9ef 100644
--- a/core/java/android/webkit/FileLoader.java
+++ b/core/java/android/webkit/FileLoader.java
@@ -18,14 +18,15 @@ package android.webkit;
import com.android.internal.R;
-import android.content.Context;
import android.content.res.AssetManager;
import android.net.http.EventHandler;
import android.net.http.Headers;
-import android.os.Environment;
+import android.util.Log;
+import android.util.TypedValue;
import java.io.File;
import java.io.FileInputStream;
+import java.lang.reflect.Field;
/**
* This class is a concrete implementation of StreamLoader that uses a
@@ -35,35 +36,46 @@ import java.io.FileInputStream;
class FileLoader extends StreamLoader {
private String mPath; // Full path to the file to load
- private Context mContext; // Application context, used for asset loads
- private boolean mIsAsset; // Indicates if the load is an asset or not
+ private int mType; // Indicates the type of the load
private boolean mAllowFileAccess; // Allow/block file system access
+ // used for files under asset directory
+ static final int TYPE_ASSET = 1;
+ // used for files under res directory
+ static final int TYPE_RES = 2;
+ // generic file
+ static final int TYPE_FILE = 3;
+
+ private static final String LOGTAG = "webkit";
+
/**
* Construct a FileLoader with the file URL specified as the content
* source.
*
* @param url Full file url pointing to content to be loaded
* @param loadListener LoadListener to pass the content to
- * @param context Context to use to access the asset.
* @param asset true if url points to an asset.
* @param allowFileAccess true if this WebView is allowed to access files
* on the file system.
*/
- FileLoader(String url, LoadListener loadListener, Context context,
- boolean asset, boolean allowFileAccess) {
+ FileLoader(String url, LoadListener loadListener, int type,
+ boolean allowFileAccess) {
super(loadListener);
- mIsAsset = asset;
- mContext = context;
+ mType = type;
mAllowFileAccess = allowFileAccess;
// clean the Url
int index = url.indexOf('?');
- if (mIsAsset) {
+ if (mType == TYPE_ASSET) {
mPath = index > 0 ? URLUtil.stripAnchor(
url.substring(URLUtil.ASSET_BASE.length(), index)) :
URLUtil.stripAnchor(url.substring(
URLUtil.ASSET_BASE.length()));
+ } else if (mType == TYPE_RES) {
+ mPath = index > 0 ? URLUtil.stripAnchor(
+ url.substring(URLUtil.RESOURCE_BASE.length(), index)) :
+ URLUtil.stripAnchor(url.substring(
+ URLUtil.RESOURCE_BASE.length()));
} else {
mPath = index > 0 ? URLUtil.stripAnchor(
url.substring(URLUtil.FILE_BASE.length(), index)) :
@@ -84,16 +96,72 @@ class FileLoader extends StreamLoader {
@Override
protected boolean setupStreamAndSendStatus() {
try {
- if (mIsAsset) {
+ if (mType == TYPE_ASSET) {
try {
mDataStream = mContext.getAssets().open(mPath);
} catch (java.io.FileNotFoundException ex) {
// try the rest files included in the package
mDataStream = mContext.getAssets().openNonAsset(mPath);
}
+ } else if (mType == TYPE_RES) {
+ // get the resource id from the path. e.g. for the path like
+ // drawable/foo.png, the id is located at field "foo" of class
+ // "<package>.R$drawable"
+ if (mPath == null || mPath.length() == 0) {
+ Log.e(LOGTAG, "Need a path to resolve the res file");
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+
+ }
+ int slash = mPath.indexOf('/');
+ int dot = mPath.indexOf('.', slash);
+ if (slash == -1 || dot == -1) {
+ Log.e(LOGTAG, "Incorrect res path: " + mPath);
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+ }
+ String subClassName = mPath.substring(0, slash);
+ String fieldName = mPath.substring(slash + 1, dot);
+ String errorMsg = null;
+ try {
+ final Class<?> d = mContext.getApplicationContext()
+ .getClassLoader().loadClass(
+ mContext.getPackageName() + ".R$"
+ + subClassName);
+ final Field field = d.getField(fieldName);
+ final int id = field.getInt(null);
+ TypedValue value = new TypedValue();
+ mContext.getResources().getValue(id, value, true);
+ if (value.type == TypedValue.TYPE_STRING) {
+ mDataStream = mContext.getAssets().openNonAsset(
+ value.assetCookie, value.string.toString(),
+ AssetManager.ACCESS_STREAMING);
+ } else {
+ errorMsg = "Only support TYPE_STRING for the res files";
+ }
+ } catch (ClassNotFoundException e) {
+ errorMsg = "Can't find class: "
+ + mContext.getPackageName() + ".R$" + subClassName;
+ } catch (SecurityException e) {
+ errorMsg = "Caught SecurityException: " + e;
+ } catch (NoSuchFieldException e) {
+ errorMsg = "Can't find field: " + fieldName + " in "
+ + mContext.getPackageName() + ".R$" + subClassName;
+ } catch (IllegalArgumentException e) {
+ errorMsg = "Caught IllegalArgumentException: " + e;
+ } catch (IllegalAccessException e) {
+ errorMsg = "Caught IllegalAccessException: " + e;
+ }
+ if (errorMsg != null) {
+ mLoadListener.error(EventHandler.FILE_ERROR, mContext
+ .getString(R.string.httpErrorFileNotFound));
+ return false;
+ }
} else {
if (!mAllowFileAccess) {
- mHandler.error(EventHandler.FILE_ERROR,
+ mLoadListener.error(EventHandler.FILE_ERROR,
mContext.getString(R.string.httpErrorFileNotFound));
return false;
}
@@ -101,14 +169,14 @@ class FileLoader extends StreamLoader {
mDataStream = new FileInputStream(mPath);
mContentLength = (new File(mPath)).length();
}
- mHandler.status(1, 1, 0, "OK");
+ mLoadListener.status(1, 1, 200, "OK");
} catch (java.io.FileNotFoundException ex) {
- mHandler.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_NOT_FOUND_ERROR, errString(ex));
return false;
} catch (java.io.IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR, errString(ex));
+ mLoadListener.error(EventHandler.FILE_ERROR, errString(ex));
return false;
}
return true;
@@ -118,23 +186,4 @@ class FileLoader extends StreamLoader {
protected void buildHeaders(Headers headers) {
// do nothing.
}
-
-
- /**
- * Construct a FileLoader and instruct it to start loading.
- *
- * @param url Full file url pointing to content to be loaded
- * @param loadListener LoadListener to pass the content to
- * @param context Context to use to access the asset.
- * @param asset true if url points to an asset.
- * @param allowFileAccess true if this FileLoader can load files from the
- * file system.
- */
- public static void requestUrl(String url, LoadListener loadListener,
- Context context, boolean asset, boolean allowFileAccess) {
- FileLoader loader = new FileLoader(url, loadListener, context, asset,
- allowFileAccess);
- loader.load();
- }
-
}
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index c1eeb3b..7fd993a 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -102,15 +102,21 @@ class FrameLoader {
com.android.internal.R.string.httpErrorBadUrl));
return false;
}
- // Make sure it is correctly URL encoded before sending the request
- if (!URLUtil.verifyURLEncoding(url)) {
+ // Make sure the host part of the url is correctly
+ // encoded before sending the request
+ if (!URLUtil.verifyURLEncoding(mListener.host())) {
mListener.error(EventHandler.ERROR_BAD_URL,
mListener.getContext().getString(
com.android.internal.R.string.httpErrorBadUrl));
return false;
}
mNetwork = Network.getInstance(mListener.getContext());
- return handleHTTPLoad();
+ if (mListener.isSynchronous()) {
+ return handleHTTPLoad();
+ }
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
+ return true;
} else if (handleLocalFile(url, mListener, mSettings)) {
return true;
}
@@ -141,21 +147,57 @@ class FrameLoader {
return true;
}
if (URLUtil.isAssetUrl(url)) {
- FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- true, settings.getAllowFileAccess());
+ if (loadListener.isSynchronous()) {
+ new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
+ true).load();
+ } else {
+ // load asset in a separate thread as it involves IO
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER,
+ new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
+ true)).sendToTarget();
+ }
+ return true;
+ } else if (URLUtil.isResourceUrl(url)) {
+ if (loadListener.isSynchronous()) {
+ new FileLoader(url, loadListener, FileLoader.TYPE_RES,
+ true).load();
+ } else {
+ // load resource in a separate thread as it involves IO
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER,
+ new FileLoader(url, loadListener, FileLoader.TYPE_RES,
+ true)).sendToTarget();
+ }
return true;
} else if (URLUtil.isFileUrl(url)) {
- FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
- false, settings.getAllowFileAccess());
+ if (loadListener.isSynchronous()) {
+ new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
+ settings.getAllowFileAccess()).load();
+ } else {
+ // load file in a separate thread as it involves IO
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER,
+ new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
+ settings.getAllowFileAccess())).sendToTarget();
+ }
return true;
} else if (URLUtil.isContentUrl(url)) {
// Send the raw url to the ContentLoader because it will do a
- // permission check and the url has to match..
- ContentLoader.requestUrl(loadListener.url(), loadListener,
- loadListener.getContext());
+ // permission check and the url has to match.
+ if (loadListener.isSynchronous()) {
+ new ContentLoader(loadListener.url(), loadListener).load();
+ } else {
+ // load content in a separate thread as it involves IO
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER,
+ new ContentLoader(loadListener.url(), loadListener))
+ .sendToTarget();
+ }
return true;
} else if (URLUtil.isDataUrl(url)) {
- DataLoader.requestUrl(url, loadListener);
+ // load data in the current thread to reduce the latency
+ new DataLoader(url, loadListener).load();
return true;
} else if (URLUtil.isAboutUrl(url)) {
loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
@@ -164,8 +206,8 @@ class FrameLoader {
}
return false;
}
-
- private boolean handleHTTPLoad() {
+
+ boolean handleHTTPLoad() {
if (mHeaders == null) {
mHeaders = new HashMap<String, String>();
}
@@ -221,7 +263,13 @@ class FrameLoader {
CacheLoader cacheLoader =
new CacheLoader(mListener, result);
mListener.setCacheLoader(cacheLoader);
- cacheLoader.load();
+ if (mListener.isSynchronous()) {
+ cacheLoader.load();
+ } else {
+ // Load the cached file in a separate thread
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
+ }
}
/*
@@ -242,7 +290,7 @@ class FrameLoader {
// to load POST content in a history navigation.
case WebSettings.LOAD_CACHE_ONLY: {
CacheResult result = CacheManager.getCacheFile(mListener.url(),
- null);
+ mListener.postIdentifier(), null);
if (result != null) {
startCacheLoad(result);
} else {
@@ -270,7 +318,7 @@ class FrameLoader {
// Get the cache file name for the current URL, passing null for
// the validation headers causes no validation to occur
CacheResult result = CacheManager.getCacheFile(mListener.url(),
- null);
+ mListener.postIdentifier(), null);
if (result != null) {
startCacheLoad(result);
return true;
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index d12d828..4565b75 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.Vector;
/**
@@ -61,11 +62,8 @@ public final class GeolocationPermissions {
private Handler mHandler;
private Handler mUIHandler;
- // Members used to transfer the origins and permissions between threads.
- private Set<String> mOrigins;
- private boolean mAllowed;
- private Set<String> mOriginsToClear;
- private Set<String> mOriginsToAllow;
+ // A queue to store messages until the handler is ready.
+ private Vector<Message> mQueuedMessages;
// Message ids
static final int GET_ORIGINS = 0;
@@ -126,7 +124,7 @@ public final class GeolocationPermissions {
* Creates the message handler. Must be called on the WebKit thread.
* @hide
*/
- public void createHandler() {
+ public synchronized void createHandler() {
if (mHandler == null) {
mHandler = new Handler() {
@Override
@@ -134,21 +132,21 @@ public final class GeolocationPermissions {
// Runs on the WebKit thread.
switch (msg.what) {
case GET_ORIGINS: {
- getOriginsImpl();
+ Set origins = nativeGetOrigins();
ValueCallback callback = (ValueCallback) msg.obj;
Map values = new HashMap<String, Object>();
values.put(CALLBACK, callback);
- values.put(ORIGINS, mOrigins);
+ values.put(ORIGINS, origins);
postUIMessage(Message.obtain(null, RETURN_ORIGINS, values));
} break;
case GET_ALLOWED: {
Map values = (Map) msg.obj;
String origin = (String) values.get(ORIGIN);
ValueCallback callback = (ValueCallback) values.get(CALLBACK);
- getAllowedImpl(origin);
+ boolean allowed = nativeGetAllowed(origin);
Map retValues = new HashMap<String, Object>();
retValues.put(CALLBACK, callback);
- retValues.put(ALLOWED, new Boolean(mAllowed));
+ retValues.put(ALLOWED, new Boolean(allowed));
postUIMessage(Message.obtain(null, RETURN_ALLOWED, retValues));
} break;
case CLEAR:
@@ -164,15 +162,12 @@ public final class GeolocationPermissions {
}
};
- if (mOriginsToClear != null) {
- for (String origin : mOriginsToClear) {
- nativeClear(origin);
- }
- }
- if (mOriginsToAllow != null) {
- for (String origin : mOriginsToAllow) {
- nativeAllow(origin);
+ // Handle the queued messages
+ if (mQueuedMessages != null) {
+ while (!mQueuedMessages.isEmpty()) {
+ mHandler.sendMessage(mQueuedMessages.remove(0));
}
+ mQueuedMessages = null;
}
}
}
@@ -180,9 +175,15 @@ public final class GeolocationPermissions {
/**
* Utility function to send a message to our handler.
*/
- private void postMessage(Message msg) {
- assert(mHandler != null);
- mHandler.sendMessage(msg);
+ private synchronized void postMessage(Message msg) {
+ if (mHandler == null) {
+ if (mQueuedMessages == null) {
+ mQueuedMessages = new Vector<Message>();
+ }
+ mQueuedMessages.add(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
}
/**
@@ -207,8 +208,8 @@ public final class GeolocationPermissions {
public void getOrigins(ValueCallback<Set<String> > callback) {
if (callback != null) {
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- getOriginsImpl();
- callback.onReceiveValue(mOrigins);
+ Set origins = nativeGetOrigins();
+ callback.onReceiveValue(origins);
} else {
postMessage(Message.obtain(null, GET_ORIGINS, callback));
}
@@ -216,14 +217,6 @@ public final class GeolocationPermissions {
}
/**
- * Helper method to get the set of origins.
- */
- private void getOriginsImpl() {
- // Called on the WebKit thread.
- mOrigins = nativeGetOrigins();
- }
-
- /**
* Gets the permission state for the specified origin.
*
* Callback is a ValueCallback object whose onReceiveValue method will be
@@ -238,8 +231,8 @@ public final class GeolocationPermissions {
return;
}
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
- getAllowedImpl(origin);
- callback.onReceiveValue(new Boolean(mAllowed));
+ boolean allowed = nativeGetAllowed(origin);
+ callback.onReceiveValue(new Boolean(allowed));
} else {
Map values = new HashMap<String, Object>();
values.put(ORIGIN, origin);
@@ -249,31 +242,13 @@ public final class GeolocationPermissions {
}
/**
- * Helper method to get the permission state for the specified origin.
- */
- private void getAllowedImpl(String origin) {
- // Called on the WebKit thread.
- mAllowed = nativeGetAllowed(origin);
- }
-
- /**
* Clears the permission state for the specified origin. This method may be
* called before the WebKit thread has intialized the message handler.
* Messages will be queued until this time.
*/
public void clear(String origin) {
// Called on the UI thread.
- if (mHandler == null) {
- if (mOriginsToClear == null) {
- mOriginsToClear = new HashSet<String>();
- }
- mOriginsToClear.add(origin);
- if (mOriginsToAllow != null) {
- mOriginsToAllow.remove(origin);
- }
- } else {
- postMessage(Message.obtain(null, CLEAR, origin));
- }
+ postMessage(Message.obtain(null, CLEAR, origin));
}
/**
@@ -283,17 +258,7 @@ public final class GeolocationPermissions {
*/
public void allow(String origin) {
// Called on the UI thread.
- if (mHandler == null) {
- if (mOriginsToAllow == null) {
- mOriginsToAllow = new HashSet<String>();
- }
- mOriginsToAllow.add(origin);
- if (mOriginsToClear != null) {
- mOriginsToClear.remove(origin);
- }
- } else {
- postMessage(Message.obtain(null, ALLOW, origin));
- }
+ postMessage(Message.obtain(null, ALLOW, origin));
}
/**
diff --git a/core/java/android/webkit/GoogleLocationSettingManager.java b/core/java/android/webkit/GoogleLocationSettingManager.java
deleted file mode 100644
index ecac70a..0000000
--- a/core/java/android/webkit/GoogleLocationSettingManager.java
+++ /dev/null
@@ -1,209 +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.webkit;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.provider.Settings;
-
-import java.util.HashSet;
-
-/**
- * A class to manage the interaction between the system setting 'Location &
- * Security - Share with Google' and the browser. When this setting is set
- * to true, we allow Geolocation for Google origins. When this setting is
- * set to false, we clear Geolocation permissions for Google origins.
- */
-class GoogleLocationSettingManager {
- // The observer used to listen to the system setting.
- private GoogleLocationSettingObserver mSettingObserver;
-
- // The value of the system setting that indicates true.
- private final static int sSystemSettingTrue = 1;
- // The value of the system setting that indicates false.
- private final static int sSystemSettingFalse = 0;
- // The value of the USE_LOCATION_FOR_SERVICES system setting last read
- // by the browser.
- private final static String LAST_READ_USE_LOCATION_FOR_SERVICES =
- "lastReadUseLocationForServices";
- // The Browser package name.
- private static final String BROWSER_PACKAGE_NAME = "com.android.browser";
- // The Google origins we consider.
- private static HashSet<String> sGoogleOrigins;
- static {
- sGoogleOrigins = new HashSet<String>();
- // NOTE: DO NOT ADD A "/" AT THE END!
- sGoogleOrigins.add("http://www.google.com");
- sGoogleOrigins.add("http://www.google.co.uk");
- }
-
- private static GoogleLocationSettingManager sGoogleLocationSettingManager = null;
- private static int sRefCount = 0;
-
- static GoogleLocationSettingManager getInstance() {
- if (sGoogleLocationSettingManager == null) {
- sGoogleLocationSettingManager = new GoogleLocationSettingManager();
- }
- return sGoogleLocationSettingManager;
- }
-
- private GoogleLocationSettingManager() {}
-
- /**
- * Starts the manager. Checks whether the setting has changed and
- * installs an observer to listen for future changes.
- */
- public void start(Context context) {
- // Are we running in the browser?
- if (context == null || !BROWSER_PACKAGE_NAME.equals(context.getPackageName())) {
- return;
- }
- // Increase the refCount
- sRefCount++;
- // Are we already registered?
- if (mSettingObserver != null) {
- return;
- }
- // Read and apply the settings if needed.
- maybeApplySetting(context);
- // Register to receive notifications when the system settings change.
- mSettingObserver = new GoogleLocationSettingObserver();
- mSettingObserver.observe(context);
- }
-
- /**
- * Stops the manager.
- */
- public void stop() {
- // Are we already registered?
- if (mSettingObserver == null) {
- return;
- }
- if (--sRefCount == 0) {
- mSettingObserver.doNotObserve();
- mSettingObserver = null;
- }
- }
- /**
- * Checks to see if the system setting has changed and if so,
- * updates the Geolocation permissions accordingly.
- * @param the Application context
- */
- private void maybeApplySetting(Context context) {
- int setting = getSystemSetting(context);
- if (settingChanged(setting, context)) {
- applySetting(setting);
- }
- }
-
- /**
- * Gets the current system setting for 'Use location for Google services'.
- * @param the Application context
- * @return The system setting.
- */
- private int getSystemSetting(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.USE_LOCATION_FOR_SERVICES,
- sSystemSettingFalse);
- }
-
- /**
- * Determines whether the supplied setting has changed from the last
- * value read by the browser.
- * @param setting The setting.
- * @param the Application context
- * @return Whether the setting has changed from the last value read
- * by the browser.
- */
- private boolean settingChanged(int setting, Context context) {
- SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(context);
- // Default to false. If the system setting is false the first time it is ever read by the
- // browser, there's nothing to do.
- int lastReadSetting = sSystemSettingFalse;
- lastReadSetting = preferences.getInt(LAST_READ_USE_LOCATION_FOR_SERVICES,
- lastReadSetting);
-
- if (lastReadSetting == setting) {
- return false;
- }
-
- Editor editor = preferences.edit();
- editor.putInt(LAST_READ_USE_LOCATION_FOR_SERVICES, setting);
- editor.commit();
- return true;
- }
-
- /**
- * Applies the supplied setting to the Geolocation permissions.
- * @param setting The setting.
- */
- private void applySetting(int setting) {
- for (String origin : sGoogleOrigins) {
- if (setting == sSystemSettingTrue) {
- GeolocationPermissions.getInstance().allow(origin);
- } else {
- GeolocationPermissions.getInstance().clear(origin);
- }
- }
- }
-
- /**
- * This class implements an observer to listen for changes to the
- * system setting.
- */
- private class GoogleLocationSettingObserver extends ContentObserver {
- private Context mContext;
-
- GoogleLocationSettingObserver() {
- super(new Handler());
- }
-
- void observe(Context context) {
- if (mContext != null) {
- return;
- }
- ContentResolver resolver = context.getContentResolver();
- resolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.USE_LOCATION_FOR_SERVICES), false, this);
- mContext = context;
- }
-
- void doNotObserve() {
- if (mContext == null) {
- return;
- }
- ContentResolver resolver = mContext.getContentResolver();
- resolver.unregisterContentObserver(this);
- mContext = null;
- }
-
- @Override
- public void onChange(boolean selfChange) {
- // This may come after the call to doNotObserve() above,
- // so mContext may be null.
- if (mContext != null) {
- maybeApplySetting(mContext);
- }
- }
- }
-}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index b7a9065..eda2e72 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -48,6 +48,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
/**
* <p>Proxy for HTML5 video views.
@@ -71,6 +73,11 @@ class HTML5VideoViewProxy extends Handler
private static final int ENDED = 201;
private static final int POSTER_FETCHED = 202;
+ private static final String COOKIE = "Cookie";
+
+ // Timer thread -> UI thread
+ private static final int TIMEUPDATE = 300;
+
// The C++ MediaPlayerPrivateAndroid object.
int mNativePointer;
// The handler for WebCore thread messages;
@@ -95,6 +102,22 @@ class HTML5VideoViewProxy extends Handler
private static View mProgressView;
// The container for the progress view and video view
private static FrameLayout mLayout;
+ // The timer for timeupate events.
+ // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
+ private static Timer mTimer;
+ private static final class TimeupdateTask extends TimerTask {
+ private HTML5VideoViewProxy mProxy;
+
+ public TimeupdateTask(HTML5VideoViewProxy proxy) {
+ mProxy = proxy;
+ }
+
+ public void run() {
+ mProxy.onTimeupdate();
+ }
+ }
+ // The spec says the timer should fire every 250 ms or less.
+ private static final int TIMEUPDATE_PERIOD = 250; // ms
private static final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
@@ -104,7 +127,12 @@ class HTML5VideoViewProxy extends Handler
// which happens when the video view is detached from its parent
// view. This happens in the WebChromeClient before this method
// is invoked.
- mCurrentProxy.playbackEnded();
+ mTimer.cancel();
+ mTimer = null;
+ if (mVideoView.isPlaying()) {
+ mVideoView.stopPlayback();
+ }
+ mCurrentProxy.dispatchOnEnded();
mCurrentProxy = null;
mLayout.removeView(mVideoView);
mVideoView = null;
@@ -118,11 +146,19 @@ class HTML5VideoViewProxy extends Handler
public static void play(String url, int time, HTML5VideoViewProxy proxy,
WebChromeClient client) {
+ if (mCurrentProxy == proxy) {
+ if (!mVideoView.isPlaying()) {
+ mVideoView.start();
+ }
+ return;
+ }
+
if (mCurrentProxy != null) {
// Some other video is already playing. Notify the caller that its playback ended.
- proxy.playbackEnded();
+ proxy.dispatchOnEnded();
return;
}
+
mCurrentProxy = proxy;
// Create a FrameLayout that will contain the VideoView and the
// progress view (if any).
@@ -134,7 +170,15 @@ class HTML5VideoViewProxy extends Handler
mVideoView = new VideoView(proxy.getContext());
mVideoView.setWillNotDraw(false);
mVideoView.setMediaController(new MediaController(proxy.getContext()));
- mVideoView.setVideoURI(Uri.parse(url));
+
+ String cookieValue = CookieManager.getInstance().getCookie(url);
+ Map<String, String> headers = null;
+ if (cookieValue != null) {
+ headers = new HashMap<String, String>();
+ headers.put(COOKIE, cookieValue);
+ }
+
+ mVideoView.setVideoURI(Uri.parse(url), headers);
mVideoView.setOnCompletionListener(proxy);
mVideoView.setOnPreparedListener(proxy);
mVideoView.setOnErrorListener(proxy);
@@ -146,10 +190,23 @@ class HTML5VideoViewProxy extends Handler
mProgressView.setVisibility(View.VISIBLE);
}
mLayout.setVisibility(View.VISIBLE);
+ mTimer = new Timer();
mVideoView.start();
client.onShowCustomView(mLayout, mCallback);
}
+ public static boolean isPlaying(HTML5VideoViewProxy proxy) {
+ return (mCurrentProxy == proxy && mVideoView != null && mVideoView.isPlaying());
+ }
+
+ public static int getCurrentPosition() {
+ int currentPosMs = 0;
+ if (mVideoView != null) {
+ currentPosMs = mVideoView.getCurrentPosition();
+ }
+ return currentPosMs;
+ }
+
public static void seek(int time, HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && time >= 0 && mVideoView != null) {
mVideoView.seekTo(time);
@@ -159,6 +216,7 @@ class HTML5VideoViewProxy extends Handler
public static void pause(HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && mVideoView != null) {
mVideoView.pause();
+ mTimer.purge();
}
}
@@ -166,6 +224,7 @@ class HTML5VideoViewProxy extends Handler
if (mProgressView == null || mLayout == null) {
return;
}
+ mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
mProgressView.setVisibility(View.GONE);
mLayout.removeView(mProgressView);
mProgressView = null;
@@ -187,7 +246,10 @@ class HTML5VideoViewProxy extends Handler
// MediaPlayer.OnCompletionListener;
public void onCompletion(MediaPlayer mp) {
- playbackEnded();
+ // The video ended by itself, so we need to
+ // send a message to the UI thread to dismiss
+ // the video view and to return to the WebView.
+ sendMessage(obtainMessage(ENDED));
}
// MediaPlayer.OnErrorListener
@@ -196,12 +258,16 @@ class HTML5VideoViewProxy extends Handler
return false;
}
- public void playbackEnded() {
+ public void dispatchOnEnded() {
Message msg = Message.obtain(mWebCoreHandler, ENDED);
mWebCoreHandler.sendMessage(msg);
}
- // Handler for the messages from WebCore thread to the UI thread.
+ public void onTimeupdate() {
+ sendMessage(obtainMessage(TIMEUPDATE));
+ }
+
+ // Handler for the messages from WebCore or Timer thread to the UI thread.
@Override
public void handleMessage(Message msg) {
// This executes on the UI thread.
@@ -224,6 +290,7 @@ class HTML5VideoViewProxy extends Handler
VideoPlayer.pause(this);
break;
}
+ case ENDED:
case ERROR: {
WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) {
@@ -238,6 +305,12 @@ class HTML5VideoViewProxy extends Handler
}
break;
}
+ case TIMEUPDATE: {
+ if (VideoPlayer.isPlaying(this)) {
+ sendTimeupdate();
+ }
+ break;
+ }
}
}
@@ -407,6 +480,9 @@ class HTML5VideoViewProxy extends Handler
Bitmap poster = (Bitmap) msg.obj;
nativeOnPosterFetched(poster, mNativePointer);
break;
+ case TIMEUPDATE:
+ nativeOnTimeupdate(msg.arg1, mNativePointer);
+ break;
}
}
};
@@ -423,6 +499,12 @@ class HTML5VideoViewProxy extends Handler
mWebCoreHandler.sendMessage(msg);
}
+ private void sendTimeupdate() {
+ Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
+ msg.arg1 = VideoPlayer.getCurrentPosition();
+ mWebCoreHandler.sendMessage(msg);
+ }
+
public Context getContext() {
return mWebView.getContext();
}
@@ -503,4 +585,5 @@ class HTML5VideoViewProxy extends Handler
private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
private native void nativeOnEnded(int nativePointer);
private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
+ private native void nativeOnTimeupdate(int position, int nativePointer);
}
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 1c17575..ed85da6 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -19,6 +19,7 @@ package android.webkit;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import java.util.ListIterator;
import java.util.LinkedList;
@@ -52,6 +53,14 @@ public class HttpAuthHandler extends Handler {
private static final int AUTH_PROCEED = 100;
private static final int AUTH_CANCEL = 200;
+ // Use to synchronize when making synchronous calls to
+ // onReceivedHttpAuthRequest(). We can't use a single Boolean object for
+ // both the lock and the state, because Boolean is immutable.
+ Object mRequestInFlightLock = new Object();
+ boolean mRequestInFlight;
+ String mUsername;
+ String mPassword;
+
/**
* Creates a new HTTP authentication handler with an empty
* loader queue
@@ -70,6 +79,7 @@ public class HttpAuthHandler extends Handler {
synchronized (mLoaderQueue) {
loader = mLoaderQueue.poll();
}
+ assert(loader.isSynchronous() == false);
switch (msg.what) {
case AUTH_PROCEED:
@@ -87,25 +97,70 @@ public class HttpAuthHandler extends Handler {
processNextLoader();
}
+ /**
+ * Helper method used to unblock handleAuthRequest(), which in the case of a
+ * synchronous request will wait for proxy.onReceivedHttpAuthRequest() to
+ * call back to either proceed() or cancel().
+ *
+ * @param username The username to use for authentication
+ * @param password The password to use for authentication
+ * @return True if the request is synchronous and handleAuthRequest() has
+ * been unblocked
+ */
+ private boolean handleResponseForSynchronousRequest(String username, String password) {
+ LoadListener loader = null;
+ synchronized (mLoaderQueue) {
+ loader = mLoaderQueue.peek();
+ }
+ if (loader.isSynchronous()) {
+ mUsername = username;
+ mPassword = password;
+ return true;
+ }
+ return false;
+ }
+
+ private void signalRequestComplete() {
+ synchronized (mRequestInFlightLock) {
+ assert(mRequestInFlight);
+ mRequestInFlight = false;
+ mRequestInFlightLock.notify();
+ }
+ }
/**
* Proceed with the authorization with the given credentials
*
+ * May be called on the UI thread, rather than the WebCore thread.
+ *
* @param username The username to use for authentication
* @param password The password to use for authentication
*/
public void proceed(String username, String password) {
+ if (handleResponseForSynchronousRequest(username, password)) {
+ signalRequestComplete();
+ return;
+ }
Message msg = obtainMessage(AUTH_PROCEED);
msg.getData().putString("username", username);
msg.getData().putString("password", password);
sendMessage(msg);
+ signalRequestComplete();
}
/**
* Cancel the authorization request
+ *
+ * May be called on the UI thread, rather than the WebCore thread.
+ *
*/
public void cancel() {
+ if (handleResponseForSynchronousRequest(null, null)) {
+ signalRequestComplete();
+ return;
+ }
sendMessage(obtainMessage(AUTH_CANCEL));
+ signalRequestComplete();
}
/**
@@ -132,6 +187,34 @@ public class HttpAuthHandler extends Handler {
* authentication request
*/
/* package */ void handleAuthRequest(LoadListener loader) {
+ // The call to proxy.onReceivedHttpAuthRequest() may be asynchronous. If
+ // the request is synchronous, we must block here until we have a
+ // response.
+ if (loader.isSynchronous()) {
+ // If there's a request in flight, wait for it to complete. The
+ // response will queue a message on this thread.
+ waitForRequestToComplete();
+ // Make a request to the proxy for this request, jumping the queue.
+ // We use the queue so that the loader is present in
+ // useHttpAuthUsernamePassword().
+ synchronized (mLoaderQueue) {
+ mLoaderQueue.addFirst(loader);
+ }
+ processNextLoader();
+ // Wait for this request to complete.
+ waitForRequestToComplete();
+ // Pop the loader from the queue.
+ synchronized (mLoaderQueue) {
+ assert(mLoaderQueue.peek() == loader);
+ mLoaderQueue.poll();
+ }
+ // Call back.
+ loader.handleAuthResponse(mUsername, mPassword);
+ // The message queued by the response from the last asynchronous
+ // request, if present, will start the next request.
+ return;
+ }
+
boolean processNext = false;
synchronized (mLoaderQueue) {
@@ -146,6 +229,21 @@ public class HttpAuthHandler extends Handler {
}
/**
+ * Wait for the request in flight, if any, to complete
+ */
+ private void waitForRequestToComplete() {
+ synchronized (mRequestInFlightLock) {
+ while (mRequestInFlight) {
+ try {
+ mRequestInFlightLock.wait();
+ } catch(InterruptedException e) {
+ Log.e(LOGTAG, "Interrupted while waiting for request to complete");
+ }
+ }
+ }
+ }
+
+ /**
* Process the next loader in the queue (helper method)
*/
private void processNextLoader() {
@@ -154,6 +252,11 @@ public class HttpAuthHandler extends Handler {
loader = mLoaderQueue.peek();
}
if (loader != null) {
+ synchronized (mRequestInFlightLock) {
+ assert(mRequestInFlight == false);
+ mRequestInFlight = true;
+ }
+
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
String hostname = loader.proxyAuthenticate() ?
@@ -164,4 +267,14 @@ public class HttpAuthHandler extends Handler {
proxy.onReceivedHttpAuthRequest(this, hostname, realm);
}
}
+
+ /**
+ * Informs the WebView of a new set of credentials.
+ * @hide Pending API council review
+ */
+ public static void onReceivedCredentials(LoadListener loader,
+ String host, String realm, String username, String password) {
+ CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+ proxy.onReceivedHttpAuthCredentials(host, realm, username, password);
+ }
}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index f350d13..e766693 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -16,11 +16,12 @@
package android.webkit;
-import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import java.util.Set;
+
final class JWebCoreJavaBridge extends Handler {
// Identifier for the timer message.
private static final int TIMER_MESSAGE = 1;
@@ -41,7 +42,9 @@ final class JWebCoreJavaBridge extends Handler {
private boolean mTimerPaused;
private boolean mHasDeferredTimers;
- private Context mContext;
+ // keep track of the main WebView attached to the current window so that we
+ // can get the proper Context.
+ private WebView mCurrentMainWebView;
/* package */
static final int REFRESH_PLUGINS = 100;
@@ -50,8 +53,7 @@ final class JWebCoreJavaBridge extends Handler {
* Construct a new JWebCoreJavaBridge to interface with
* WebCore timers and cookies.
*/
- public JWebCoreJavaBridge(Context context) {
- mContext = context;
+ public JWebCoreJavaBridge() {
nativeConstructor();
}
@@ -60,6 +62,22 @@ final class JWebCoreJavaBridge extends Handler {
nativeFinalize();
}
+ synchronized void setActiveWebView(WebView webview) {
+ if (mCurrentMainWebView != null) {
+ // it is possible if there is a sub-WebView. Do nothing.
+ return;
+ }
+ mCurrentMainWebView = webview;
+ }
+
+ synchronized void removeActiveWebView(WebView webview) {
+ if (mCurrentMainWebView != webview) {
+ // it is possible if there is a sub-WebView. Do nothing.
+ return;
+ }
+ mCurrentMainWebView = null;
+ }
+
/**
* Call native timer callbacks.
*/
@@ -236,9 +254,17 @@ final class JWebCoreJavaBridge extends Handler {
return CertTool.getKeyStrengthList();
}
- private String getSignedPublicKey(int index, String challenge, String url) {
- // generateKeyPair expects organizations which we don't have. Ignore url.
- return CertTool.getSignedPublicKey(mContext, index, challenge);
+ synchronized private String getSignedPublicKey(int index, String challenge,
+ String url) {
+ if (mCurrentMainWebView != null) {
+ // generateKeyPair expects organizations which we don't have. Ignore
+ // url.
+ return CertTool.getSignedPublicKey(
+ mCurrentMainWebView.getContext(), index, challenge);
+ } else {
+ Log.e(LOGTAG, "There is no active WebView for getSignedPublicKey");
+ return "";
+ }
}
private native void nativeConstructor();
@@ -247,4 +273,8 @@ final class JWebCoreJavaBridge extends Handler {
private native void nativeUpdatePluginDirectories(String[] directories,
boolean reload);
public native void setNetworkOnLine(boolean online);
+ public native void setNetworkType(String type, String subtype);
+ public native void addPackageNames(Set<String> packageNames);
+ public native void addPackageName(String packageName);
+ public native void removePackageName(String packageName);
}
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
index 0c86e0a..e61ab21 100644
--- a/core/java/android/webkit/JsResult.java
+++ b/core/java/android/webkit/JsResult.java
@@ -26,7 +26,10 @@ public class JsResult {
private boolean mTriedToNotifyBeforeReady;
// This is a basic result of a confirm or prompt dialog.
protected boolean mResult;
- // This is the caller of the prompt and is the object that is waiting.
+ /**
+ * This is the caller of the prompt and is the object that is waiting.
+ * @hide
+ */
protected final CallbackProxy mProxy;
// This is the default value of the result.
private final boolean mDefaultValue;
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 4c17f99..e6fa405 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -16,9 +16,14 @@
package android.webkit;
+import android.content.ActivityNotFoundException;
import android.content.Context;
-import android.net.WebAddress;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.ParseException;
+import android.net.Uri;
+import android.net.WebAddress;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.net.http.HttpAuthHeader;
@@ -59,6 +64,7 @@ class LoadListener extends Handler implements EventHandler {
// Standard HTTP status codes in a more representative format
private static final int HTTP_OK = 200;
+ private static final int HTTP_PARTIAL_CONTENT = 206;
private static final int HTTP_MOVED_PERMANENTLY = 301;
private static final int HTTP_FOUND = 302;
private static final int HTTP_SEE_OTHER = 303;
@@ -78,7 +84,7 @@ class LoadListener extends Handler implements EventHandler {
private static int sNativeLoaderCount;
- private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
+ private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder();
private String mUrl;
private WebAddress mUri;
@@ -96,7 +102,6 @@ class LoadListener extends Handler implements EventHandler {
private boolean mCancelled; // The request has been cancelled.
private boolean mAuthFailed; // indicates that the prev. auth failed
private CacheLoader mCacheLoader;
- private CacheManager.CacheResult mCacheResult;
private boolean mFromCache = false;
private HttpAuthHeader mAuthHeader;
private int mErrorID = OK;
@@ -104,6 +109,7 @@ class LoadListener extends Handler implements EventHandler {
private SslError mSslError;
private RequestHandle mRequestHandle;
private RequestHandle mSslErrorRequestHandle;
+ private long mPostIdentifier;
// Request data. It is only valid when we are doing a load from the
// cache. It is needed if the cache returns a redirect
@@ -116,20 +122,29 @@ class LoadListener extends Handler implements EventHandler {
// Does this loader correspond to the main-frame top-level page?
private boolean mIsMainPageLoader;
+ // Does this loader correspond to the main content (as opposed to a supporting resource)
+ private final boolean mIsMainResourceLoader;
+ private final boolean mUserGesture;
private Headers mHeaders;
+ private final String mUsername;
+ private final String mPassword;
+
// =========================================================================
// Public functions
// =========================================================================
- public static LoadListener getLoadListener(
- Context context, BrowserFrame frame, String url,
- int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+ public static LoadListener getLoadListener(Context context,
+ BrowserFrame frame, String url, int nativeLoader,
+ boolean synchronous, boolean isMainPageLoader,
+ boolean isMainResource, boolean userGesture, long postIdentifier,
+ String username, String password) {
sNativeLoaderCount += 1;
- return new LoadListener(
- context, frame, url, nativeLoader, synchronous, isMainPageLoader);
+ return new LoadListener(context, frame, url, nativeLoader, synchronous,
+ isMainPageLoader, isMainResource, userGesture, postIdentifier,
+ username, password);
}
public static int getNativeLoaderCount() {
@@ -137,7 +152,9 @@ class LoadListener extends Handler implements EventHandler {
}
LoadListener(Context context, BrowserFrame frame, String url,
- int nativeLoader, boolean synchronous, boolean isMainPageLoader) {
+ int nativeLoader, boolean synchronous, boolean isMainPageLoader,
+ boolean isMainResource, boolean userGesture, long postIdentifier,
+ String username, String password) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener constructor url=" + url);
}
@@ -150,6 +167,11 @@ class LoadListener extends Handler implements EventHandler {
mMessageQueue = new Vector<Message>();
}
mIsMainPageLoader = isMainPageLoader;
+ mIsMainResourceLoader = isMainResource;
+ mUserGesture = userGesture;
+ mPostIdentifier = postIdentifier;
+ mUsername = username;
+ mPassword = password;
}
/**
@@ -288,19 +310,38 @@ class LoadListener extends Handler implements EventHandler {
*/
public void headers(Headers headers) {
if (DebugFlags.LOAD_LISTENER) Log.v(LOGTAG, "LoadListener.headers");
+ // call db (setCookie) in the non-WebCore thread
+ if (mCancelled) return;
+ ArrayList<String> cookies = headers.getSetCookie();
+ for (int i = 0; i < cookies.size(); ++i) {
+ CookieManager.getInstance().setCookie(mUri, cookies.get(i));
+ }
sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS, headers));
}
+ // This is the same regex that DOMImplementation uses to check for xml
+ // content. Use this to check if another Activity wants to handle the
+ // content before giving it to webkit.
+ private static final String XML_MIME_TYPE =
+ "^[\\w_\\-+~!$\\^{}|.%'`#&*]+/" +
+ "[\\w_\\-+~!$\\^{}|.%'`#&*]+\\+xml$";
+
// Does the header parsing work on the WebCore thread.
private void handleHeaders(Headers headers) {
if (mCancelled) return;
- mHeaders = headers;
- ArrayList<String> cookies = headers.getSetCookie();
- for (int i = 0; i < cookies.size(); ++i) {
- CookieManager.getInstance().setCookie(mUri, cookies.get(i));
+ // Note: the headers we care in LoadListeners, like
+ // content-type/content-length, should not be updated for partial
+ // content. Just skip here and go ahead with adding data.
+ if (mStatusCode == HTTP_PARTIAL_CONTENT) {
+ // we don't support cache for partial content yet
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
+ return;
}
+ mHeaders = headers;
+
long contentLength = headers.getContentLength();
if (contentLength != Headers.NO_CONTENT_LENGTH) {
mContentLength = contentLength;
@@ -349,6 +390,27 @@ class LoadListener extends Handler implements EventHandler {
than the headers that are returned from the server. */
guessMimeType();
}
+ // At this point, mMimeType has been set to non-null.
+ if (mIsMainPageLoader && mIsMainResourceLoader && mUserGesture &&
+ Pattern.matches(XML_MIME_TYPE, mMimeType) &&
+ !mMimeType.equalsIgnoreCase("application/xhtml+xml")) {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setDataAndType(Uri.parse(url()), mMimeType);
+ ResolveInfo info = mContext.getPackageManager().resolveActivity(i,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null && !mContext.getPackageName().equals(
+ info.activityInfo.packageName)) {
+ // someone (other than the current app) knows how to
+ // handle this mime type.
+ try {
+ mContext.startActivity(i);
+ mBrowserFrame.stopLoading();
+ return;
+ } catch (ActivityNotFoundException ex) {
+ // continue loading internally.
+ }
+ }
+ }
// is it an authentication request?
boolean mustAuthenticate = (mStatusCode == HTTP_AUTH ||
@@ -361,7 +423,7 @@ class LoadListener extends Handler implements EventHandler {
// if we tried to authenticate ourselves last time
if (mAuthHeader != null) {
- // we failed, if we must to authenticate again now and
+ // we failed, if we must authenticate again now and
// we have a proxy-ness match
mAuthFailed = (mustAuthenticate &&
isProxyAuthRequest == mAuthHeader.isProxy());
@@ -408,13 +470,27 @@ class LoadListener extends Handler implements EventHandler {
mStatusCode == HTTP_MOVED_PERMANENTLY ||
mStatusCode == HTTP_TEMPORARY_REDIRECT) &&
mNativeLoader != 0) {
- if (!mFromCache && mRequestHandle != null) {
- mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode,
- headers, mMimeType, false);
- }
- if (mCacheResult != null) {
- mCacheResult.encoding = mEncoding;
+ // for POST request, only cache the result if there is an identifier
+ // associated with it. postUrl() or form submission should set the
+ // identifier while XHR POST doesn't.
+ if (!mFromCache && mRequestHandle != null
+ && (!mRequestHandle.getMethod().equals("POST")
+ || mPostIdentifier != 0)) {
+ WebViewWorker.CacheCreateData data = new WebViewWorker.CacheCreateData();
+ data.mListener = this;
+ data.mUrl = mUrl;
+ data.mMimeType = mMimeType;
+ data.mStatusCode = mStatusCode;
+ data.mPostId = mPostIdentifier;
+ data.mHeaders = headers;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_CREATE_CACHE, data).sendToTarget();
}
+ WebViewWorker.CacheEncoding ce = new WebViewWorker.CacheEncoding();
+ ce.mEncoding = mEncoding;
+ ce.mListener = this;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_UPDATE_CACHE_ENCODING, ce).sendToTarget();
}
commitHeadersCheckRedirect();
}
@@ -468,23 +544,28 @@ class LoadListener extends Handler implements EventHandler {
}
/**
- * Implementation of certificate handler for EventHandler.
- * Called every time a resource is loaded via a secure
- * connection. In this context, can be called multiple
- * times if we have redirects
- * @param certificate The SSL certifcate
- * IMPORTANT: as this is called from network thread, can't call native
- * directly
+ * Implementation of certificate handler for EventHandler. Called
+ * before a resource is requested. In this context, can be called
+ * multiple times if we have redirects
+ *
+ * IMPORTANT: as this is called from network thread, can't call
+ * native directly
+ *
+ * @param certificate The SSL certifcate or null if the request
+ * was not secure
*/
public void certificate(SslCertificate certificate) {
+ if (DebugFlags.LOAD_LISTENER) {
+ Log.v(LOGTAG, "LoadListener.certificate: " + certificate);
+ }
sendMessageInternal(obtainMessage(MSG_SSL_CERTIFICATE, certificate));
}
// Handle the certificate on the WebCore thread.
private void handleCertificate(SslCertificate certificate) {
- // if this is the top-most main-frame page loader
- if (mIsMainPageLoader) {
- // update the browser frame (ie, the main frame)
+ // if this is main resource of the top frame
+ if (mIsMainPageLoader && mIsMainResourceLoader) {
+ // update the browser frame with certificate
mBrowserFrame.certificate(certificate);
}
}
@@ -522,17 +603,18 @@ class LoadListener extends Handler implements EventHandler {
* IMPORTANT: as this is called from network thread, can't call native
* directly
* XXX: Unlike the other network thread methods, this method can do the
- * work of decoding the data and appending it to the data builder because
- * mDataBuilder is a thread-safe structure.
+ * work of decoding the data and appending it to the data builder.
*/
public void data(byte[] data, int length) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.data(): url: " + url());
}
- // Synchronize on mData because commitLoad may write mData to WebCore
- // and we don't want to replace mData or mDataLength at the same time
- // as a write.
+ // The reason isEmpty() and append() need to synchronized together is
+ // because it is possible for getFirstChunk() to be called multiple
+ // times between isEmpty() and append(). This could cause commitLoad()
+ // to finish before processing the newly appended data and no message
+ // will be sent.
boolean sendMessage = false;
synchronized (mDataBuilder) {
sendMessage = mDataBuilder.isEmpty();
@@ -593,7 +675,18 @@ class LoadListener extends Handler implements EventHandler {
if (mAuthHeader != null &&
(Network.getInstance(mContext).isValidProxySet() ||
!mAuthHeader.isProxy())) {
- Network.getInstance(mContext).handleAuthRequest(this);
+ // If this is the first attempt to authenticate, try again with the username and
+ // password supplied in the URL, if present.
+ if (!mAuthFailed && mUsername != null && mPassword != null) {
+ String host = mAuthHeader.isProxy() ?
+ Network.getInstance(mContext).getProxyHostname() :
+ mUri.mHost;
+ HttpAuthHandler.onReceivedCredentials(this, host,
+ mAuthHeader.getRealm(), mUsername, mPassword);
+ makeAuthResponse(mUsername, mPassword);
+ } else {
+ Network.getInstance(mContext).handleAuthRequest(this);
+ }
return;
}
break; // use default
@@ -603,7 +696,14 @@ class LoadListener extends Handler implements EventHandler {
// ask for it, so make sure we have a valid CacheLoader
// before calling it.
if (mCacheLoader != null) {
- mCacheLoader.load();
+ if (isSynchronous()) {
+ mCacheLoader.load();
+ } else {
+ // Load the cached file in a separate thread
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
+ .sendToTarget();
+ }
mFromCache = true;
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener cache load url=" + url());
@@ -636,7 +736,7 @@ class LoadListener extends Handler implements EventHandler {
*/
boolean checkCache(Map<String, String> headers) {
// Get the cache file name for the current URL
- CacheResult result = CacheManager.getCacheFile(url(),
+ CacheResult result = CacheManager.getCacheFile(url(), mPostIdentifier,
headers);
// Go ahead and set the cache loader to null in case the result is
@@ -662,8 +762,14 @@ class LoadListener extends Handler implements EventHandler {
Log.v(LOGTAG, "FrameLoader: HTTP URL in cache " +
"and usable: " + url());
}
- // Load the cached file
- mCacheLoader.load();
+ if (isSynchronous()) {
+ mCacheLoader.load();
+ } else {
+ // Load the cached file in a separate thread
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_ADD_STREAMLOADER, mCacheLoader)
+ .sendToTarget();
+ }
mFromCache = true;
return true;
}
@@ -772,37 +878,8 @@ class LoadListener extends Handler implements EventHandler {
+ " username: " + username
+ " password: " + password);
}
-
- // create and queue an authentication-response
if (username != null && password != null) {
- if (mAuthHeader != null && mRequestHandle != null) {
- mAuthHeader.setUsername(username);
- mAuthHeader.setPassword(password);
-
- int scheme = mAuthHeader.getScheme();
- if (scheme == HttpAuthHeader.BASIC) {
- // create a basic response
- boolean isProxy = mAuthHeader.isProxy();
-
- mRequestHandle.setupBasicAuthResponse(isProxy,
- username, password);
- } else {
- if (scheme == HttpAuthHeader.DIGEST) {
- // create a digest response
- boolean isProxy = mAuthHeader.isProxy();
-
- String realm = mAuthHeader.getRealm();
- String nonce = mAuthHeader.getNonce();
- String qop = mAuthHeader.getQop();
- String algorithm = mAuthHeader.getAlgorithm();
- String opaque = mAuthHeader.getOpaque();
-
- mRequestHandle.setupDigestAuthResponse
- (isProxy, username, password, realm,
- nonce, qop, algorithm, opaque);
- }
- }
- }
+ makeAuthResponse(username, password);
} else {
// Commit whatever data we have and tear down the loader.
commitLoad();
@@ -810,6 +887,35 @@ class LoadListener extends Handler implements EventHandler {
}
}
+ void makeAuthResponse(String username, String password) {
+ if (mAuthHeader == null || mRequestHandle == null) {
+ return;
+ }
+
+ mAuthHeader.setUsername(username);
+ mAuthHeader.setPassword(password);
+
+ int scheme = mAuthHeader.getScheme();
+ if (scheme == HttpAuthHeader.BASIC) {
+ // create a basic response
+ boolean isProxy = mAuthHeader.isProxy();
+
+ mRequestHandle.setupBasicAuthResponse(isProxy, username, password);
+ } else if (scheme == HttpAuthHeader.DIGEST) {
+ // create a digest response
+ boolean isProxy = mAuthHeader.isProxy();
+
+ String realm = mAuthHeader.getRealm();
+ String nonce = mAuthHeader.getNonce();
+ String qop = mAuthHeader.getQop();
+ String algorithm = mAuthHeader.getAlgorithm();
+ String opaque = mAuthHeader.getOpaque();
+
+ mRequestHandle.setupDigestAuthResponse(isProxy, username, password,
+ realm, nonce, qop, algorithm, opaque);
+ }
+ }
+
/**
* This is called when a request can be satisfied by the cache, however,
* the cache result could be a redirect. In this case we need to issue
@@ -861,6 +967,10 @@ class LoadListener extends Handler implements EventHandler {
}
}
+ long postIdentifier() {
+ return mPostIdentifier;
+ }
+
void attachRequestHandle(RequestHandle requestHandle) {
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "LoadListener.attachRequestHandle(): " +
@@ -884,10 +994,10 @@ class LoadListener extends Handler implements EventHandler {
* WebCore.
*/
void downloadFile() {
- // Setting the Cache Result to null ensures that this
- // content is not added to the cache
- mCacheResult = null;
-
+ // remove the cache
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
+
// Inform the client that they should download a file
mBrowserFrame.getCallbackProxy().onDownloadStart(url(),
mBrowserFrame.getUserAgentString(),
@@ -907,8 +1017,9 @@ class LoadListener extends Handler implements EventHandler {
* be used. This is just for forward/back navigation to a POST
* URL.
*/
- static boolean willLoadFromCache(String url) {
- boolean inCache = CacheManager.getCacheFile(url, null) != null;
+ static boolean willLoadFromCache(String url, long identifier) {
+ boolean inCache =
+ CacheManager.getCacheFile(url, identifier, null) != null;
if (DebugFlags.LOAD_LISTENER) {
Log.v(LOGTAG, "willLoadFromCache: " + url + " in cache: " +
inCache);
@@ -951,8 +1062,12 @@ class LoadListener extends Handler implements EventHandler {
// do not call webcore if it is redirect. According to the code in
// InspectorController::willSendRequest(), the response is only updated
- // when it is not redirect.
- if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307) {
+ // when it is not redirect. If we received a not-modified response from
+ // the server and mCacheLoader is not null, do not send the response to
+ // webkit. This is just a validation response for loading from the
+ // cache.
+ if ((mStatusCode >= 301 && mStatusCode <= 303) || mStatusCode == 307 ||
+ (mStatusCode == 304 && mCacheLoader != null)) {
return;
}
@@ -967,6 +1082,13 @@ class LoadListener extends Handler implements EventHandler {
return;
}
+ // If the response is an authentication and we've resent the
+ // request with some credentials then don't commit the headers
+ // of this response; wait for the response to the request with the
+ // credentials.
+ if (mAuthHeader != null)
+ return;
+
// Commit the headers to WebCore
int nativeResponse = createNativeResponse();
// The native code deletes the native response object.
@@ -987,7 +1109,7 @@ class LoadListener extends Handler implements EventHandler {
mCacheLoader != null) ? HTTP_OK : mStatusCode;
// pass content-type content-length and content-encoding
final int nativeResponse = nativeCreateResponse(
- mUrl, statusCode, mStatusText,
+ originalUrl(), statusCode, mStatusText,
mMimeType, mContentLength, mEncoding);
if (mHeaders != null) {
mHeaders.getHeaders(new Headers.HeaderCallback() {
@@ -1009,28 +1131,34 @@ class LoadListener extends Handler implements EventHandler {
if (mIsMainPageLoader) {
String type = sCertificateTypeMap.get(mMimeType);
if (type != null) {
- // In the case of downloading certificate, we will save it to
- // the KeyStore and stop the current loading so that it will not
- // generate a new history page
- byte[] cert = new byte[mDataBuilder.getByteSize()];
- int offset = 0;
- while (true) {
- ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
- if (c == null) break;
-
- if (c.mLength != 0) {
- System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
- offset += c.mLength;
+ // This must be synchronized so that no more data can be added
+ // after getByteSize returns.
+ synchronized (mDataBuilder) {
+ // In the case of downloading certificate, we will save it
+ // to the KeyStore and stop the current loading so that it
+ // will not generate a new history page
+ byte[] cert = new byte[mDataBuilder.getByteSize()];
+ int offset = 0;
+ while (true) {
+ ByteArrayBuilder.Chunk c = mDataBuilder.getFirstChunk();
+ if (c == null) break;
+
+ if (c.mLength != 0) {
+ System.arraycopy(c.mArray, 0, cert, offset, c.mLength);
+ offset += c.mLength;
+ }
+ c.release();
}
- mDataBuilder.releaseChunk(c);
+ CertTool.addCertificate(mContext, type, cert);
+ mBrowserFrame.stopLoading();
+ return;
}
- CertTool.addCertificate(mContext, type, cert);
- mBrowserFrame.stopLoading();
- return;
}
}
- // Give the data to WebKit now
+ // Give the data to WebKit now. We don't have to synchronize on
+ // mDataBuilder here because pulling each chunk removes it from the
+ // internal list so it cannot be modified.
PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
while (true) {
@@ -1038,16 +1166,15 @@ class LoadListener extends Handler implements EventHandler {
if (c == null) break;
if (c.mLength != 0) {
- if (mCacheResult != null) {
- try {
- mCacheResult.outStream.write(c.mArray, 0, c.mLength);
- } catch (IOException e) {
- mCacheResult = null;
- }
- }
nativeAddData(c.mArray, c.mLength);
+ WebViewWorker.CacheData data = new WebViewWorker.CacheData();
+ data.mListener = this;
+ data.mChunk = c;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_APPEND_CACHE, data).sendToTarget();
+ } else {
+ c.release();
}
- mDataBuilder.releaseChunk(c);
checker.responseAlert("res nativeAddData");
}
}
@@ -1057,16 +1184,16 @@ class LoadListener extends Handler implements EventHandler {
* cancellation or errors during the load.
*/
void tearDown() {
- if (mCacheResult != null) {
- if (getErrorID() == OK) {
- CacheManager.saveCacheFile(mUrl, mCacheResult);
- }
-
- // we need to reset mCacheResult to be null
- // resource loader's tearDown will call into WebCore's
- // nativeFinish, which in turn calls loader.cancel().
- // If we don't reset mCacheFile, the file will be deleted.
- mCacheResult = null;
+ if (getErrorID() == OK) {
+ WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
+ data.mListener = this;
+ data.mUrl = mUrl;
+ data.mPostId = mPostIdentifier;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
+ } else {
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
}
if (mNativeLoader != 0) {
PerfChecker checker = new PerfChecker();
@@ -1105,6 +1232,16 @@ class LoadListener extends Handler implements EventHandler {
}
/**
+ * Pause the load. For example, if a plugin is unable to accept more data,
+ * we pause reading from the request. Called directly from the WebCore thread.
+ */
+ void pauseLoad(boolean pause) {
+ if (mRequestHandle != null) {
+ mRequestHandle.pauseRequest(pause);
+ }
+ }
+
+ /**
* Cancel a request.
* FIXME: This will only work if the request has yet to be handled. This
* is in no way guarenteed if requests are served in a separate thread.
@@ -1124,7 +1261,8 @@ class LoadListener extends Handler implements EventHandler {
mRequestHandle = null;
}
- mCacheResult = null;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
mCancelled = true;
clearNativeLoader();
@@ -1180,18 +1318,22 @@ class LoadListener extends Handler implements EventHandler {
return;
}
- if (mOriginalUrl == null) {
- mOriginalUrl = mUrl;
- }
// Cache the redirect response
- if (mCacheResult != null) {
- if (getErrorID() == OK) {
- CacheManager.saveCacheFile(mUrl, mCacheResult);
- }
- mCacheResult = null;
+ if (getErrorID() == OK) {
+ WebViewWorker.CacheSaveData data = new WebViewWorker.CacheSaveData();
+ data.mListener = this;
+ data.mUrl = mUrl;
+ data.mPostId = mPostIdentifier;
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_SAVE_CACHE, data).sendToTarget();
+ } else {
+ WebViewWorker.getHandler().obtainMessage(
+ WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
}
+ // Saving a copy of the unstripped url for the response
+ mOriginalUrl = redirectTo;
// This will strip the anchor
setUrl(redirectTo);
@@ -1210,8 +1352,17 @@ class LoadListener extends Handler implements EventHandler {
// mRequestHandle can be null when the request was satisfied
// by the cache, and the cache returned a redirect
if (mRequestHandle != null) {
- mRequestHandle.setupRedirect(mUrl, mStatusCode,
- mRequestHeaders);
+ try {
+ mRequestHandle.setupRedirect(mUrl, mStatusCode,
+ mRequestHeaders);
+ } catch(RuntimeException e) {
+ Log.e(LOGTAG, e.getMessage());
+ // Signal a bad url error if we could not load the
+ // redirection.
+ handleError(EventHandler.ERROR_BAD_URL,
+ mContext.getString(R.string.httpErrorBadUrl));
+ return;
+ }
} else {
// If the original request came from the cache, there is no
// RequestHandle, we have to create a new one through
@@ -1482,7 +1633,7 @@ class LoadListener extends Handler implements EventHandler {
// from http thread. Then it is called again from WebViewCore thread
// after the load is completed. So make sure the queue is cleared but
// don't set it to null.
- for (int size = mMessageQueue.size(); size > 0; size--) {
+ while (!mMessageQueue.isEmpty()) {
handleMessage(mMessageQueue.remove(0));
}
}
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 60dfce7..ca9ad53 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -124,6 +124,11 @@ public class MimeTypeMap {
return null;
}
+ // Static method called by jni.
+ private static String mimeTypeFromExtension(String extension) {
+ return getSingleton().getMimeTypeFromExtension(extension);
+ }
+
/**
* Return true if the given extension has a registered MIME type.
* @param extension A file extension without the leading '.'
@@ -344,6 +349,7 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl");
sMimeTypeMap.loadEntry("application/x-quicktimeplayer", "qtl");
sMimeTypeMap.loadEntry("application/x-shar", "shar");
+ sMimeTypeMap.loadEntry("application/x-shockwave-flash", "swf");
sMimeTypeMap.loadEntry("application/x-stuffit", "sit");
sMimeTypeMap.loadEntry("application/x-sv4cpio", "sv4cpio");
sMimeTypeMap.loadEntry("application/x-sv4crc", "sv4crc");
@@ -362,6 +368,7 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("application/x-xcf", "xcf");
sMimeTypeMap.loadEntry("application/x-xfig", "fig");
sMimeTypeMap.loadEntry("application/xhtml+xml", "xhtml");
+ sMimeTypeMap.loadEntry("audio/3gpp", "3gpp");
sMimeTypeMap.loadEntry("audio/basic", "snd");
sMimeTypeMap.loadEntry("audio/midi", "mid");
sMimeTypeMap.loadEntry("audio/midi", "midi");
@@ -431,6 +438,8 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("text/calendar", "icz");
sMimeTypeMap.loadEntry("text/comma-separated-values", "csv");
sMimeTypeMap.loadEntry("text/css", "css");
+ sMimeTypeMap.loadEntry("text/html", "htm");
+ sMimeTypeMap.loadEntry("text/html", "html");
sMimeTypeMap.loadEntry("text/h323", "323");
sMimeTypeMap.loadEntry("text/iuls", "uls");
sMimeTypeMap.loadEntry("text/mathml", "mml");
@@ -475,12 +484,14 @@ public class MimeTypeMap {
sMimeTypeMap.loadEntry("text/x-tex", "cls");
sMimeTypeMap.loadEntry("text/x-vcalendar", "vcs");
sMimeTypeMap.loadEntry("text/x-vcard", "vcf");
+ sMimeTypeMap.loadEntry("video/3gpp", "3gpp");
sMimeTypeMap.loadEntry("video/3gpp", "3gp");
sMimeTypeMap.loadEntry("video/3gpp", "3g2");
sMimeTypeMap.loadEntry("video/dl", "dl");
sMimeTypeMap.loadEntry("video/dv", "dif");
sMimeTypeMap.loadEntry("video/dv", "dv");
sMimeTypeMap.loadEntry("video/fli", "fli");
+ sMimeTypeMap.loadEntry("video/m4v", "m4v");
sMimeTypeMap.loadEntry("video/mpeg", "mpeg");
sMimeTypeMap.loadEntry("video/mpeg", "mpg");
sMimeTypeMap.loadEntry("video/mpeg", "mpe");
diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java
index af0cb1e..598f20d 100644
--- a/core/java/android/webkit/Network.java
+++ b/core/java/android/webkit/Network.java
@@ -163,10 +163,10 @@ class Network {
return false;
}
- // asset, file system or data stream are handled in the other code path.
- // This only handles network request.
- if (URLUtil.isAssetUrl(url) || URLUtil.isFileUrl(url) ||
- URLUtil.isDataUrl(url)) {
+ // asset, res, file system or data stream are handled in the other code
+ // path. This only handles network request.
+ if (URLUtil.isAssetUrl(url) || URLUtil.isResourceUrl(url)
+ || URLUtil.isFileUrl(url) || URLUtil.isDataUrl(url)) {
return false;
}
@@ -180,20 +180,24 @@ class Network {
}
RequestQueue q = mRequestQueue;
+ RequestHandle handle = null;
if (loader.isSynchronous()) {
- q = new RequestQueue(loader.getContext(), 1);
- }
-
- RequestHandle handle = q.queueRequest(
- url, loader.getWebAddress(), method, headers, loader,
- bodyProvider, bodyLength);
- loader.attachRequestHandle(handle);
-
- if (loader.isSynchronous()) {
- handle.waitUntilComplete();
+ handle = q.queueSynchronousRequest(url, loader.getWebAddress(),
+ method, headers, loader, bodyProvider, bodyLength);
+ loader.attachRequestHandle(handle);
+ handle.processRequest();
loader.loadSynchronousMessages();
- q.shutdown();
+ } else {
+ handle = q.queueRequest(url, loader.getWebAddress(), method,
+ headers, loader, bodyProvider, bodyLength);
+ // FIXME: Although this is probably a rare condition, normal network
+ // requests are processed in a separate thread. This means that it
+ // is possible to process part of the request before setting the
+ // request handle on the loader. We should probably refactor this to
+ // ensure the handle is attached before processing begins.
+ loader.attachRequestHandle(handle);
}
+
return true;
}
diff --git a/core/java/android/webkit/PluginActivity.java b/core/java/android/webkit/PluginActivity.java
deleted file mode 100644
index cda7b59..0000000
--- a/core/java/android/webkit/PluginActivity.java
+++ /dev/null
@@ -1,67 +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.webkit;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-
-/**
- * This activity is invoked when a plugin elects to go into full screen mode.
- * @hide
- */
-public class PluginActivity extends Activity {
-
- /* package */ static final String INTENT_EXTRA_PACKAGE_NAME =
- "android.webkit.plugin.PACKAGE_NAME";
- /* package */ static final String INTENT_EXTRA_CLASS_NAME =
- "android.webkit.plugin.CLASS_NAME";
- /* package */ static final String INTENT_EXTRA_NPP_INSTANCE =
- "android.webkit.plugin.NPP_INSTANCE";
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Intent intent = getIntent();
- if (intent == null) {
- // No intent means no class to lookup.
- finish();
- }
- final String packageName =
- intent.getStringExtra(INTENT_EXTRA_PACKAGE_NAME);
- final String className = intent.getStringExtra(INTENT_EXTRA_CLASS_NAME);
- final int npp = intent.getIntExtra(INTENT_EXTRA_NPP_INSTANCE, -1);
- // Retrieve the PluginStub implemented in packageName.className
- PluginStub stub =
- PluginUtil.getPluginStub(this, packageName, className);
-
- if (stub != null) {
- View pluginView = stub.getFullScreenView(npp, this);
- if (pluginView != null) {
- setContentView(pluginView);
- } else {
- // No custom full-sreen view returned by the plugin, odd but
- // just in case, finish the activity.
- finish();
- }
- } else {
- finish();
- }
- }
-}
diff --git a/core/java/android/webkit/PluginFullScreenHolder.java b/core/java/android/webkit/PluginFullScreenHolder.java
new file mode 100644
index 0000000..ae326d5
--- /dev/null
+++ b/core/java/android/webkit/PluginFullScreenHolder.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2009, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package android.webkit;
+
+import android.app.Dialog;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+
+class PluginFullScreenHolder extends Dialog {
+
+ private final WebView mWebView;
+ private final int mNpp;
+ private View mContentView;
+
+ PluginFullScreenHolder(WebView webView, int npp) {
+ super(webView.getContext(), android.R.style.Theme_NoTitleBar_Fullscreen);
+ mWebView = webView;
+ mNpp = npp;
+ }
+
+ @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));
+ // 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
+ // by the ViewManager when it is re-attached to the WebView.
+ if (contentView instanceof SurfaceView) {
+ final SurfaceView sView = (SurfaceView) contentView;
+ if (sView.isFixedSize()) {
+ sView.getHolder().setSizeFromLayout();
+ }
+ }
+ super.setContentView(contentView);
+ mContentView = contentView;
+ }
+
+ @Override
+ public void onBackPressed() {
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
+ .sendToTarget();
+ }
+
+ @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 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
+ 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;
+ }
+
+ @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);
+ }
+ mWebView.getWebViewCore().sendMessage(
+ WebViewCore.EventHub.HIDE_FULLSCREEN, mNpp, 0);
+ }
+
+}
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index 4588f46..cdcb662 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -21,6 +21,7 @@ import java.util.List;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
@@ -60,6 +61,9 @@ public class PluginManager {
private static final String LOGTAG = "webkit";
+ private static final String PLUGIN_TYPE = "type";
+ private static final String TYPE_NATIVE = "native";
+
private static PluginManager mInstance = null;
private final Context mContext;
@@ -85,7 +89,7 @@ public class PluginManager {
throw new IllegalStateException(
"First call to PluginManager need a valid context.");
}
- mInstance = new PluginManager(context);
+ mInstance = new PluginManager(context.getApplicationContext());
}
return mInstance;
}
@@ -108,7 +112,8 @@ public class PluginManager {
ArrayList<String> directories = new ArrayList<String>();
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(
- PLUGIN_ACTION), PackageManager.GET_SERVICES);
+ PLUGIN_ACTION), PackageManager.GET_SERVICES
+ | PackageManager.GET_META_DATA);
synchronized(mPackageInfoCache) {
@@ -116,27 +121,35 @@ public class PluginManager {
mPackageInfoCache.clear();
for (ResolveInfo info : plugins) {
+
+ // retrieve the plugin's service information
ServiceInfo serviceInfo = info.serviceInfo;
if (serviceInfo == null) {
Log.w(LOGTAG, "Ignore bad plugin");
continue;
}
+
+ // retrieve information from the plugin's manifest
PackageInfo pkgInfo;
try {
pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
PackageManager.GET_PERMISSIONS
| PackageManager.GET_SIGNATURES);
} catch (NameNotFoundException e) {
- Log.w(LOGTAG, "Cant find plugin: " + serviceInfo.packageName);
+ Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
continue;
}
if (pkgInfo == null) {
continue;
}
+
+ // check if their is a conflict in the lib directory names
String directory = pkgInfo.applicationInfo.dataDir + "/lib";
if (directories.contains(directory)) {
continue;
}
+
+ // check if the plugin has the required permissions
String permissions[] = pkgInfo.requestedPermissions;
if (permissions == null) {
continue;
@@ -151,6 +164,8 @@ public class PluginManager {
if (!permissionOk) {
continue;
}
+
+ // check to ensure the plugin is properly signed
Signature signatures[] = pkgInfo.signatures;
if (signatures == null) {
continue;
@@ -169,6 +184,39 @@ public class PluginManager {
continue;
}
}
+
+ // determine the type of plugin from the manifest
+ if (serviceInfo.metaData == null) {
+ Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined");
+ continue;
+ }
+
+ String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
+ if (!TYPE_NATIVE.equals(pluginType)) {
+ Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
+ continue;
+ }
+
+ try {
+ Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
+
+ //TODO implement any requirements of the plugin class here!
+ boolean classFound = true;
+
+ if (!classFound) {
+ Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
+ continue;
+ }
+
+ } catch (NameNotFoundException e) {
+ Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
+ continue;
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
+ continue;
+ }
+
+ // if all checks have passed then make the plugin available
mPackageInfoCache.add(pkgInfo);
directories.add(directory);
}
@@ -177,6 +225,7 @@ public class PluginManager {
return directories.toArray(new String[directories.size()]);
}
+ /* package */
String getPluginsAPKName(String pluginLib) {
// basic error checking on input params
@@ -200,4 +249,14 @@ public class PluginManager {
String getPluginSharedDataDirectory() {
return mContext.getDir("plugins", 0).getPath();
}
+
+ /* package */
+ Class<?> getPluginClass(String packageName, String className)
+ throws NameNotFoundException, ClassNotFoundException {
+ Context pluginContext = mContext.createPackageContext(packageName,
+ Context.CONTEXT_INCLUDE_CODE |
+ Context.CONTEXT_IGNORE_SECURITY);
+ ClassLoader pluginCL = pluginContext.getClassLoader();
+ return pluginCL.loadClass(className);
+ }
}
diff --git a/core/java/android/webkit/PluginUtil.java b/core/java/android/webkit/PluginUtil.java
deleted file mode 100644
index 8fdbd67..0000000
--- a/core/java/android/webkit/PluginUtil.java
+++ /dev/null
@@ -1,59 +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.webkit;
-
-import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Log;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
-class PluginUtil {
-
- private static final String LOGTAG = "PluginUtil";
-
- /**
- *
- * @param packageName the name of the apk where the class can be found
- * @param className the fully qualified name of a subclass of PluginStub
- */
- /* package */
- static PluginStub getPluginStub(Context context, String packageName,
- String className) {
- try {
- Context pluginContext = context.createPackageContext(packageName,
- Context.CONTEXT_INCLUDE_CODE |
- Context.CONTEXT_IGNORE_SECURITY);
- ClassLoader pluginCL = pluginContext.getClassLoader();
-
- Class<?> stubClass = pluginCL.loadClass(className);
- Object stubObject = stubClass.newInstance();
-
- if (stubObject instanceof PluginStub) {
- return (PluginStub) stubObject;
- } else {
- Log.e(LOGTAG, "The plugin class is not of type PluginStub");
- }
- } catch (Exception e) {
- // Any number of things could have happened. Log the exception and
- // return null. Careful not to use Log.e(LOGTAG, "String", e)
- // because that reports the exception to the checkin service.
- Log.e(LOGTAG, Log.getStackTraceString(e));
- }
- return null;
- }
-}
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 90ed65d..1b0afaf 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -51,6 +51,10 @@ public class SslErrorHandler extends Handler {
*/
private Bundle mSslPrefTable;
+ // These are only used in the client facing SslErrorHandler.
+ private final SslErrorHandler mOriginHandler;
+ private final LoadListener mLoadListener;
+
// Message id for handling the response
private static final int HANDLE_RESPONSE = 100;
@@ -59,9 +63,12 @@ public class SslErrorHandler extends Handler {
switch (msg.what) {
case HANDLE_RESPONSE:
LoadListener loader = (LoadListener) msg.obj;
- handleSslErrorResponse(loader, loader.sslError(),
- msg.arg1 == 1);
- fastProcessQueuedSslErrors();
+ synchronized (SslErrorHandler.this) {
+ handleSslErrorResponse(loader, loader.sslError(),
+ msg.arg1 == 1);
+ mLoaderQueue.remove(loader);
+ fastProcessQueuedSslErrors();
+ }
break;
}
}
@@ -72,6 +79,18 @@ public class SslErrorHandler extends Handler {
/* package */ SslErrorHandler() {
mLoaderQueue = new LinkedList<LoadListener>();
mSslPrefTable = new Bundle();
+
+ // These are used by client facing SslErrorHandlers.
+ mOriginHandler = null;
+ mLoadListener = null;
+ }
+
+ /**
+ * Create a new error handler that will be passed to the client.
+ */
+ private SslErrorHandler(SslErrorHandler origin, LoadListener listener) {
+ mOriginHandler = origin;
+ mLoadListener = listener;
}
/**
@@ -191,7 +210,7 @@ public class SslErrorHandler extends Handler {
// if we do not have information on record, ask
// the user (display a dialog)
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
- proxy.onReceivedSslError(this, error);
+ proxy.onReceivedSslError(new SslErrorHandler(this, loader), error);
}
// the queue must be empty, stop
@@ -202,7 +221,9 @@ public class SslErrorHandler extends Handler {
* Proceed with the SSL certificate.
*/
public void proceed() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0, mLoaderQueue.poll()));
+ mOriginHandler.sendMessage(
+ mOriginHandler.obtainMessage(
+ HANDLE_RESPONSE, 1, 0, mLoadListener));
}
/**
@@ -210,7 +231,9 @@ public class SslErrorHandler extends Handler {
* the error.
*/
public void cancel() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0, mLoaderQueue.poll()));
+ mOriginHandler.sendMessage(
+ mOriginHandler.obtainMessage(
+ HANDLE_RESPONSE, 0, 0, mLoadListener));
}
/**
diff --git a/core/java/android/webkit/StreamLoader.java b/core/java/android/webkit/StreamLoader.java
index 623ff29..7bcd50d 100644
--- a/core/java/android/webkit/StreamLoader.java
+++ b/core/java/android/webkit/StreamLoader.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.content.Context;
import android.net.http.EventHandler;
import android.net.http.Headers;
import android.os.Handler;
@@ -24,7 +25,6 @@ import android.os.Message;
import java.io.IOException;
import java.io.InputStream;
-
/**
* This abstract class is used for all content loaders that rely on streaming
* content into the rendering engine loading framework.
@@ -43,20 +43,22 @@ import java.io.InputStream;
* that indicates the content should not be cached.
*
*/
-abstract class StreamLoader extends Handler {
-
- public static final String NO_STORE = "no-store";
+abstract class StreamLoader implements Handler.Callback {
private static final int MSG_STATUS = 100; // Send status to loader
private static final int MSG_HEADERS = 101; // Send headers to loader
private static final int MSG_DATA = 102; // Send data to loader
private static final int MSG_END = 103; // Send endData to loader
- protected LoadListener mHandler; // loader class
+ protected final Context mContext;
+ protected final LoadListener mLoadListener; // loader class
protected InputStream mDataStream; // stream to read data from
protected long mContentLength; // content length of data
private byte [] mData; // buffer to pass data to loader with.
+ // Handler which will be initialized in the thread where load() is called.
+ private Handler mHandler;
+
/**
* Constructor. Although this class calls the LoadListener, it only calls
* the EventHandler Interface methods. LoadListener concrete class is used
@@ -65,12 +67,13 @@ abstract class StreamLoader extends Handler {
* @param loadlistener The LoadListener to call with the data.
*/
StreamLoader(LoadListener loadlistener) {
- mHandler = loadlistener;
+ mLoadListener = loadlistener;
+ mContext = loadlistener.getContext();
}
/**
* This method is called when the derived class should setup mDataStream,
- * and call mHandler.status() to indicate that the load can occur. If it
+ * and call mLoadListener.status() to indicate that the load can occur. If it
* fails to setup, it should still call status() with the error code.
*
* @return true if stream was successfully setup
@@ -86,15 +89,20 @@ abstract class StreamLoader extends Handler {
*/
abstract protected void buildHeaders(Headers headers);
-
/**
* Calling this method starts the load of the content for this StreamLoader.
- * This method simply posts a message to send the status and returns
- * immediately.
+ * This method simply creates a Handler in the current thread and posts a
+ * message to send the status and returns immediately.
*/
- public void load() {
- if (!mHandler.isSynchronous()) {
- sendMessage(obtainMessage(MSG_STATUS));
+ final void load() {
+ synchronized (this) {
+ if (mHandler == null) {
+ mHandler = new Handler(this);
+ }
+ }
+
+ if (!mLoadListener.isSynchronous()) {
+ mHandler.sendEmptyMessage(MSG_STATUS);
} else {
// Load the stream synchronously.
if (setupStreamAndSendStatus()) {
@@ -102,23 +110,20 @@ abstract class StreamLoader extends Handler {
// to pass data to the loader
mData = new byte[8192];
sendHeaders();
- while (!sendData() && !mHandler.cancelled());
+ while (!sendData() && !mLoadListener.cancelled());
closeStreamAndSendEndData();
- mHandler.loadSynchronousMessages();
+ mLoadListener.loadSynchronousMessages();
}
}
}
- /* (non-Javadoc)
- * @see android.os.Handler#handleMessage(android.os.Message)
- */
- public void handleMessage(Message msg) {
- if (DebugFlags.STREAM_LOADER && mHandler.isSynchronous()) {
+ public boolean handleMessage(Message msg) {
+ if (mLoadListener.isSynchronous()) {
throw new AssertionError();
}
- if (mHandler.cancelled()) {
+ if (mLoadListener.cancelled()) {
closeStreamAndSendEndData();
- return;
+ return true;
}
switch(msg.what) {
case MSG_STATUS:
@@ -126,27 +131,27 @@ abstract class StreamLoader extends Handler {
// We were able to open the stream, create the array
// to pass data to the loader
mData = new byte[8192];
- sendMessage(obtainMessage(MSG_HEADERS));
+ mHandler.sendEmptyMessage(MSG_HEADERS);
}
break;
case MSG_HEADERS:
sendHeaders();
- sendMessage(obtainMessage(MSG_DATA));
+ mHandler.sendEmptyMessage(MSG_DATA);
break;
case MSG_DATA:
if (sendData()) {
- sendMessage(obtainMessage(MSG_END));
+ mHandler.sendEmptyMessage(MSG_END);
} else {
- sendMessage(obtainMessage(MSG_DATA));
+ mHandler.sendEmptyMessage(MSG_DATA);
}
break;
case MSG_END:
closeStreamAndSendEndData();
break;
default:
- super.handleMessage(msg);
- break;
+ return false;
}
+ return true;
}
/**
@@ -158,7 +163,7 @@ abstract class StreamLoader extends Handler {
headers.setContentLength(mContentLength);
}
buildHeaders(headers);
- mHandler.headers(headers);
+ mLoadListener.headers(headers);
}
/**
@@ -173,12 +178,11 @@ abstract class StreamLoader extends Handler {
try {
int amount = mDataStream.read(mData);
if (amount > 0) {
- mHandler.data(mData, amount);
+ mLoadListener.data(mData, amount);
return false;
}
} catch (IOException ex) {
- mHandler.error(EventHandler.FILE_ERROR,
- ex.getMessage());
+ mLoadListener.error(EventHandler.FILE_ERROR, ex.getMessage());
}
}
return true;
@@ -195,7 +199,6 @@ abstract class StreamLoader extends Handler {
// ignore.
}
}
- mHandler.endData();
+ mLoadListener.endData();
}
-
}
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index 232ed36..7c5f2b0 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -28,8 +28,14 @@ import android.util.Log;
public final class URLUtil {
private static final String LOGTAG = "webkit";
-
+
+ // to refer to bar.png under your package's asset/foo/ directory, use
+ // "file:///android_asset/foo/bar.png".
static final String ASSET_BASE = "file:///android_asset/";
+ // to refer to bar.png under your package's res/drawable/ directory, use
+ // "file:///android_res/drawable/bar.png". Use "drawable" to refer to
+ // "drawable-hdpi" directory as well.
+ static final String RESOURCE_BASE = "file:///android_res/";
static final String FILE_BASE = "file://";
static final String PROXY_BASE = "file:///cookieless_proxy/";
@@ -166,7 +172,15 @@ public final class URLUtil {
public static boolean isAssetUrl(String url) {
return (null != url) && url.startsWith(ASSET_BASE);
}
-
+
+ /**
+ * @return True iff the url is a resource file.
+ * @hide
+ */
+ public static boolean isResourceUrl(String url) {
+ return (null != url) && url.startsWith(RESOURCE_BASE);
+ }
+
/**
* @return True iff the url is an proxy url to allow cookieless network
* requests from a file url.
@@ -251,6 +265,7 @@ public final class URLUtil {
}
return (isAssetUrl(url) ||
+ isResourceUrl(url) ||
isFileUrl(url) ||
isAboutUrl(url) ||
isHttpUrl(url) ||
@@ -367,19 +382,23 @@ public final class URLUtil {
/** Regex used to parse content-disposition headers */
private static final Pattern CONTENT_DISPOSITION_PATTERN =
- Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
+ Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
+ Pattern.CASE_INSENSITIVE);
/*
* Parse the Content-Disposition HTTP Header. The format of the header
* is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
* This header provides a filename for content that is going to be
* downloaded to the file system. We only support the attachment type.
+ * Note that RFC 2616 specifies the filename value must be double-quoted.
+ * Unfortunately some servers do not quote the value so to maintain
+ * consistent behaviour with other browsers, we allow unquoted values too.
*/
static String parseContentDisposition(String contentDisposition) {
try {
Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
if (m.find()) {
- return m.group(1);
+ return m.group(2);
}
} catch (IllegalStateException ex) {
// This function is defined as returning null when it can't parse the header
diff --git a/core/java/android/webkit/ViewManager.java b/core/java/android/webkit/ViewManager.java
index 6a838c3..153c1c2 100644
--- a/core/java/android/webkit/ViewManager.java
+++ b/core/java/android/webkit/ViewManager.java
@@ -16,8 +16,9 @@
package android.webkit;
-import android.content.Context;
+import android.view.SurfaceView;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.AbsoluteLayout;
import java.util.ArrayList;
@@ -26,6 +27,13 @@ class ViewManager {
private final WebView mWebView;
private final ArrayList<ChildView> mChildren = new ArrayList<ChildView>();
private boolean mHidden;
+ private boolean mReadyToDraw;
+ private boolean mZoomInProgress = false;
+
+ // Threshold at which a surface is prevented from further increasing in size
+ private final int MAX_SURFACE_AREA;
+ // GPU Limit (hard coded for now)
+ private static final int MAX_SURFACE_DIMENSION = 2048;
class ChildView {
int x;
@@ -49,27 +57,28 @@ class ViewManager {
return;
}
setBounds(x, y, width, height);
- final AbsoluteLayout.LayoutParams lp =
- new AbsoluteLayout.LayoutParams(ctvD(width), ctvD(height),
- ctvX(x), ctvY(y));
+
mWebView.mPrivateHandler.post(new Runnable() {
public void run() {
// This method may be called multiple times. If the view is
// already attached, just set the new LayoutParams,
// otherwise attach the view and add it to the list of
// children.
- if (mView.getParent() != null) {
- mView.setLayoutParams(lp);
- } else {
- attachViewOnUIThread(lp);
+ requestLayout(ChildView.this);
+
+ if (mView.getParent() == null) {
+ attachViewOnUIThread();
}
}
});
}
- void attachViewOnUIThread(AbsoluteLayout.LayoutParams lp) {
- mWebView.addView(mView, lp);
+ private void attachViewOnUIThread() {
+ mWebView.addView(mView);
mChildren.add(this);
+ if (!mReadyToDraw) {
+ mView.setVisibility(View.GONE);
+ }
}
void removeView() {
@@ -83,7 +92,7 @@ class ViewManager {
});
}
- void removeViewOnUIThread() {
+ private void removeViewOnUIThread() {
mWebView.removeView(mView);
mChildren.remove(this);
}
@@ -91,6 +100,14 @@ class ViewManager {
ViewManager(WebView w) {
mWebView = w;
+
+ int pixelArea = w.getResources().getDisplayMetrics().widthPixels *
+ w.getResources().getDisplayMetrics().heightPixels;
+ /* set the threshold to be 275% larger than the screen size. The
+ percentage is simply an estimation and is not based on anything but
+ basic trial-and-error tests run on multiple devices.
+ */
+ MAX_SURFACE_AREA = (int)(pixelArea * 2.75);
}
ChildView createView() {
@@ -98,40 +115,128 @@ class ViewManager {
}
/**
- * Shorthand for calling mWebView.contentToViewDimension. Used when
- * obtaining a view dimension from a content dimension, whether it be in x
- * or y.
+ * This should only be called from the UI thread.
*/
- private int ctvD(int val) {
- return mWebView.contentToViewDimension(val);
+ private void requestLayout(ChildView v) {
+
+ int width = mWebView.contentToViewDimension(v.width);
+ int height = mWebView.contentToViewDimension(v.height);
+ int x = mWebView.contentToViewX(v.x);
+ int y = mWebView.contentToViewY(v.y);
+
+ AbsoluteLayout.LayoutParams lp;
+ ViewGroup.LayoutParams layoutParams = v.mView.getLayoutParams();
+
+ if (layoutParams instanceof AbsoluteLayout.LayoutParams) {
+ lp = (AbsoluteLayout.LayoutParams) layoutParams;
+ lp.width = width;
+ lp.height = height;
+ lp.x = x;
+ lp.y = y;
+ } else {
+ lp = new AbsoluteLayout.LayoutParams(width, height, x, y);
+ }
+
+ // apply the layout to the view
+ v.mView.setLayoutParams(lp);
+
+ if(v.mView instanceof SurfaceView) {
+
+ final SurfaceView sView = (SurfaceView) v.mView;
+
+ if (sView.isFixedSize() && mZoomInProgress) {
+ /* If we're already fixed, and we're in a zoom, then do nothing
+ about the size. Just wait until we get called at the end of
+ the zoom session (with mZoomInProgress false) and we'll
+ fixup our size then.
+ */
+ return;
+ }
+
+ /* Compute proportional fixed width/height if necessary.
+ *
+ * NOTE: plugins (e.g. Flash) must not explicitly fix the size of
+ * their surface. The logic below will result in unexpected behavior
+ * for the plugin if they attempt to fix the size of the surface.
+ */
+ int fixedW = width;
+ int fixedH = height;
+ if (fixedW > MAX_SURFACE_DIMENSION || fixedH > MAX_SURFACE_DIMENSION) {
+ if (v.width > v.height) {
+ fixedW = MAX_SURFACE_DIMENSION;
+ fixedH = v.height * MAX_SURFACE_DIMENSION / v.width;
+ } else {
+ fixedH = MAX_SURFACE_DIMENSION;
+ fixedW = v.width * MAX_SURFACE_DIMENSION / v.height;
+ }
+ }
+ if (fixedW * fixedH > MAX_SURFACE_AREA) {
+ float area = MAX_SURFACE_AREA;
+ if (v.width > v.height) {
+ fixedW = (int)Math.sqrt(area * v.width / v.height);
+ fixedH = v.height * fixedW / v.width;
+ } else {
+ fixedH = (int)Math.sqrt(area * v.height / v.width);
+ fixedW = v.width * fixedH / v.height;
+ }
+ }
+
+ if (fixedW != width || fixedH != height) {
+ // if we get here, either our dimensions or area (or both)
+ // exeeded our max, so we had to compute fixedW and fixedH
+ sView.getHolder().setFixedSize(fixedW, fixedH);
+ } else if (!sView.isFixedSize() && mZoomInProgress) {
+ // just freeze where we were (view size) until we're done with
+ // the zoom progress
+ sView.getHolder().setFixedSize(sView.getWidth(),
+ sView.getHeight());
+ } else if (sView.isFixedSize() && !mZoomInProgress) {
+ /* The changing of visibility is a hack to get around a bug in
+ * the framework that causes the surface to revert to the size
+ * it was prior to being fixed before it redraws using the
+ * values currently in its layout.
+ *
+ * The surface is destroyed when it is set to invisible and then
+ * recreated at the new dimensions when it is made visible. The
+ * same destroy/create step occurs without the change in
+ * visibility, but then exhibits the behavior described in the
+ * previous paragraph.
+ */
+ if (sView.getVisibility() == View.VISIBLE) {
+ sView.setVisibility(View.INVISIBLE);
+ sView.getHolder().setSizeFromLayout();
+ // setLayoutParams() only requests the layout. If we set it
+ // to VISIBLE now, it will use the old dimension to set the
+ // size. Post a message to ensure that it shows the new size.
+ mWebView.mPrivateHandler.post(new Runnable() {
+ public void run() {
+ sView.setVisibility(View.VISIBLE);
+ }
+ });
+ } else {
+ sView.getHolder().setSizeFromLayout();
+ }
+ }
+ }
}
- /**
- * Shorthand for calling mWebView.contentToViewX. Used when obtaining a
- * view x coordinate from a content x coordinate.
- */
- private int ctvX(int val) {
- return mWebView.contentToViewX(val);
+ void startZoom() {
+ mZoomInProgress = true;
+ for (ChildView v : mChildren) {
+ requestLayout(v);
+ }
}
- /**
- * Shorthand for calling mWebView.contentToViewY. Used when obtaining a
- * view y coordinate from a content y coordinate.
- */
- private int ctvY(int val) {
- return mWebView.contentToViewY(val);
+ void endZoom() {
+ mZoomInProgress = false;
+ for (ChildView v : mChildren) {
+ requestLayout(v);
+ }
}
void scaleAll() {
for (ChildView v : mChildren) {
- View view = v.mView;
- AbsoluteLayout.LayoutParams lp =
- (AbsoluteLayout.LayoutParams) view.getLayoutParams();
- lp.width = ctvD(v.width);
- lp.height = ctvD(v.height);
- lp.x = ctvX(v.x);
- lp.y = ctvY(v.y);
- view.setLayoutParams(lp);
+ requestLayout(v);
}
}
@@ -154,4 +259,38 @@ class ViewManager {
}
mHidden = false;
}
+
+ void postResetStateAll() {
+ mWebView.mPrivateHandler.post(new Runnable() {
+ public void run() {
+ mReadyToDraw = false;
+ }
+ });
+ }
+
+ void postReadyToDrawAll() {
+ mWebView.mPrivateHandler.post(new Runnable() {
+ public void run() {
+ mReadyToDraw = true;
+ for (ChildView v : mChildren) {
+ v.mView.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+
+ ChildView hitTest(int contentX, int contentY) {
+ if (mHidden) {
+ return null;
+ }
+ for (ChildView v : mChildren) {
+ if (v.mView.getVisibility() == View.VISIBLE) {
+ if (contentX >= v.x && contentX < (v.x + v.width)
+ && contentY >= v.y && contentY < (v.y + v.height)) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index 62a5531..79e634e 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -31,13 +31,16 @@ public class WebBackForwardList implements Cloneable, Serializable {
private ArrayList<WebHistoryItem> mArray;
// Flag to indicate that the list is invalid
private boolean mClearPending;
+ // CallbackProxy to issue client callbacks.
+ private final CallbackProxy mCallbackProxy;
/**
* Construct a back/forward list used by clients of WebView.
*/
- /*package*/ WebBackForwardList() {
+ /*package*/ WebBackForwardList(CallbackProxy proxy) {
mCurrentIndex = -1;
mArray = new ArrayList<WebHistoryItem>();
+ mCallbackProxy = proxy;
}
/**
@@ -116,6 +119,9 @@ public class WebBackForwardList implements Cloneable, Serializable {
}
// Add the item to the list.
mArray.add(item);
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onNewHistoryItem(item);
+ }
}
/**
@@ -152,7 +158,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* webkit package classes.
*/
protected synchronized WebBackForwardList clone() {
- WebBackForwardList l = new WebBackForwardList();
+ WebBackForwardList l = new WebBackForwardList(null);
if (mClearPending) {
// If a clear is pending, return a copy with only the current item.
l.addHistoryItem(getCurrentItem());
@@ -174,6 +180,9 @@ public class WebBackForwardList implements Cloneable, Serializable {
*/
/*package*/ synchronized void setCurrentIndex(int newIndex) {
mCurrentIndex = newIndex;
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex);
+ }
}
/**
diff --git a/core/java/android/webkit/WebBackForwardListClient.java b/core/java/android/webkit/WebBackForwardListClient.java
new file mode 100644
index 0000000..7fe9281
--- /dev/null
+++ b/core/java/android/webkit/WebBackForwardListClient.java
@@ -0,0 +1,40 @@
+/*
+ * 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.webkit;
+
+/**
+ * Interface to receive notifications when items are added to the
+ * {@link WebBackForwardList}.
+ * {@hide}
+ */
+public abstract class WebBackForwardListClient {
+
+ /**
+ * Notify the client that <var>item</var> has been added to the
+ * WebBackForwardList.
+ * @param item The newly created WebHistoryItem
+ */
+ public void onNewHistoryItem(WebHistoryItem item) { }
+
+ /**
+ * Notify the client that the <var>item</var> at <var>index</var> is now
+ * the current history item.
+ * @param item A WebHistoryItem
+ * @param index The new history index
+ */
+ public void onIndexChanged(WebHistoryItem item, int index) { }
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 8ca4142..1d5aac7 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Message;
import android.view.View;
@@ -261,8 +262,24 @@ public class WebChromeClient {
* @param message The error message to report.
* @param lineNumber The line number of the error.
* @param sourceID The name of the source file that caused the error.
+ * @deprecated Use {@link #onConsoleMessage(ConsoleMessage) onConsoleMessage(ConsoleMessage)}
+ * instead.
*/
- public void onConsoleMessage(String message, int lineNumber, String sourceID) {}
+ @Deprecated
+ public void onConsoleMessage(String message, int lineNumber, String sourceID) { }
+
+ /**
+ * Report a JavaScript console message to the host application. The ChromeClient
+ * should override this to process the log message as they see fit.
+ * @param consoleMessage Object containing details of the console message.
+ * @return true if the message is handled by the client.
+ */
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ // Call the old version of this function for backwards compatability.
+ onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
+ consoleMessage.sourceId());
+ return false;
+ }
/**
* When not playing, video elements are represented by a 'poster' image. The
@@ -294,4 +311,13 @@ public class WebChromeClient {
public void getVisitedHistory(ValueCallback<String[]> callback) {
}
+ /**
+ * Tell the client to open a file chooser.
+ * @param uploadFile A ValueCallback to set the URI of the file to upload.
+ * onReceiveValue must be called to wake up the thread.
+ * @hide
+ */
+ public void openFileChooser(ValueCallback<Uri> uploadFile) {
+ uploadFile.onReceiveValue(null);
+ }
}
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index abd8237..428a59c 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -41,6 +41,8 @@ public class WebHistoryItem implements Cloneable {
private byte[] mFlattenedData;
// The apple-touch-icon url for use when adding the site to the home screen
private String mTouchIconUrl;
+ // Custom client data that is not flattened or read by native code.
+ private Object mCustomData;
/**
* Basic constructor that assigns a unique id to the item. Called by JNI
@@ -137,6 +139,28 @@ public class WebHistoryItem implements Cloneable {
}
/**
+ * Return the custom data provided by the client.
+ * @hide
+ */
+ public Object getCustomData() {
+ return mCustomData;
+ }
+
+ /**
+ * Set the custom data field.
+ * @param data An Object containing any data the client wishes to associate
+ * with the item.
+ * @hide
+ */
+ public void setCustomData(Object data) {
+ // NOTE: WebHistoryItems are used in multiple threads. However, the
+ // public facing apis are all getters with the exception of this one
+ // api. Since this api is exclusive to clients, we don't make any
+ // promises about thread safety.
+ mCustomData = data;
+ }
+
+ /**
* Set the favicon.
* @param icon A Bitmap containing the favicon for this history item.
* Note: The VM ensures 32-bit atomic read/write operations so we don't have
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 6cc6bb4..bb9ec48 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -16,10 +16,15 @@
package android.webkit;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
-import android.graphics.Bitmap;
+import android.provider.Browser;
+import android.util.Log;
+import java.util.HashMap;
import java.util.Vector;
/**
@@ -30,6 +35,7 @@ import java.util.Vector;
* single object.
*/
public final class WebIconDatabase {
+ private static final String LOGTAG = "WebIconDatabase";
// Global instance of a WebIconDatabase
private static WebIconDatabase sIconDatabase;
// EventHandler for handling messages before and after the WebCore thread is
@@ -45,6 +51,7 @@ public final class WebIconDatabase {
static final int REQUEST_ICON = 3;
static final int RETAIN_ICON = 4;
static final int RELEASE_ICON = 5;
+ static final int BULK_REQUEST_ICON = 6;
// Message for dispatching icon request results
private static final int ICON_RESULT = 10;
// Actual handler that runs in WebCore thread
@@ -100,12 +107,11 @@ public final class WebIconDatabase {
case REQUEST_ICON:
IconListener l = (IconListener) msg.obj;
String url = msg.getData().getString("url");
- Bitmap icon = nativeIconForPageUrl(url);
- if (icon != null) {
- EventHandler.this.sendMessage(
- Message.obtain(null, ICON_RESULT,
- new IconResult(url, icon, l)));
- }
+ requestIconAndSendResult(url, l);
+ break;
+
+ case BULK_REQUEST_ICON:
+ bulkRequestIcons(msg);
break;
case RETAIN_ICON:
@@ -126,6 +132,10 @@ public final class WebIconDatabase {
}
}
+ private synchronized boolean hasHandler() {
+ return mHandler != null;
+ }
+
private synchronized void postMessage(Message msg) {
if (mMessages != null) {
mMessages.add(msg);
@@ -133,6 +143,39 @@ public final class WebIconDatabase {
mHandler.sendMessage(msg);
}
}
+
+ private void bulkRequestIcons(Message msg) {
+ HashMap map = (HashMap) msg.obj;
+ IconListener listener = (IconListener) map.get("listener");
+ ContentResolver cr = (ContentResolver) map.get("contentResolver");
+ String where = (String) map.get("where");
+
+ Cursor c = null;
+ try {
+ c = cr.query(
+ Browser.BOOKMARKS_URI,
+ new String[] { Browser.BookmarkColumns.URL },
+ where, null, null);
+ if (c.moveToFirst()) {
+ do {
+ String url = c.getString(0);
+ requestIconAndSendResult(url, listener);
+ } while (c.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "BulkRequestIcons", e);
+ } finally {
+ if (c != null) c.close();
+ }
+ }
+
+ private void requestIconAndSendResult(String url, IconListener listener) {
+ Bitmap icon = nativeIconForPageUrl(url);
+ if (icon != null) {
+ sendMessage(obtainMessage(ICON_RESULT,
+ new IconResult(url, icon, listener)));
+ }
+ }
}
/**
@@ -192,6 +235,30 @@ public final class WebIconDatabase {
mEventHandler.postMessage(msg);
}
+ /** {@hide}
+ */
+ public void bulkRequestIconForPageUrl(ContentResolver cr, String where,
+ IconListener listener) {
+ if (listener == null) {
+ return;
+ }
+
+ // Special case situation: we don't want to add this message to the
+ // queue if there is no handler because we may never have a real
+ // handler to service the messages and the cursor will never get
+ // closed.
+ if (mEventHandler.hasHandler()) {
+ // Don't use Bundle as it is parcelable.
+ HashMap<String, Object> map = new HashMap<String, Object>();
+ map.put("contentResolver", cr);
+ map.put("where", where);
+ map.put("listener", listener);
+ Message msg =
+ Message.obtain(null, EventHandler.BULK_REQUEST_ICON, map);
+ mEventHandler.postMessage(msg);
+ }
+ }
+
/**
* Retain the icon for the given page url.
* @param url The page's url.
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 093756d..b767f11 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -22,7 +22,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
-import android.provider.Checkin;
+import android.util.EventLog;
import java.lang.SecurityException;
import java.util.Locale;
@@ -120,6 +120,21 @@ public class WebSettings {
LOW
}
+ /**
+ * The plugin state effects how plugins are treated on a page. ON means
+ * that any object will be loaded even if a plugin does not exist to handle
+ * the content. ON_DEMAND means that if there is a plugin installed that
+ * can handle the content, a placeholder is shown until the user clicks on
+ * the placeholder. Once clicked, the plugin will be enabled on the page.
+ * OFF means that all plugins will be turned off and any fallback content
+ * will be used.
+ */
+ public enum PluginState {
+ ON,
+ ON_DEMAND,
+ OFF
+ }
+
// WebView associated with this WebSettings.
private WebView mWebView;
// BrowserFrame used to access the native frame pointer.
@@ -152,11 +167,12 @@ public class WebSettings {
private int mMinimumLogicalFontSize = 8;
private int mDefaultFontSize = 16;
private int mDefaultFixedFontSize = 13;
+ private int mPageCacheCapacity = 0;
private boolean mLoadsImagesAutomatically = true;
private boolean mBlockNetworkImage = false;
private boolean mBlockNetworkLoads;
private boolean mJavaScriptEnabled = false;
- private boolean mPluginsEnabled = false;
+ private PluginState mPluginState = PluginState.OFF;
private boolean mJavaScriptCanOpenWindowsAutomatically = false;
private boolean mUseDoubleTree = false;
private boolean mUseWideViewport = false;
@@ -172,6 +188,9 @@ public class WebSettings {
private long mAppCacheMaxSize = Long.MAX_VALUE;
private String mAppCachePath = "";
private String mDatabasePath = "";
+ // The WebCore DatabaseTracker only allows the database path to be set
+ // once. Keep track of when the path has been set.
+ private boolean mDatabasePathHasBeenSet = false;
private String mGeolocationDatabasePath = "";
// Don't need to synchronize the get/set methods as they
// are basic types, also none of these values are used in
@@ -500,8 +519,8 @@ public class WebSettings {
*/
public synchronized void setTextSize(TextSize t) {
if (WebView.mLogEvent && mTextSize != t ) {
- Checkin.updateStats(mContext.getContentResolver(),
- Checkin.Stats.Tag.BROWSER_TEXT_SIZE_CHANGE, 1, 0.0);
+ EventLog.writeEvent(EventLogTags.BROWSER_TEXT_SIZE_CHANGE,
+ mTextSize.value, t.value);
}
mTextSize = t;
postSync();
@@ -881,6 +900,20 @@ public class WebSettings {
}
/**
+ * Set the number of pages cached by the WebKit for the history navigation.
+ * @param size A non-negative integer between 0 (no cache) and 20 (max).
+ * @hide
+ */
+ public synchronized void setPageCacheCapacity(int size) {
+ if (size < 0) size = 0;
+ if (size > 20) size = 20;
+ if (mPageCacheCapacity != size) {
+ mPageCacheCapacity = size;
+ postSync();
+ }
+ }
+
+ /**
* Tell the WebView to load image resources automatically.
* @param flag True if the WebView should load images automatically.
*/
@@ -901,9 +934,12 @@ public class WebSettings {
}
/**
- * Tell the WebView to block network image. This is only checked when
- * getLoadsImagesAutomatically() is true.
- * @param flag True if the WebView should block network image
+ * Tell the WebView to block network images. This is only checked when
+ * {@link #getLoadsImagesAutomatically} is true. If you set the value to
+ * false, images will automatically be loaded. Use this api to reduce
+ * bandwidth only. Use {@link #setBlockNetworkLoads} if possible.
+ * @param flag True if the WebView should block network images.
+ * @see #setBlockNetworkLoads
*/
public synchronized void setBlockNetworkImage(boolean flag) {
if (mBlockNetworkImage != flag) {
@@ -913,17 +949,21 @@ public class WebSettings {
}
/**
- * Return true if the WebView will block network image. The default is false.
- * @return True if the WebView blocks network image.
+ * Return true if the WebView will block network images. The default is
+ * false.
+ * @return True if the WebView blocks network images.
*/
public synchronized boolean getBlockNetworkImage() {
return mBlockNetworkImage;
}
/**
- * @hide
- * Tell the WebView to block all network load requests.
- * @param flag True if the WebView should block all network loads
+ * Tell the WebView to block all network load requests. If you set the
+ * value to false, you must call {@link android.webkit.WebView#reload} to
+ * fetch remote resources. This flag supercedes the value passed to
+ * {@link #setBlockNetworkImage}.
+ * @param flag True if the WebView should block all network loads.
+ * @see android.webkit.WebView#reload
*/
public synchronized void setBlockNetworkLoads(boolean flag) {
if (mBlockNetworkLoads != flag) {
@@ -933,9 +973,8 @@ public class WebSettings {
}
/**
- * @hide
- * Return true if the WebView will block all network loads.
- * The default is false.
+ * Return true if the WebView will block all network loads. The default is
+ * false.
* @return True if the WebView blocks all network loads.
*/
public synchronized boolean getBlockNetworkLoads() {
@@ -969,10 +1008,23 @@ public class WebSettings {
/**
* Tell the WebView to enable plugins.
* @param flag True if the WebView should load plugins.
+ * @deprecated This method has been deprecated in favor of
+ * {@link #setPluginState}
*/
public synchronized void setPluginsEnabled(boolean flag) {
- if (mPluginsEnabled != flag) {
- mPluginsEnabled = flag;
+ setPluginState(PluginState.ON);
+ }
+
+ /**
+ * Tell the WebView to enable, disable, or have plugins on demand. On
+ * demand mode means that if a plugin exists that can handle the embedded
+ * content, a placeholder icon will be shown instead of the plugin. When
+ * the placeholder is clicked, the plugin will be enabled.
+ * @param state One of the PluginState values.
+ */
+ public synchronized void setPluginState(PluginState state) {
+ if (mPluginState != state) {
+ mPluginState = state;
postSync();
}
}
@@ -985,13 +1037,15 @@ public class WebSettings {
/**
* Set the path to where database storage API databases should be saved.
+ * Nota that the WebCore Database Tracker only allows the path to be set once.
* This will update WebCore when the Sync runs in the C++ side.
* @param databasePath String path to the directory where databases should
* be saved. May be the empty string but should never be null.
*/
public synchronized void setDatabasePath(String databasePath) {
- if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+ if (databasePath != null && !mDatabasePathHasBeenSet) {
mDatabasePath = databasePath;
+ mDatabasePathHasBeenSet = true;
postSync();
}
}
@@ -1004,7 +1058,8 @@ public class WebSettings {
* should never be null.
*/
public synchronized void setGeolocationDatabasePath(String databasePath) {
- if (databasePath != null && !databasePath.equals(mDatabasePath)) {
+ if (databasePath != null
+ && !databasePath.equals(mGeolocationDatabasePath)) {
mGeolocationDatabasePath = databasePath;
postSync();
}
@@ -1131,9 +1186,18 @@ public class WebSettings {
/**
* Return true if plugins are enabled.
* @return True if plugins are enabled.
+ * @deprecated This method has been replaced by {@link #getPluginState}
*/
public synchronized boolean getPluginsEnabled() {
- return mPluginsEnabled;
+ return mPluginState == PluginState.ON;
+ }
+
+ /**
+ * Return the current plugin state.
+ * @return A value corresponding to the enum PluginState.
+ */
+ public synchronized PluginState getPluginState() {
+ return mPluginState;
}
/**
@@ -1339,8 +1403,6 @@ public class WebSettings {
junit.framework.Assert.assertTrue(frame.mNativeFrame != 0);
}
- GoogleLocationSettingManager.getInstance().start(mContext);
-
SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE,
Context.MODE_PRIVATE);
if (mDoubleTapToastCount > 0) {
@@ -1357,7 +1419,6 @@ public class WebSettings {
*/
/*package*/
synchronized void onDestroyed() {
- GoogleLocationSettingManager.getInstance().stop();
}
private int pin(int size) {
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index a182287..5345879 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -146,7 +146,7 @@ public final class WebStorage {
* @hide
* Message handler, webcore side
*/
- public void createHandler() {
+ public synchronized void createHandler() {
if (mHandler == null) {
mHandler = new Handler() {
@Override
@@ -340,9 +340,18 @@ public final class WebStorage {
}
/**
+ * Sets the maximum size of the ApplicationCache.
+ * This should only ever be called on the WebKit thread.
+ * @hide Pending API council approval
+ */
+ public void setAppCacheMaximumSize(long size) {
+ nativeSetAppCacheMaximumSize(size);
+ }
+
+ /**
* Utility function to send a message to our handler
*/
- private void postMessage(Message msg) {
+ private synchronized void postMessage(Message msg) {
if (mHandler != null) {
mHandler.sendMessage(msg);
}
@@ -389,8 +398,8 @@ public final class WebStorage {
mOrigins = new HashMap<String, Origin>();
for (String origin : tmp) {
Origin website = new Origin(origin,
- nativeGetUsageForOrigin(origin),
- nativeGetQuotaForOrigin(origin));
+ nativeGetQuotaForOrigin(origin),
+ nativeGetUsageForOrigin(origin));
mOrigins.put(origin, website);
}
}
@@ -402,4 +411,5 @@ public final class WebStorage {
private static native void nativeSetQuotaForOrigin(String origin, long quota);
private static native void nativeDeleteOrigin(String origin);
private static native void nativeDeleteAllData();
+ private static native void nativeSetAppCacheMaximumSize(long size);
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index e0d41c2..19abec1 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -84,14 +84,22 @@ import java.util.ArrayList;
// True if the most recent drag event has caused either the TextView to
// scroll or the web page to scroll. Gets reset after a touch down.
private boolean mScrolled;
- // Gets set to true when the the IME jumps to the next textfield. When this
- // happens, the next time the user hits a key it is okay for the focus
- // pointer to not match the WebTextView's node pointer
- boolean mOkayForFocusNotToMatch;
// Whether or not a selection change was generated from webkit. If it was,
// we do not need to pass the selection back to webkit.
private boolean mFromWebKit;
+ // Whether or not a selection change was generated from the WebTextView
+ // gaining focus. If it is, we do not want to pass it to webkit. This
+ // selection comes from the MovementMethod, but we behave differently. If
+ // WebTextView gained focus from a touch, webkit will determine the
+ // selection.
+ private boolean mFromFocusChange;
+ // Whether or not a selection change was generated from setInputType. We
+ // do not want to pass this change to webkit.
+ private boolean mFromSetInputType;
private boolean mGotTouchDown;
+ // Keep track of whether a long press has happened. Only meaningful after
+ // an ACTION_DOWN MotionEvent
+ private boolean mHasPerformedLongClick;
private boolean mInSetTextAndKeepSelection;
// Array to store the final character added in onTextChanged, so that its
// KeyEvents may be determined.
@@ -108,7 +116,7 @@ import java.util.ArrayList;
* @param webView The WebView that created this.
*/
/* package */ WebTextView(Context context, WebView webView) {
- super(context);
+ super(context, null, com.android.internal.R.attr.webTextViewStyle);
mWebView = webView;
mMaxLength = -1;
}
@@ -136,20 +144,13 @@ import java.util.ArrayList;
isArrowKey = true;
break;
}
- if (!isArrowKey && !mOkayForFocusNotToMatch
- && mWebView.nativeFocusNodePointer() != mNodePointer) {
- mWebView.nativeClearCursor();
- // Do not call remove() here, which hides the soft keyboard. If
- // the soft keyboard is being displayed, the user will still want
- // it there.
- mWebView.removeView(this);
- mWebView.requestFocus();
- return mWebView.dispatchKeyEvent(event);
- }
- // After a jump to next textfield and the first key press, the cursor
- // and focus will once again match, so reset this value.
- mOkayForFocusNotToMatch = false;
+ if (KeyEvent.KEYCODE_TAB == keyCode) {
+ if (down) {
+ onEditorAction(EditorInfo.IME_ACTION_NEXT);
+ }
+ return true;
+ }
Spannable text = (Spannable) getText();
int oldLength = text.length();
// Normally the delete key's dom events are sent via onTextChanged.
@@ -185,7 +186,7 @@ import java.util.ArrayList;
}
// Center key should be passed to a potential onClick
if (!down) {
- mWebView.shortPressOnTextField();
+ mWebView.centerKeyPressOnTextField();
}
// Pass to super to handle longpress.
return super.dispatchKeyEvent(event);
@@ -300,23 +301,42 @@ import java.util.ArrayList;
}
@Override
+ protected void onDraw(Canvas canvas) {
+ // onDraw should only be called for password fields. If WebTextView is
+ // still drawing, but is no longer corresponding to a password field,
+ // remove it.
+ if (mWebView == null || !mWebView.nativeFocusCandidateIsPassword()
+ || !isSameTextField(mWebView.nativeFocusCandidatePointer())) {
+ // Although calling remove() would seem to make more sense here,
+ // changing it to not be a password field will make it not draw.
+ // Other code will make sure that it is removed completely, but this
+ // way the user will not see it.
+ setInPassword(false);
+ } else {
+ super.onDraw(canvas);
+ }
+ }
+
+ @Override
public void onEditorAction(int actionCode) {
switch (actionCode) {
case EditorInfo.IME_ACTION_NEXT:
- mWebView.nativeMoveCursorToNextTextInput();
- // Preemptively rebuild the WebTextView, so that the action will
- // be set properly.
- mWebView.rebuildWebTextView();
- // Since the cursor will no longer be in the same place as the
- // focus, set the focus controller back to inactive
- mWebView.setFocusControllerInactive();
- mWebView.invalidate();
- mOkayForFocusNotToMatch = true;
+ if (mWebView.nativeMoveCursorToNextTextInput()) {
+ // Since the cursor will no longer be in the same place as the
+ // focus, set the focus controller back to inactive
+ mWebView.setFocusControllerInactive();
+ // Preemptively rebuild the WebTextView, so that the action will
+ // be set properly.
+ mWebView.rebuildWebTextView();
+ setDefaultSelection();
+ mWebView.invalidate();
+ }
break;
case EditorInfo.IME_ACTION_DONE:
super.onEditorAction(actionCode);
break;
case EditorInfo.IME_ACTION_GO:
+ case EditorInfo.IME_ACTION_SEARCH:
// Send an enter and hide the soft keyboard
InputMethodManager.getInstance(mContext)
.hideSoftInputFromWindow(getWindowToken(), 0);
@@ -331,7 +351,16 @@ import java.util.ArrayList;
}
@Override
+ protected void onFocusChanged(boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ mFromFocusChange = true;
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ mFromFocusChange = false;
+ }
+
+ @Override
protected void onSelectionChanged(int selStart, int selEnd) {
+ if (mInSetTextAndKeepSelection) return;
// This code is copied from TextView.onDraw(). That code does not get
// executed, however, because the WebTextView does not draw, allowing
// webkit's drawing to show through.
@@ -342,7 +371,8 @@ import java.util.ArrayList;
int candEnd = EditableInputConnection.getComposingSpanEnd(sp);
imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
}
- if (!mFromWebKit && mWebView != null) {
+ if (!mFromWebKit && !mFromFocusChange && !mFromSetInputType
+ && mWebView != null) {
if (DebugFlags.WEB_TEXT_VIEW) {
Log.v(LOGTAG, "onSelectionChanged selStart=" + selStart
+ " selEnd=" + selEnd);
@@ -365,11 +395,17 @@ import java.util.ArrayList;
return;
}
mPreChange = postChange;
- // This was simply a delete or a cut, so just delete the selection.
- if (before > 0 && 0 == count) {
- mWebView.deleteSelection(start, start + before);
- // For this and all changes to the text, update our cache
- updateCachedTextfield();
+ if (0 == count) {
+ if (before > 0) {
+ // This was simply a delete or a cut, so just delete the
+ // selection.
+ mWebView.deleteSelection(start, start + before);
+ // For this and all changes to the text, update our cache
+ updateCachedTextfield();
+ }
+ // before should never be negative, so whether it was a cut
+ // (handled above), or before is 0, in which case nothing has
+ // changed, we should return.
return;
}
// Find the last character being replaced. If it can be represented by
@@ -426,8 +462,13 @@ import java.util.ArrayList;
mDragSent = false;
mScrolled = false;
mGotTouchDown = true;
+ mHasPerformedLongClick = false;
break;
case MotionEvent.ACTION_MOVE:
+ if (mHasPerformedLongClick) {
+ mGotTouchDown = false;
+ return false;
+ }
int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
Spannable buffer = getText();
int initialScrollX = Touch.getInitialScrollX(this, buffer);
@@ -451,6 +492,7 @@ import java.util.ArrayList;
mScrollX / maxScrollX : 0, mScrollY);
}
mScrolled = true;
+ cancelLongPress();
return true;
}
if (Math.abs((int) event.getX() - mDragStartX) < slop
@@ -477,6 +519,10 @@ import java.util.ArrayList;
return false;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ if (mHasPerformedLongClick) {
+ mGotTouchDown = false;
+ return false;
+ }
if (!mScrolled) {
// If the page scrolled, or the TextView scrolled, we do not
// want to change the selection
@@ -520,6 +566,12 @@ import java.util.ArrayList;
return false;
}
+ @Override
+ public boolean performLongClick() {
+ mHasPerformedLongClick = true;
+ return super.performLongClick();
+ }
+
/**
* Remove this WebTextView from its host WebView, and return
* focus to the host.
@@ -552,7 +604,8 @@ import java.util.ArrayList;
*/
public void setAdapterCustom(AutoCompleteAdapter adapter) {
if (adapter != null) {
- setInputType(EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+ setInputType(getInputType()
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE);
adapter.setTextView(this);
}
super.setAdapter(adapter);
@@ -591,6 +644,32 @@ import java.util.ArrayList;
}
/**
+ * Sets the selection when the user clicks on a textfield or textarea with
+ * the trackball or center key, or starts typing into it without clicking on
+ * it.
+ */
+ /* package */ void setDefaultSelection() {
+ Spannable text = (Spannable) getText();
+ int selection = mSingle ? text.length() : 0;
+ if (Selection.getSelectionStart(text) == selection
+ && Selection.getSelectionEnd(text) == selection) {
+ // The selection of the UI copy is set correctly, but the
+ // WebTextView still needs to inform the webkit thread to set the
+ // selection. Normally that is done in onSelectionChanged, but
+ // onSelectionChanged will not be called because the UI copy is not
+ // changing. (This can happen when the WebTextView takes focus.
+ // That onSelectionChanged was blocked because the selection set
+ // when focusing is not necessarily the desirable selection for
+ // WebTextView.)
+ if (mWebView != null) {
+ mWebView.setSelection(selection, selection);
+ }
+ } else {
+ Selection.setSelection(text, selection, selection);
+ }
+ }
+
+ /**
* Determine whether to use the system-wide password disguising method,
* or to use none.
* @param inPassword True if the textfield is a password field.
@@ -660,7 +739,14 @@ import java.util.ArrayList;
setTextColor(Color.BLACK);
}
- /* package */ void setMaxLength(int maxLength) {
+ @Override
+ public void setInputType(int type) {
+ mFromSetInputType = true;
+ super.setInputType(type);
+ mFromSetInputType = false;
+ }
+
+ private void setMaxLength(int maxLength) {
mMaxLength = maxLength;
if (-1 == maxLength) {
setFilters(NO_FILTERS);
@@ -708,7 +794,6 @@ import java.util.ArrayList;
// Set up a measure spec so a layout can always be recreated.
mWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
mHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
- requestFocus();
}
/**
@@ -725,68 +810,6 @@ import java.util.ArrayList;
}
/**
- * Set whether this is a single-line textfield or a multi-line textarea.
- * Textfields scroll horizontally, and do not handle the enter key.
- * Textareas behave oppositely.
- * Do NOT call this after calling setInPassword(true). This will result in
- * removing the password input type.
- */
- public void setSingleLine(boolean single) {
- int inputType = EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
- if (single) {
- int action = mWebView.nativeTextFieldAction();
- switch (action) {
- // Keep in sync with CachedRoot::ImeAction
- case 0: // NEXT
- setImeOptions(EditorInfo.IME_ACTION_NEXT);
- break;
- case 1: // GO
- setImeOptions(EditorInfo.IME_ACTION_GO);
- break;
- case -1: // FAILURE
- case 2: // DONE
- setImeOptions(EditorInfo.IME_ACTION_DONE);
- break;
- }
- } else {
- inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
- | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
- | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
- setImeOptions(EditorInfo.IME_ACTION_NONE);
- }
- mSingle = single;
- setHorizontallyScrolling(single);
- setInputType(inputType);
- }
-
- /**
- * Set the text for this WebTextView, and set the selection to (start, end)
- * @param text Text to go into this WebTextView.
- * @param start Beginning of the selection.
- * @param end End of the selection.
- */
- /* package */ void setText(CharSequence text, int start, int end) {
- mPreChange = text.toString();
- setText(text);
- Spannable span = (Spannable) getText();
- int length = span.length();
- if (end > length) {
- end = length;
- }
- if (start < 0) {
- start = 0;
- } else if (start > length) {
- start = length;
- }
- if (DebugFlags.WEB_TEXT_VIEW) {
- Log.v(LOGTAG, "setText start=" + start
- + " end=" + end);
- }
- Selection.setSelection(span, start, end);
- }
-
- /**
* Set the text to the new string, but use the old selection, making sure
* to keep it within the new string.
* @param text The new text to place in the textfield.
@@ -794,13 +817,103 @@ import java.util.ArrayList;
/* package */ void setTextAndKeepSelection(String text) {
mPreChange = text.toString();
Editable edit = (Editable) getText();
+ int selStart = Selection.getSelectionStart(edit);
+ int selEnd = Selection.getSelectionEnd(edit);
mInSetTextAndKeepSelection = true;
edit.replace(0, edit.length(), text);
+ int newLength = edit.length();
+ if (selStart > newLength) selStart = newLength;
+ if (selEnd > newLength) selEnd = newLength;
+ Selection.setSelection(edit, selStart, selEnd);
mInSetTextAndKeepSelection = false;
updateCachedTextfield();
}
/**
+ * Called by WebView.rebuildWebTextView(). Based on the type of the <input>
+ * element, set up the WebTextView, its InputType, and IME Options properly.
+ * @param type int corresponding to enum "type" defined in WebView.cpp.
+ * Does not correspond to HTMLInputElement::InputType so this
+ * is unaffected if that changes, and also because that has no
+ * type corresponding to textarea (which is its own tag).
+ */
+ /* package */ void setType(int type) {
+ if (mWebView == null) return;
+ boolean single = true;
+ boolean inPassword = false;
+ int maxLength = -1;
+ int inputType = EditorInfo.TYPE_CLASS_TEXT;
+ if (mWebView.nativeFocusCandidateHasNextTextfield()) {
+ inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ }
+ int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
+ | EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ switch (type) {
+ case 0: // NORMAL_TEXT_FIELD
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case 1: // TEXT_AREA
+ single = false;
+ inputType = EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
+ | EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT;
+ imeOptions |= EditorInfo.IME_ACTION_NONE;
+ break;
+ case 2: // PASSWORD
+ inPassword = true;
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case 3: // SEARCH
+ imeOptions |= EditorInfo.IME_ACTION_SEARCH;
+ break;
+ case 4: // EMAIL
+ // TYPE_TEXT_VARIATION_WEB_EDIT_TEXT prevents EMAIL_ADDRESS
+ // from working, so exclude it for now.
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ case 5: // NUMBER
+ inputType |= EditorInfo.TYPE_CLASS_NUMBER;
+ // 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 6: // TELEPHONE
+ inputType |= EditorInfo.TYPE_CLASS_PHONE;
+ imeOptions |= EditorInfo.IME_ACTION_NEXT;
+ break;
+ case 7: // URL
+ // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
+ // exclude it for now.
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ default:
+ imeOptions |= EditorInfo.IME_ACTION_GO;
+ break;
+ }
+ setHint(null);
+ if (single) {
+ mWebView.requestLabel(mWebView.nativeFocusCandidateFramePointer(),
+ mNodePointer);
+ maxLength = mWebView.nativeFocusCandidateMaxLength();
+ if (type != 2 /* PASSWORD */) {
+ String name = mWebView.nativeFocusCandidateName();
+ if (name != null && name.length() > 0) {
+ mWebView.requestFormData(name, mNodePointer);
+ }
+ }
+ }
+ mSingle = single;
+ setMaxLength(maxLength);
+ setHorizontallyScrolling(single);
+ setInputType(inputType);
+ setImeOptions(imeOptions);
+ setInPassword(inPassword);
+ AutoCompleteAdapter adapter = null;
+ setAdapterCustom(adapter);
+ }
+
+ /**
* Update the cache to reflect the current text.
*/
/* package */ void updateCachedTextfield() {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c5c14d3..f921caa 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.annotation.Widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -24,21 +25,27 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Interpolator;
+import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
+import android.graphics.Shader;
import android.graphics.drawable.Drawable;
-import android.net.http.SslCertificate;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.provider.Checkin;
import android.text.IClipboard;
import android.text.Selection;
import android.text.Spannable;
@@ -56,17 +63,21 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AlphaAnimation;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebTextView.AutoCompleteAdapter;
import android.webkit.WebViewCore.EventHub;
+import android.webkit.WebViewCore.TouchEventData;
import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
@@ -81,8 +92,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+
+import junit.framework.Assert;
/**
* <p>A View that displays web pages. This class is the basis upon which you
@@ -196,6 +211,7 @@ import java.util.Map;
* changes, and then just leave the WebView alone. It'll automatically
* re-orient itself as appropriate.</p>
*/
+@Widget
public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
ViewGroup.OnHierarchyChangeListener {
@@ -301,6 +317,9 @@ public class WebView extends AbsoluteLayout
// Used by WebViewCore to create child views.
/* package */ final ViewManager mViewManager;
+ // Used to display in full screen mode
+ PluginFullScreenHolder mFullScreenHolder;
+
/**
* Position of the last touch event.
*/
@@ -331,6 +350,7 @@ public class WebView extends AbsoluteLayout
* choice. Maybe make this in the buildspec later.
*/
private static final int TOUCH_SENT_INTERVAL = 50;
+ private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
/**
* Helper class to get velocity for fling
@@ -358,13 +378,29 @@ public class WebView extends AbsoluteLayout
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
- // Whether to prevent drag during touch. The initial value depends on
- // mForwardTouchEvents. If WebCore wants touch events, we assume it will
- // take control of touch events unless it says no for touch down event.
- private static final int PREVENT_DRAG_NO = 0;
- private static final int PREVENT_DRAG_MAYBE_YES = 1;
- private static final int PREVENT_DRAG_YES = 2;
- private int mPreventDrag = PREVENT_DRAG_NO;
+ // Whether to prevent default during touch. The initial value depends on
+ // mForwardTouchEvents. If WebCore wants all the touch events, it says yes
+ // for touch down. Otherwise UI will wait for the answer of the first
+ // confirmed move before taking over the control.
+ private static final int PREVENT_DEFAULT_NO = 0;
+ private static final int PREVENT_DEFAULT_MAYBE_YES = 1;
+ private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2;
+ private static final int PREVENT_DEFAULT_YES = 3;
+ private static final int PREVENT_DEFAULT_IGNORE = 4;
+ private int mPreventDefault = PREVENT_DEFAULT_IGNORE;
+
+ // true when the touch movement exceeds the slop
+ private boolean mConfirmMove;
+
+ // if true, touch events will be first processed by WebCore, if prevent
+ // default is not set, the UI will continue handle them.
+ private boolean mDeferTouchProcess;
+
+ // to avoid interfering with the current touch events, track them
+ // separately. Currently no snapping or fling in the deferred process mode
+ private int mDeferTouchMode = TOUCH_DONE_MODE;
+ private float mLastDeferTouchX;
+ private float mLastDeferTouchY;
// To keep track of whether the current drag was initiated by a WebTextView,
// so that we know not to hide the cursor
@@ -376,6 +412,10 @@ public class WebView extends AbsoluteLayout
// true if onPause has been called (and not onResume)
private boolean mIsPaused;
+ // true if, during a transition to a new page, we're delaying
+ // deleting a root layer until there's something to draw of the new page.
+ private boolean mDelayedDeleteRootLayer;
+
/**
* Customizable constant
*/
@@ -395,6 +435,8 @@ public class WebView extends AbsoluteLayout
private static final int LONG_PRESS_TIMEOUT = 1000;
// needed to avoid flinging after a pause of no movement
private static final int MIN_FLING_TIME = 250;
+ // draw unfiltered after drag is held without movement
+ private static final int MOTIONLESS_TIME = 100;
// The time that the Zoom Controls are visible before fading away
private static final long ZOOM_CONTROLS_TIMEOUT =
ViewConfiguration.getZoomControlsTimeout();
@@ -432,20 +474,23 @@ public class WebView extends AbsoluteLayout
private Scroller mScroller;
private boolean mWrapContent;
+ private static final int MOTIONLESS_FALSE = 0;
+ private static final int MOTIONLESS_PENDING = 1;
+ private static final int MOTIONLESS_TRUE = 2;
+ private static final int MOTIONLESS_IGNORE = 3;
+ private int mHeldMotionless;
// whether support multi-touch
private boolean mSupportMultiTouch;
// use the framework's ScaleGestureDetector to handle multi-touch
private ScaleGestureDetector mScaleDetector;
- // minimum scale change during multi-touch zoom
- private static float PREVIEW_SCALE_INCREMENT = 0.01f;
// the anchor point in the document space where VIEW_SIZE_CHANGED should
// apply to
private int mAnchorX;
private int mAnchorY;
- /**
+ /*
* Private message ids
*/
private static final int REMEMBER_PASSWORD = 1;
@@ -454,63 +499,109 @@ public class WebView extends AbsoluteLayout
private static final int SWITCH_TO_LONGPRESS = 4;
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
- private static final int RESUME_WEBCORE_UPDATE = 7;
+ private static final int RESUME_WEBCORE_PRIORITY = 7;
+ private static final int DRAG_HELD_MOTIONLESS = 8;
+ private static final int AWAKEN_SCROLL_BARS = 9;
+ private static final int PREVENT_DEFAULT_TIMEOUT = 10;
+ private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
+ private static final int LAST_PRIVATE_MSG_ID = PREVENT_DEFAULT_TIMEOUT;
+
+ /*
+ * Package message ids
+ */
//! arg1=x, arg2=y
- static final int SCROLL_TO_MSG_ID = 10;
- static final int SCROLL_BY_MSG_ID = 11;
+ static final int SCROLL_TO_MSG_ID = 101;
+ static final int SCROLL_BY_MSG_ID = 102;
//! arg1=x, arg2=y
- static final int SPAWN_SCROLL_TO_MSG_ID = 12;
+ static final int SPAWN_SCROLL_TO_MSG_ID = 103;
//! arg1=x, arg2=y
- static final int SYNC_SCROLL_TO_MSG_ID = 13;
- static final int NEW_PICTURE_MSG_ID = 14;
- static final int UPDATE_TEXT_ENTRY_MSG_ID = 15;
- static final int WEBCORE_INITIALIZED_MSG_ID = 16;
- static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17;
- static final int UPDATE_ZOOM_RANGE = 18;
- static final int MOVE_OUT_OF_PLUGIN = 19;
- static final int CLEAR_TEXT_ENTRY = 20;
- static final int UPDATE_TEXT_SELECTION_MSG_ID = 21;
- static final int UPDATE_CLIPBOARD = 22;
- static final int LONG_PRESS_CENTER = 23;
- static final int PREVENT_TOUCH_ID = 24;
- static final int WEBCORE_NEED_TOUCH_EVENTS = 25;
+ static final int SYNC_SCROLL_TO_MSG_ID = 104;
+ static final int NEW_PICTURE_MSG_ID = 105;
+ static final int UPDATE_TEXT_ENTRY_MSG_ID = 106;
+ static final int WEBCORE_INITIALIZED_MSG_ID = 107;
+ static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108;
+ static final int UPDATE_ZOOM_RANGE = 109;
+ static final int MOVE_OUT_OF_PLUGIN = 110;
+ static final int CLEAR_TEXT_ENTRY = 111;
+ static final int UPDATE_TEXT_SELECTION_MSG_ID = 112;
+ static final int SHOW_RECT_MSG_ID = 113;
+ static final int LONG_PRESS_CENTER = 114;
+ static final int PREVENT_TOUCH_ID = 115;
+ static final int WEBCORE_NEED_TOUCH_EVENTS = 116;
// obj=Rect in doc coordinates
- static final int INVAL_RECT_MSG_ID = 26;
- static final int REQUEST_KEYBOARD = 27;
- static final int SHOW_RECT_MSG_ID = 28;
-
- static final String[] HandlerDebugString = {
+ static final int INVAL_RECT_MSG_ID = 117;
+ static final int REQUEST_KEYBOARD = 118;
+ static final int DO_MOTION_UP = 119;
+ static final int SHOW_FULLSCREEN = 120;
+ static final int HIDE_FULLSCREEN = 121;
+ static final int DOM_FOCUS_CHANGED = 122;
+ static final int IMMEDIATE_REPAINT_MSG_ID = 123;
+ static final int SET_ROOT_LAYER_MSG_ID = 124;
+ static final int RETURN_LABEL = 125;
+ static final int FIND_AGAIN = 126;
+ static final int CENTER_FIT_RECT = 127;
+ static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128;
+ static final int SET_SCROLLBAR_MODES = 129;
+
+ private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
+ private static final int LAST_PACKAGE_MSG_ID = SET_SCROLLBAR_MODES;
+
+ static final String[] HandlerPrivateDebugString = {
"REMEMBER_PASSWORD", // = 1;
"NEVER_REMEMBER_PASSWORD", // = 2;
"SWITCH_TO_SHORTPRESS", // = 3;
"SWITCH_TO_LONGPRESS", // = 4;
"RELEASE_SINGLE_TAP", // = 5;
"REQUEST_FORM_DATA", // = 6;
- "SWITCH_TO_CLICK", // = 7;
- "RESUME_WEBCORE_UPDATE", // = 8;
- "9",
- "SCROLL_TO_MSG_ID", // = 10;
- "SCROLL_BY_MSG_ID", // = 11;
- "SPAWN_SCROLL_TO_MSG_ID", // = 12;
- "SYNC_SCROLL_TO_MSG_ID", // = 13;
- "NEW_PICTURE_MSG_ID", // = 14;
- "UPDATE_TEXT_ENTRY_MSG_ID", // = 15;
- "WEBCORE_INITIALIZED_MSG_ID", // = 16;
- "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17;
- "UPDATE_ZOOM_RANGE", // = 18;
- "MOVE_OUT_OF_PLUGIN", // = 19;
- "CLEAR_TEXT_ENTRY", // = 20;
- "UPDATE_TEXT_SELECTION_MSG_ID", // = 21;
- "UPDATE_CLIPBOARD", // = 22;
- "LONG_PRESS_CENTER", // = 23;
- "PREVENT_TOUCH_ID", // = 24;
- "WEBCORE_NEED_TOUCH_EVENTS", // = 25;
- "INVAL_RECT_MSG_ID", // = 26;
- "REQUEST_KEYBOARD", // = 27;
- "SHOW_RECT_MSG_ID" // = 28;
+ "RESUME_WEBCORE_PRIORITY", // = 7;
+ "DRAG_HELD_MOTIONLESS", // = 8;
+ "AWAKEN_SCROLL_BARS", // = 9;
+ "PREVENT_DEFAULT_TIMEOUT" // = 10;
};
+ static final String[] HandlerPackageDebugString = {
+ "SCROLL_TO_MSG_ID", // = 101;
+ "SCROLL_BY_MSG_ID", // = 102;
+ "SPAWN_SCROLL_TO_MSG_ID", // = 103;
+ "SYNC_SCROLL_TO_MSG_ID", // = 104;
+ "NEW_PICTURE_MSG_ID", // = 105;
+ "UPDATE_TEXT_ENTRY_MSG_ID", // = 106;
+ "WEBCORE_INITIALIZED_MSG_ID", // = 107;
+ "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108;
+ "UPDATE_ZOOM_RANGE", // = 109;
+ "MOVE_OUT_OF_PLUGIN", // = 110;
+ "CLEAR_TEXT_ENTRY", // = 111;
+ "UPDATE_TEXT_SELECTION_MSG_ID", // = 112;
+ "SHOW_RECT_MSG_ID", // = 113;
+ "LONG_PRESS_CENTER", // = 114;
+ "PREVENT_TOUCH_ID", // = 115;
+ "WEBCORE_NEED_TOUCH_EVENTS", // = 116;
+ "INVAL_RECT_MSG_ID", // = 117;
+ "REQUEST_KEYBOARD", // = 118;
+ "DO_MOTION_UP", // = 119;
+ "SHOW_FULLSCREEN", // = 120;
+ "HIDE_FULLSCREEN", // = 121;
+ "DOM_FOCUS_CHANGED", // = 122;
+ "IMMEDIATE_REPAINT_MSG_ID", // = 123;
+ "SET_ROOT_LAYER_MSG_ID", // = 124;
+ "RETURN_LABEL", // = 125;
+ "FIND_AGAIN", // = 126;
+ "CENTER_FIT_RECT", // = 127;
+ "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
+ "SET_SCROLLBAR_MODES" // = 129;
+ };
+
+ // If the site doesn't use the viewport meta tag to specify the viewport,
+ // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
+ static final int DEFAULT_VIEWPORT_WIDTH = 800;
+
+ // normally we try to fit the content to the minimum preferred width
+ // calculated by the Webkit. To avoid the bad behavior when some site's
+ // minimum preferred width keeps growing when changing the viewport width or
+ // the minimum preferred width is huge, an upper limit is needed.
+ static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
+
// default scale limit. Depending on the display density
private static float DEFAULT_MAX_ZOOM_SCALE;
private static float DEFAULT_MIN_ZOOM_SCALE;
@@ -530,14 +621,16 @@ public class WebView extends AbsoluteLayout
// ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
// engadget always have wider mContentWidth no matter what viewport size is.
- int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH;
+ int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
float mTextWrapScale;
// default scale. Depending on the display density.
static int DEFAULT_SCALE_PERCENT;
private float mDefaultScale;
- // set to true temporarily while the zoom control is being dragged
+ private static float MINIMUM_SCALE_INCREMENT = 0.01f;
+
+ // set to true temporarily during ScaleGesture triggered zoom
private boolean mPreviewZoomOnly = false;
// computed scale and inverse, from mZoomWidth.
@@ -555,19 +648,30 @@ public class WebView extends AbsoluteLayout
private boolean mUserScroll = false;
private int mSnapScrollMode = SNAP_NONE;
- private static final int SNAP_NONE = 1;
- private static final int SNAP_X = 2;
- private static final int SNAP_Y = 3;
- private static final int SNAP_X_LOCK = 4;
- private static final int SNAP_Y_LOCK = 5;
+ private static final int SNAP_NONE = 0;
+ private static final int SNAP_LOCK = 1; // not a separate state
+ private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
+ private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
private boolean mSnapPositive;
+ // keep these in sync with their counterparts in WebView.cpp
+ private static final int DRAW_EXTRAS_NONE = 0;
+ private static final int DRAW_EXTRAS_FIND = 1;
+ private static final int DRAW_EXTRAS_SELECTION = 2;
+ private static final int DRAW_EXTRAS_CURSOR_RING = 3;
+
+ // keep this in sync with WebCore:ScrollbarMode in WebKit
+ private static final int SCROLLBAR_AUTO = 0;
+ private static final int SCROLLBAR_ALWAYSOFF = 1;
+ // as we auto fade scrollbar, this is ignored.
+ private static final int SCROLLBAR_ALWAYSON = 2;
+ private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
+ private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
+
// Used to match key downs and key ups
private boolean mGotKeyDown;
/* package */ static boolean mLogEvent = true;
- private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101;
- private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102;
// for event log
private long mLastTouchUpTime = 0;
@@ -672,6 +776,8 @@ public class WebView extends AbsoluteLayout
private ExtendedZoomControls mZoomControls;
private Runnable mZoomControlRunnable;
+ // mZoomButtonsController will be lazy initialized in
+ // getZoomButtonsController() to get better performance.
private ZoomButtonsController mZoomButtonsController;
// These keep track of the center point of the zoom. They are used to
@@ -685,6 +791,9 @@ public class WebView extends AbsoluteLayout
public void onVisibilityChanged(boolean visible) {
if (visible) {
switchOutDrawHistory();
+ // Bring back the hidden zoom controls.
+ mZoomButtonsController.getZoomControls().setVisibility(
+ View.VISIBLE);
updateZoomButtonsEnabled();
}
}
@@ -730,7 +839,7 @@ public class WebView extends AbsoluteLayout
/**
* Construct a new WebView with layout parameters, a default style and a set
* of custom Javscript interfaces to be added to the WebView at initialization
- * time. This guraratees that these interfaces will be available when the JS
+ * time. This guarantees that these interfaces will be available when the JS
* context is initialized.
* @param context A Context object used to access application assets.
* @param attrs An AttributeSet passed to our parent.
@@ -745,24 +854,11 @@ public class WebView extends AbsoluteLayout
init();
mCallbackProxy = new CallbackProxy(context, this);
+ mViewManager = new ViewManager(this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
mDatabase = WebViewDatabase.getInstance(context);
mScroller = new Scroller(context);
- mViewManager = new ViewManager(this);
-
- mZoomButtonsController = new ZoomButtonsController(this);
- mZoomButtonsController.setOnZoomListener(mZoomListener);
- // ZoomButtonsController positions the buttons at the bottom, but in
- // the middle. Change their layout parameters so they appear on the
- // right.
- View controls = mZoomButtonsController.getZoomControls();
- ViewGroup.LayoutParams params = controls.getLayoutParams();
- if (params instanceof FrameLayout.LayoutParams) {
- FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams)
- params;
- frameParams.gravity = Gravity.RIGHT;
- }
updateMultiTouchSupport(context);
}
@@ -780,6 +876,7 @@ public class WebView extends AbsoluteLayout
}
private void updateZoomButtonsEnabled() {
+ if (mZoomButtonsController == null) return;
boolean canZoomIn = mActualScale < mMaxZoomScale;
boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview;
if (!canZoomIn && !canZoomOut) {
@@ -787,9 +884,6 @@ public class WebView extends AbsoluteLayout
// button, if the page cannot zoom
mZoomButtonsController.getZoomControls().setVisibility(View.GONE);
} else {
- // Bring back the hidden zoom controls.
- mZoomButtonsController.getZoomControls()
- .setVisibility(View.VISIBLE);
// Set each one individually, as a page may be able to zoom in
// or out.
mZoomButtonsController.setZoomInEnabled(canZoomIn);
@@ -998,6 +1092,9 @@ public class WebView extends AbsoluteLayout
* Sets the SSL certificate for the main top-level page.
*/
public void setCertificate(SslCertificate certificate) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "setCertificate=" + certificate);
+ }
// here, the certificate can be null (if the site is not secure)
mCertificate = certificate;
}
@@ -1050,16 +1147,18 @@ public class WebView extends AbsoluteLayout
* methods may be called on a WebView after destroy.
*/
public void destroy() {
- clearTextEntry();
+ clearTextEntry(false);
if (mWebViewCore != null) {
// Set the handlers to null before destroying WebViewCore so no
// more messages will be posted.
mCallbackProxy.setWebViewClient(null);
mCallbackProxy.setWebChromeClient(null);
// Tell WebViewCore to destroy itself
- WebViewCore webViewCore = mWebViewCore;
- mWebViewCore = null; // prevent using partial webViewCore
- webViewCore.destroy();
+ synchronized (this) {
+ WebViewCore webViewCore = mWebViewCore;
+ mWebViewCore = null; // prevent using partial webViewCore
+ webViewCore.destroy();
+ }
// Remove any pending messages that might not be serviced yet.
mPrivateHandler.removeCallbacksAndMessages(null);
mCallbackProxy.removeCallbacksAndMessages(null);
@@ -1113,6 +1212,16 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Inform WebView about the current network type.
+ * {@hide}
+ */
+ public void setNetworkType(String type, String subtype) {
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("type", type);
+ map.put("subtype", subtype);
+ mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
+ }
+ /**
* Save the state of this WebView used in
* {@link android.app.Activity#onSaveInstanceState}. Please note that this
* method no longer stores the display data for this WebView. The previous
@@ -1177,31 +1286,57 @@ public class WebView extends AbsoluteLayout
* overwritten with this WebView's picture data.
* @return True if the picture was successfully saved.
*/
- public boolean savePicture(Bundle b, File dest) {
+ public boolean savePicture(Bundle b, final File dest) {
if (dest == null || b == null) {
return false;
}
final Picture p = capturePicture();
- try {
- final FileOutputStream out = new FileOutputStream(dest);
- p.writeToStream(out);
- out.close();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- }
- if (dest.length() > 0) {
- b.putInt("scrollX", mScrollX);
- b.putInt("scrollY", mScrollY);
- b.putFloat("scale", mActualScale);
- b.putFloat("textwrapScale", mTextWrapScale);
- b.putBoolean("overview", mInZoomOverview);
- return true;
- }
- return false;
+ // Use a temporary file while writing to ensure the destination file
+ // contains valid data.
+ final File temp = new File(dest.getPath() + ".writing");
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ FileOutputStream out = new FileOutputStream(temp);
+ p.writeToStream(out);
+ out.close();
+ // Writing the picture succeeded, rename the temporary file
+ // to the destination.
+ temp.renameTo(dest);
+ } catch (Exception e) {
+ // too late to do anything about it.
+ } finally {
+ temp.delete();
+ }
+ }
+ }).start();
+ // now update the bundle
+ b.putInt("scrollX", mScrollX);
+ b.putInt("scrollY", mScrollY);
+ b.putFloat("scale", mActualScale);
+ b.putFloat("textwrapScale", mTextWrapScale);
+ b.putBoolean("overview", mInZoomOverview);
+ return true;
+ }
+
+ private void restoreHistoryPictureFields(Picture p, Bundle b) {
+ int sx = b.getInt("scrollX", 0);
+ int sy = b.getInt("scrollY", 0);
+ float scale = b.getFloat("scale", 1.0f);
+ mDrawHistory = true;
+ mHistoryPicture = p;
+ mScrollX = sx;
+ mScrollY = sy;
+ mHistoryWidth = Math.round(p.getWidth() * scale);
+ mHistoryHeight = Math.round(p.getHeight() * scale);
+ // as getWidth() / getHeight() of the view are not available yet, set up
+ // mActualScale, so that when onSizeChanged() is called, the rest will
+ // be set correctly
+ mActualScale = scale;
+ mInvActualScale = 1 / scale;
+ mTextWrapScale = b.getFloat("textwrapScale", scale);
+ mInZoomOverview = b.getBoolean("overview");
+ invalidate();
}
/**
@@ -1215,41 +1350,35 @@ public class WebView extends AbsoluteLayout
if (src == null || b == null) {
return false;
}
- if (src.exists()) {
- Picture p = null;
- try {
- final FileInputStream in = new FileInputStream(src);
- p = Picture.createFromStream(in);
- in.close();
- } catch (FileNotFoundException e){
- e.printStackTrace();
- } catch (RuntimeException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (p != null) {
- int sx = b.getInt("scrollX", 0);
- int sy = b.getInt("scrollY", 0);
- float scale = b.getFloat("scale", 1.0f);
- mDrawHistory = true;
- mHistoryPicture = p;
- mScrollX = sx;
- mScrollY = sy;
- mHistoryWidth = Math.round(p.getWidth() * scale);
- mHistoryHeight = Math.round(p.getHeight() * scale);
- // as getWidth() / getHeight() of the view are not
- // available yet, set up mActualScale, so that when
- // onSizeChanged() is called, the rest will be set
- // correctly
- mActualScale = scale;
- mTextWrapScale = b.getFloat("textwrapScale", scale);
- mInZoomOverview = b.getBoolean("overview");
- invalidate();
- return true;
- }
+ if (!src.exists()) {
+ return false;
}
- return false;
+ try {
+ final FileInputStream in = new FileInputStream(src);
+ final Bundle copy = new Bundle(b);
+ new Thread(new Runnable() {
+ public void run() {
+ final Picture p = Picture.createFromStream(in);
+ if (p != null) {
+ // Post a runnable on the main thread to update the
+ // history picture fields.
+ mPrivateHandler.post(new Runnable() {
+ public void run() {
+ restoreHistoryPictureFields(p, copy);
+ }
+ });
+ }
+ try {
+ in.close();
+ } catch (Exception e) {
+ // Nothing we can do now.
+ }
+ }
+ }).start();
+ } catch (FileNotFoundException e){
+ e.printStackTrace();
+ }
+ return true;
}
/**
@@ -1314,6 +1443,22 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Load the given url with the extra headers.
+ * @param url The url of the resource to load.
+ * @param extraHeaders The extra headers sent with this url. This should not
+ * include the common headers like "user-agent". If it does, it
+ * will be replaced by the intrinsic value of the WebView.
+ */
+ public void loadUrl(String url, Map<String, String> extraHeaders) {
+ switchOutDrawHistory();
+ WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
+ arg.mUrl = url;
+ arg.mExtraHeaders = extraHeaders;
+ mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
+ clearTextEntry(false);
+ }
+
+ /**
* Load the given url.
* @param url The url of the resource to load.
*/
@@ -1321,9 +1466,7 @@ public class WebView extends AbsoluteLayout
if (url == null) {
return;
}
- switchOutDrawHistory();
- mWebViewCore.sendMessage(EventHub.LOAD_URL, url);
- clearTextEntry();
+ loadUrl(url, null);
}
/**
@@ -1341,7 +1484,7 @@ public class WebView extends AbsoluteLayout
arg.mUrl = url;
arg.mPostData = postData;
mWebViewCore.sendMessage(EventHub.POST_URL, arg);
- clearTextEntry();
+ clearTextEntry(false);
} else {
loadUrl(url);
}
@@ -1351,7 +1494,9 @@ public class WebView extends AbsoluteLayout
* 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.
+ * @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
*/
@@ -1362,10 +1507,8 @@ public class WebView extends AbsoluteLayout
/**
* Load the given data into the WebView, use the provided URL as the base
* URL for the content. The base URL is the URL that represents the page
- * that is loaded through this interface. As such, it is used for the
- * history entry and to resolve any relative URLs. The failUrl is used if
- * browser fails to load the data provided. If it is empty or null, and the
- * load fails, then no history entry is created.
+ * 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
@@ -1380,10 +1523,10 @@ public class WebView extends AbsoluteLayout
* @param mimeType The MIMEType of the data. i.e. text/html. If null,
* defaults to "text/html"
* @param encoding The encoding of the data. i.e. utf-8, us-ascii
- * @param failUrl URL to use if the content fails to load or null.
+ * @param historyUrl URL to use as the history entry. Can be null.
*/
public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding, String failUrl) {
+ String mimeType, String encoding, String historyUrl) {
if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
loadData(data, mimeType, encoding);
@@ -1395,9 +1538,9 @@ public class WebView extends AbsoluteLayout
arg.mData = data;
arg.mMimeType = mimeType;
arg.mEncoding = encoding;
- arg.mFailUrl = failUrl;
+ arg.mHistoryUrl = historyUrl;
mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
- clearTextEntry();
+ clearTextEntry(false);
}
/**
@@ -1414,7 +1557,7 @@ public class WebView extends AbsoluteLayout
* Reload the current url.
*/
public void reload() {
- clearTextEntry();
+ clearTextEntry(false);
switchOutDrawHistory();
mWebViewCore.sendMessage(EventHub.RELOAD);
}
@@ -1493,15 +1636,8 @@ public class WebView extends AbsoluteLayout
}
private void goBackOrForward(int steps, boolean ignoreSnapshot) {
- // every time we go back or forward, we want to reset the
- // WebView certificate:
- // if the new site is secure, we will reload it and get a
- // new certificate set;
- // if the new site is not secure, the certificate must be
- // null, and that will be the case
- mCertificate = null;
if (steps != 0) {
- clearTextEntry();
+ clearTextEntry(false);
mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
ignoreSnapshot ? 1 : 0);
}
@@ -1597,13 +1733,20 @@ public class WebView extends AbsoluteLayout
* Return true if the browser is displaying a TextView for text input.
*/
private boolean inEditingMode() {
- return mWebTextView != null && mWebTextView.getParent() != null
- && mWebTextView.hasFocus();
+ return mWebTextView != null && mWebTextView.getParent() != null;
}
- private void clearTextEntry() {
+ /**
+ * Remove the WebTextView.
+ * @param disableFocusController If true, send a message to webkit
+ * disabling the focus controller, so the caret stops blinking.
+ */
+ private void clearTextEntry(boolean disableFocusController) {
if (inEditingMode()) {
mWebTextView.remove();
+ if (disableFocusController) {
+ setFocusControllerInactive();
+ }
}
}
@@ -1637,9 +1780,9 @@ public class WebView extends AbsoluteLayout
Log.w(LOGTAG, "This WebView doesn't support zoom.");
return;
}
- clearTextEntry();
+ clearTextEntry(false);
if (getSettings().getBuiltInZoomControls()) {
- mZoomButtonsController.setVisible(true);
+ getZoomButtonsController().setVisible(true);
} else {
mPrivateHandler.removeCallbacks(mZoomControlRunnable);
mPrivateHandler.postDelayed(mZoomControlRunnable,
@@ -1711,6 +1854,13 @@ public class WebView extends AbsoluteLayout
return result;
}
+ // Called by JNI when the DOM has changed the focus. Clear the focus so
+ // that new keys will go to the newly focused field
+ private void domChangedFocus() {
+ if (inEditingMode()) {
+ mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget();
+ }
+ }
/**
* Request the href of an anchor element due to getFocusNodePath returning
* "href." If hrefMsg is null, this method returns immediately and does not
@@ -1775,15 +1925,8 @@ public class WebView extends AbsoluteLayout
// Expects y in view coordinates
private int pinLocY(int y) {
- int titleH = getTitleHeight();
- // if the titlebar is still visible, just pin against 0
- if (y <= titleH) {
- return Math.max(y, 0);
- }
- // convert to 0-based coordinate (subtract the title height)
- // pin(), and then add the title height back in
- return pinLoc(y - titleH, getViewHeight(),
- computeVerticalScrollRange()) + titleH;
+ return pinLoc(y, getViewHeightWithTitle(),
+ computeVerticalScrollRange() + getTitleHeight());
}
/**
@@ -1814,7 +1957,7 @@ public class WebView extends AbsoluteLayout
}
if (null != v) {
addView(v, new AbsoluteLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0));
if (mTitleShadow == null) {
mTitleShadow = (Drawable) mContext.getResources().getDrawable(
@@ -1853,6 +1996,23 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Given a x coordinate in view space, convert it to content space.
+ * Returns the result as a float.
+ */
+ private float viewToContentXf(int x) {
+ return x * mInvActualScale;
+ }
+
+ /**
+ * Given a y coordinate in view space, convert it to content space.
+ * Takes into account the height of the title bar if there is one
+ * embedded into the WebView. Returns the result as a float.
+ */
+ private float viewToContentYf(int y) {
+ return (y - getTitleHeight()) * mInvActualScale;
+ }
+
+ /**
* Given a distance in content space, convert it to view space. Note: this
* does not reflect translation, just scaling, so this should not be called
* with coordinates, but should be called for dimensions like width or
@@ -1954,7 +2114,7 @@ public class WebView extends AbsoluteLayout
mScrollX = pinLocX(mScrollX);
mScrollY = pinLocY(mScrollY);
if (oldX != mScrollX || oldY != mScrollY) {
- sendOurVisibleRect();
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
if (!mScroller.isFinished()) {
// We are in the middle of a scroll. Repin the final scroll
@@ -1971,6 +2131,8 @@ public class WebView extends AbsoluteLayout
boolean force) {
if (scale < mMinZoomScale) {
scale = mMinZoomScale;
+ // set mInZoomOverview for non mobile sites
+ if (scale < mDefaultScale) mInZoomOverview = true;
} else if (scale > mMaxZoomScale) {
scale = mMaxZoomScale;
}
@@ -2017,8 +2179,15 @@ public class WebView extends AbsoluteLayout
mScrollY = pinLocY(Math.round(sy));
// update webkit
+ if (oldX != mScrollX || oldY != mScrollY) {
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ } else {
+ // the scroll position is adjusted at the beginning of the
+ // zoom animation. But we want to update the WebKit at the
+ // end of the zoom animation. See comments in onScaleEnd().
+ sendOurVisibleRect();
+ }
sendViewSizeZoom();
- sendOurVisibleRect();
}
}
}
@@ -2069,14 +2238,33 @@ public class WebView extends AbsoluteLayout
// Sets r to be our visible rectangle in content coordinates
private void calcOurContentVisibleRect(Rect r) {
calcOurVisibleRect(r);
- r.left = viewToContentX(r.left);
+ // pin the rect to the bounds of the content
+ r.left = Math.max(viewToContentX(r.left), 0);
// viewToContentY will remove the total height of the title bar. Add
// the visible height back in to account for the fact that if the title
// bar is partially visible, the part of the visible rect which is
// displaying our content is displaced by that amount.
- r.top = viewToContentY(r.top + getVisibleTitleHeight());
- r.right = viewToContentX(r.right);
- r.bottom = viewToContentY(r.bottom);
+ r.top = Math.max(viewToContentY(r.top + getVisibleTitleHeight()), 0);
+ r.right = Math.min(viewToContentX(r.right), mContentWidth);
+ r.bottom = Math.min(viewToContentY(r.bottom), mContentHeight);
+ }
+
+ // Sets r to be our visible rectangle in content coordinates. We use this
+ // method on the native side to compute the position of the fixed layers.
+ // Uses floating coordinates (necessary to correctly place elements when
+ // the scale factor is not 1)
+ private void calcOurContentVisibleRectF(RectF r) {
+ Rect ri = new Rect(0,0,0,0);
+ calcOurVisibleRect(ri);
+ // pin the rect to the bounds of the content
+ r.left = Math.max(viewToContentXf(ri.left), 0.0f);
+ // viewToContentY will remove the total height of the title bar. Add
+ // the visible height back in to account for the fact that if the title
+ // bar is partially visible, the part of the visible rect which is
+ // displaying our content is displaced by that amount.
+ r.top = Math.max(viewToContentYf(ri.top + getVisibleTitleHeight()), 0.0f);
+ r.right = Math.min(viewToContentXf(ri.right), (float)mContentWidth);
+ r.bottom = Math.min(viewToContentYf(ri.bottom), (float)mContentHeight);
}
static class ViewSizeData {
@@ -2135,6 +2323,10 @@ public class WebView extends AbsoluteLayout
protected int computeHorizontalScrollRange() {
if (mDrawHistory) {
return mHistoryWidth;
+ } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
+ && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+ // only honor the scrollbar mode when it is at minimum zoom level
+ return computeHorizontalScrollExtent();
} else {
// to avoid rounding error caused unnecessary scrollbar, use floor
return (int) Math.floor(mContentWidth * mActualScale);
@@ -2145,6 +2337,10 @@ public class WebView extends AbsoluteLayout
protected int computeVerticalScrollRange() {
if (mDrawHistory) {
return mHistoryHeight;
+ } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
+ && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+ // only honor the scrollbar mode when it is at minimum zoom level
+ return computeVerticalScrollExtent();
} else {
// to avoid rounding error caused unnecessary scrollbar, use floor
return (int) Math.floor(mContentHeight * mActualScale);
@@ -2380,20 +2576,34 @@ public class WebView extends AbsoluteLayout
*/
public int findAll(String find) {
if (0 == mNativeClass) return 0; // client isn't initialized
- if (mFindIsUp == false) {
+ int result = find != null ? nativeFindAll(find.toLowerCase(),
+ find.toUpperCase()) : 0;
+ invalidate();
+ mLastFind = find;
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public void setFindIsUp(boolean isUp) {
+ mFindIsUp = isUp;
+ if (isUp) {
recordNewContentSize(mContentWidth, mContentHeight + mFindHeight,
false);
- mFindIsUp = true;
}
- int result = nativeFindAll(find.toLowerCase(), find.toUpperCase());
- invalidate();
- return result;
+ if (0 == mNativeClass) return; // client isn't initialized
+ nativeSetFindIsUp(isUp);
}
// Used to know whether the find dialog is open. Affects whether
// or not we draw the highlights for matches.
private boolean mFindIsUp;
+
private int mFindHeight;
+ // Keep track of the last string sent, so we can search again after an
+ // orientation change or the dismissal of the soft keyboard.
+ private String mLastFind;
/**
* Return the first substring consisting of the address of a physical
@@ -2449,12 +2659,24 @@ public class WebView extends AbsoluteLayout
* Clear the highlighting surrounding text matches created by findAll.
*/
public void clearMatches() {
- if (mFindIsUp) {
- recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
- false);
- mFindIsUp = false;
+ mLastFind = "";
+ if (mNativeClass == 0)
+ return;
+ nativeSetFindIsEmpty();
+ invalidate();
+ }
+
+ /**
+ * @hide
+ */
+ public void notifyFindDialogDismissed() {
+ if (mWebViewCore == null) {
+ return;
}
- nativeSetFindIsDown();
+ clearMatches();
+ setFindIsUp(false);
+ recordNewContentSize(mContentWidth, mContentHeight - mFindHeight,
+ false);
// Now that the dialog has been removed, ensure that we scroll to a
// location that is not beyond the end of the page.
pinScrollTo(mScrollX, mScrollY, false, 0);
@@ -2493,9 +2715,7 @@ public class WebView extends AbsoluteLayout
mScrollY = mScroller.getCurrY();
postInvalidate(); // So we draw again
if (oldX != mScrollX || oldY != mScrollY) {
- // as onScrollChanged() is not called, sendOurVisibleRect()
- // needs to be call explicitly
- sendOurVisibleRect();
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
}
} else {
super.computeScroll();
@@ -2568,6 +2788,41 @@ public class WebView extends AbsoluteLayout
}
}
+ /**
+ * Called by CallbackProxy when the page finishes loading.
+ * @param url The URL of the page which has finished loading.
+ */
+ /* package */ void onPageFinished(String url) {
+ if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
+ // If the user is now on a different page, or has scrolled the page
+ // past the point where the title bar is offscreen, ignore the
+ // scroll request.
+ if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
+ && mScrollX == 0 && mScrollY == 0) {
+ pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
+ SLIDE_TITLE_DURATION);
+ }
+ mPageThatNeedsToSlideTitleBarOffScreen = null;
+ }
+ }
+
+ /**
+ * The URL of a page that sent a message to scroll the title bar off screen.
+ *
+ * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
+ * title bar off the screen. Sometimes, the scroll position is set before
+ * the page finishes loading. Rather than scrolling while the page is still
+ * loading, keep track of the URL and new scroll position so we can perform
+ * the scroll once the page finishes loading.
+ */
+ private String mPageThatNeedsToSlideTitleBarOffScreen;
+
+ /**
+ * The destination Y scroll position to be used when the page finishes
+ * loading. See mPageThatNeedsToSlideTitleBarOffScreen.
+ */
+ private int mYDistanceToSlideTitleOffScreen;
+
// scale from content to view coordinates, and pin
// return true if pin caused the final x/y different than the request cx/cy,
// and a future scroll may reach the request cx/cy after our size has
@@ -2602,8 +2857,18 @@ public class WebView extends AbsoluteLayout
// page, assume this is an attempt to scroll off the title bar, and
// animate the title bar off screen slowly enough that the user can see
// it.
- if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0) {
- pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0
+ && mTitleBar != null) {
+ // FIXME: 100 should be defined somewhere as our max progress.
+ if (getProgress() < 100) {
+ // Wait to scroll the title bar off screen until the page has
+ // finished loading. Keep track of the URL and the destination
+ // Y position
+ mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
+ mYDistanceToSlideTitleOffScreen = vy;
+ } else {
+ pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
+ }
// Since we are animating, we have not yet reached the desired
// scroll position. Do not return true to request another attempt
return false;
@@ -2644,12 +2909,12 @@ public class WebView extends AbsoluteLayout
if (mHeightCanMeasure) {
if (getMeasuredHeight() != contentToViewDimension(mContentHeight)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else if (mWidthCanMeasure) {
if (getMeasuredWidth() != contentToViewDimension(mContentWidth)
- && updateLayout) {
+ || updateLayout) {
requestLayout();
}
} else {
@@ -2669,6 +2934,16 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Gets the WebViewClient
+ * @return the current WebViewClient instance.
+ *
+ *@hide pending API council approval.
+ */
+ public WebViewClient getWebViewClient() {
+ return mCallbackProxy.getWebViewClient();
+ }
+
+ /**
* Register the interface to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current handler.
@@ -2699,6 +2974,25 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Set the back/forward list client. This is an implementation of
+ * WebBackForwardListClient for handling new items and changes in the
+ * history index.
+ * @param client An implementation of WebBackForwardListClient.
+ * {@hide}
+ */
+ public void setWebBackForwardListClient(WebBackForwardListClient client) {
+ mCallbackProxy.setWebBackForwardListClient(client);
+ }
+
+ /**
+ * Gets the WebBackForwardListClient.
+ * {@hide}
+ */
+ public WebBackForwardListClient getWebBackForwardListClient() {
+ return mCallbackProxy.getWebBackForwardListClient();
+ }
+
+ /**
* Set the Picture listener. This is an interface used to receive
* notifications of a new Picture.
* @param listener An implementation of WebView.PictureListener.
@@ -2759,6 +3053,45 @@ public class WebView extends AbsoluteLayout
return mWebViewCore.getSettings();
}
+ /**
+ * Use this method to inform the webview about packages that are installed
+ * in the system. This information will be used by the
+ * navigator.isApplicationInstalled() API.
+ * @param packageNames is a set of package names that are known to be
+ * installed in the system.
+ *
+ * @hide not a public API
+ */
+ public void addPackageNames(Set<String> packageNames) {
+ mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, packageNames);
+ }
+
+ /**
+ * Use this method to inform the webview about single packages that are
+ * installed in the system. This information will be used by the
+ * navigator.isApplicationInstalled() API.
+ * @param packageName is the name of a package that is known to be
+ * installed in the system.
+ *
+ * @hide not a public API
+ */
+ public void addPackageName(String packageName) {
+ mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName);
+ }
+
+ /**
+ * Use this method to inform the webview about packages that are uninstalled
+ * in the system. This information will be used by the
+ * navigator.isApplicationInstalled() API.
+ * @param packageName is the name of a package that has been uninstalled in
+ * the system.
+ *
+ * @hide not a public API
+ */
+ public void removePackageName(String packageName) {
+ mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
+ }
+
/**
* Return the list of currently loaded plugins.
* @return The list of currently loaded plugins.
@@ -2809,8 +3142,8 @@ public class WebView extends AbsoluteLayout
// If mNativeClass is 0, we should not reach here, so we do not
// need to check it again.
nativeRecordButtons(hasFocus() && hasWindowFocus(),
- mTouchMode == TOUCH_SHORTPRESS_START_MODE
- || mTrackballDown || mGotCenterDown, false);
+ mTouchMode == TOUCH_SHORTPRESS_START_MODE
+ || mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
}
@@ -2820,27 +3153,45 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) {
return;
}
+
+ // if both mContentWidth and mContentHeight are 0, it means there is no
+ // valid Picture passed to WebView yet. This can happen when WebView
+ // just starts. Draw the background and return.
+ if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
+ canvas.drawColor(mBackgroundColor);
+ return;
+ }
+
int saveCount = canvas.save();
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
- if (mDragTrackerHandler == null || !mDragTrackerHandler.draw(canvas)) {
+ if (mDragTrackerHandler == null) {
drawContent(canvas);
+ } else {
+ if (!mDragTrackerHandler.draw(canvas)) {
+ // sometimes the tracker doesn't draw, even though its active
+ drawContent(canvas);
+ }
+ if (mDragTrackerHandler.isFinished()) {
+ mDragTrackerHandler = null;
+ }
}
canvas.restoreToCount(saveCount);
// Now draw the shadow.
- if (mTitleBar != null) {
- int y = mScrollY + getVisibleTitleHeight();
+ int titleH = getVisibleTitleHeight();
+ if (mTitleBar != null && titleH == 0) {
int height = (int) (5f * getContext().getResources()
.getDisplayMetrics().density);
- mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(),
- y + height);
+ mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(),
+ mScrollY + height);
mTitleShadow.draw(canvas);
}
if (AUTO_REDRAW_HACK && mAutoRedraw) {
invalidate();
}
+ mWebViewCore.signalRepaintDone();
}
@Override
@@ -2853,12 +3204,13 @@ public class WebView extends AbsoluteLayout
@Override
public boolean performLongClick() {
+ // performLongClick() is the result of a delayed message. If we switch
+ // to windows overview, the WebView will be temporarily removed from the
+ // view system. In that case, do nothing.
+ if (getParent() == null) return false;
if (mNativeClass != 0 && nativeCursorIsTextInput()) {
// Send the click so that the textfield is in focus
- // FIXME: When we start respecting changes to the native textfield's
- // selection, need to make sure that this does not change it.
- mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
- nativeCursorNodePointer());
+ centerKeyPressOnTextField();
rebuildWebTextView();
}
if (inEditingMode()) {
@@ -2881,38 +3233,43 @@ public class WebView extends AbsoluteLayout
*/
private boolean mNeedToAdjustWebTextView;
- // if checkVisibility is false, the WebTextView may trigger a move of
- // WebView to bring itself into the view.
- private void adjustTextView(boolean checkVisibility) {
+ private boolean didUpdateTextViewBounds(boolean allowIntersect) {
Rect contentBounds = nativeFocusCandidateNodeBounds();
Rect vBox = contentToViewRect(contentBounds);
Rect visibleRect = new Rect();
calcOurVisibleRect(visibleRect);
- if (!checkVisibility || visibleRect.contains(vBox)) {
- // As a result of the zoom, the textfield is now on
- // screen. Place the WebTextView in its new place,
- // accounting for our new scroll/zoom values.
- mWebTextView
- .setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- contentToViewDimension(nativeFocusCandidateTextSize()));
+ // If the textfield is on screen, place the WebTextView in
+ // its new place, accounting for our new scroll/zoom values,
+ // and adjust its textsize.
+ if (allowIntersect ? Rect.intersects(visibleRect, vBox)
+ : visibleRect.contains(vBox)) {
mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
vBox.height());
- // If it is a password field, start drawing the
- // WebTextView once again.
- if (nativeFocusCandidateIsPassword()) {
- mWebTextView.setInPassword(true);
- }
+ mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ contentToViewDimension(
+ nativeFocusCandidateTextSize()));
+ return true;
} else {
- // The textfield is now off screen. The user probably
- // was not zooming to see the textfield better. Remove
- // the WebTextView. If the user types a key, and the
+ // The textfield is now off screen. The user probably
+ // was not zooming to see the textfield better. Remove
+ // the WebTextView. If the user types a key, and the
// textfield is still in focus, we will reconstruct
// the WebTextView and scroll it back on screen.
mWebTextView.remove();
+ return false;
}
}
+ private void drawExtras(Canvas canvas, int extras, boolean animationsRunning) {
+ // If mNativeClass is 0, we should not reach here, so we do not
+ // need to check it again.
+ if (animationsRunning) {
+ canvas.setDrawFilter(mWebViewCore.mZoomFilter);
+ }
+ nativeDrawExtras(canvas, extras);
+ canvas.setDrawFilter(null);
+ }
+
private void drawCoreAndCursorRing(Canvas canvas, int color,
boolean drawCursorRing) {
if (mDrawHistory) {
@@ -2922,8 +3279,23 @@ public class WebView extends AbsoluteLayout
}
boolean animateZoom = mZoomScale != 0;
- boolean animateScroll = !mScroller.isFinished()
- || mVelocityTracker != null;
+ boolean animateScroll = ((!mScroller.isFinished()
+ || mVelocityTracker != null)
+ && (mTouchMode != TOUCH_DRAG_MODE ||
+ mHeldMotionless != MOTIONLESS_TRUE))
+ || mDeferTouchMode == TOUCH_DRAG_MODE;
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ if (mHeldMotionless == MOTIONLESS_PENDING) {
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_FALSE;
+ }
+ if (mHeldMotionless == MOTIONLESS_FALSE) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+ mHeldMotionless = MOTIONLESS_PENDING;
+ }
+ }
if (animateZoom) {
float zoomScale;
int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
@@ -2936,11 +3308,17 @@ public class WebView extends AbsoluteLayout
zoomScale = mZoomScale;
// set mZoomScale to be 0 as we have done animation
mZoomScale = 0;
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
// call invalidate() again to draw with the final filters
invalidate();
if (mNeedToAdjustWebTextView) {
mNeedToAdjustWebTextView = false;
- adjustTextView(true);
+ if (didUpdateTextViewBounds(false)
+ && nativeFocusCandidateIsPassword()) {
+ // If it is a password field, start drawing the
+ // WebTextView once again.
+ mWebTextView.setInPassword(true);
+ }
}
}
// calculate the intermediate scroll position. As we need to use
@@ -2974,34 +3352,58 @@ public class WebView extends AbsoluteLayout
canvas.scale(mActualScale, mActualScale);
}
+ boolean UIAnimationsRunning = false;
+ // Currently for each draw we compute the animation values;
+ // We may in the future decide to do that independently.
+ if (mNativeClass != 0 && nativeEvaluateLayersAnimations()) {
+ UIAnimationsRunning = true;
+ // If we have unfinished (or unstarted) animations,
+ // we ask for a repaint.
+ invalidate();
+ }
mWebViewCore.drawContentPicture(canvas, color,
- (animateZoom || mPreviewZoomOnly), animateScroll);
-
+ (animateZoom || mPreviewZoomOnly || UIAnimationsRunning),
+ animateScroll);
if (mNativeClass == 0) return;
- if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
- if (mTouchSelection) {
- nativeDrawSelectionRegion(canvas);
- } else {
- nativeDrawSelection(canvas, mInvActualScale, getTitleHeight(),
- mSelectX, mSelectY, mExtendSelection);
+ // decide which adornments to draw
+ int extras = DRAW_EXTRAS_NONE;
+ if (mFindIsUp) {
+ // When the FindDialog is up, only draw the matches if we are not in
+ // the process of scrolling them into view.
+ if (!animateScroll) {
+ extras = DRAW_EXTRAS_FIND;
+ }
+ } else if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (!animateZoom && !mPreviewZoomOnly) {
+ extras = DRAW_EXTRAS_SELECTION;
+ nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
+ nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
+ mSelectX, mSelectY - getTitleHeight(),
+ mExtendSelection);
}
} else if (drawCursorRing) {
+ extras = DRAW_EXTRAS_CURSOR_RING;
+ }
+ drawExtras(canvas, extras, UIAnimationsRunning);
+
+ if (extras == DRAW_EXTRAS_CURSOR_RING) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
HitTestResult hitTest = getHitTestResult();
- if (hitTest != null &&
- hitTest.mType != HitTestResult.UNKNOWN_TYPE) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(SWITCH_TO_LONGPRESS),
- LONG_PRESS_TIMEOUT);
+ if (hitTest == null
+ || hitTest.mType == HitTestResult.UNKNOWN_TYPE) {
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
}
}
- nativeDrawCursorRing(canvas);
}
- // When the FindDialog is up, only draw the matches if we are not in
- // the process of scrolling them into view.
- if (mFindIsUp && !animateScroll) {
- nativeDrawMatches(canvas);
+ if (mFocusSizeChanged) {
+ mFocusSizeChanged = false;
+ // If we are zooming, this will get handled above, when the zoom
+ // finishes. We also do not need to do this unless the WebTextView
+ // is showing.
+ if (!animateZoom && inEditingMode()) {
+ didUpdateTextViewBounds(true);
+ }
}
}
@@ -3021,6 +3423,7 @@ public class WebView extends AbsoluteLayout
if (null == mWebViewCore) return; // CallbackProxy may trigger this
if (mDrawHistory && mWebViewCore.pictureReady()) {
mDrawHistory = false;
+ mHistoryPicture = null;
invalidate();
int oldScrollX = mScrollX;
int oldScrollY = mScrollY;
@@ -3030,8 +3433,10 @@ public class WebView extends AbsoluteLayout
mUserScroll = false;
mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX,
oldScrollY);
+ onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY);
+ } else {
+ sendOurVisibleRect();
}
- sendOurVisibleRect();
}
}
@@ -3070,29 +3475,46 @@ public class WebView extends AbsoluteLayout
mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
}
- // Called by JNI when a touch event puts a textfield into focus.
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ InputConnection connection = super.onCreateInputConnection(outAttrs);
+ outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN;
+ return connection;
+ }
+
+ /**
+ * Called in response to a message from webkit telling us that the soft
+ * keyboard should be launched.
+ */
private void displaySoftKeyboard(boolean isTextView) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ // bring it back to the default scale so that user can enter text
+ boolean zoom = mActualScale < mDefaultScale;
+ if (zoom) {
+ mInZoomOverview = false;
+ mZoomCenterX = mLastTouchX;
+ mZoomCenterY = mLastTouchY;
+ // do not change text wrap scale so that there is no reflow
+ setNewZoomScale(mDefaultScale, false, false);
+ }
if (isTextView) {
- if (mWebTextView == null) return;
-
- imm.showSoftInput(mWebTextView, 0);
- if (mActualScale < mDefaultScale) {
- // bring it back to the default scale so that user can enter
- // text.
- mInZoomOverview = false;
- mZoomCenterX = mLastTouchX;
- mZoomCenterY = mLastTouchY;
- // do not change text wrap scale so that there is no reflow
- setNewZoomScale(mDefaultScale, false, false);
- adjustTextView(false);
+ rebuildWebTextView();
+ if (inEditingMode()) {
+ imm.showSoftInput(mWebTextView, 0);
+ if (zoom) {
+ didUpdateTextViewBounds(true);
+ }
+ return;
}
}
- else { // used by plugins
- imm.showSoftInput(this, 0);
- }
+ // Used by plugins.
+ // Also used if the navigation cache is out of date, and
+ // does not recognize that a textfield is in focus. In that
+ // case, use WebView as the targeted view.
+ // see http://b/issue?id=2457459
+ imm.showSoftInput(this, 0);
}
// Called by WebKit to instruct the UI to hide the keyboard
@@ -3136,6 +3558,8 @@ public class WebView extends AbsoluteLayout
// Note that sendOurVisibleRect calls viewToContent, so the coordinates
// should be in content coordinates.
Rect bounds = nativeFocusCandidateNodeBounds();
+ Rect vBox = contentToViewRect(bounds);
+ mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height());
if (!Rect.intersects(bounds, visibleRect)) {
mWebTextView.bringIntoView();
}
@@ -3146,80 +3570,63 @@ public class WebView extends AbsoluteLayout
// i.e. In the case of opening/closing the screen.
// In that case, we need to set the dimensions, but not the other
// aspects.
- // We also need to restore the selection, which gets wrecked by
- // calling setTextEntryRect.
- Spannable spannable = (Spannable) mWebTextView.getText();
- int start = Selection.getSelectionStart(spannable);
- int end = Selection.getSelectionEnd(spannable);
// If the text has been changed by webkit, update it. However, if
// there has been more UI text input, ignore it. We will receive
// another update when that text is recognized.
- if (text != null && !text.equals(spannable.toString())
+ if (text != null && !text.equals(mWebTextView.getText().toString())
&& nativeTextGeneration() == mTextGeneration) {
mWebTextView.setTextAndKeepSelection(text);
- } else {
- Selection.setSelection(spannable, start, end);
}
} else {
- Rect vBox = contentToViewRect(bounds);
- mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
- vBox.height());
mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
Gravity.RIGHT : Gravity.NO_GRAVITY);
- // this needs to be called before update adapter thread starts to
- // ensure the mWebTextView has the same node pointer
+ // This needs to be called before setType, which may call
+ // requestFormData, and it needs to have the correct nodePointer.
mWebTextView.setNodePointer(nodePointer);
- int maxLength = -1;
- boolean isTextField = nativeFocusCandidateIsTextField();
- if (isTextField) {
- maxLength = nativeFocusCandidateMaxLength();
- String name = nativeFocusCandidateName();
- if (mWebViewCore.getSettings().getSaveFormData()
- && name != null) {
- Message update = mPrivateHandler.obtainMessage(
- REQUEST_FORM_DATA, nodePointer);
- RequestFormData updater = new RequestFormData(name,
- getUrl(), update);
- Thread t = new Thread(updater);
- t.start();
- }
- }
- mWebTextView.setMaxLength(maxLength);
- AutoCompleteAdapter adapter = null;
- mWebTextView.setAdapterCustom(adapter);
- mWebTextView.setSingleLine(isTextField);
- mWebTextView.setInPassword(nativeFocusCandidateIsPassword());
+ mWebTextView.setType(nativeFocusCandidateType());
if (null == text) {
- mWebTextView.setText("", 0, 0);
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "rebuildWebTextView null == text");
}
- } else {
- // Change to true to enable the old style behavior, where
- // entering a textfield/textarea always set the selection to the
- // whole field. This was desirable for the case where the user
- // intends to scroll past the field using the trackball.
- // However, it causes a problem when replying to emails - the
- // user expects the cursor to be at the beginning of the
- // textarea. Testing out a new behavior, where textfields set
- // selection at the end, and textareas at the beginning.
- if (false) {
- mWebTextView.setText(text, 0, text.length());
- } else if (isTextField) {
- int length = text.length();
- mWebTextView.setText(text, length, length);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView length=" + length);
- }
- } else {
- mWebTextView.setText(text, 0, 0);
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "rebuildWebTextView !isTextField");
- }
- }
+ text = "";
+ }
+ mWebTextView.setTextAndKeepSelection(text);
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null && imm.isActive(mWebTextView)) {
+ imm.restartInput(mWebTextView);
}
- mWebTextView.requestFocus();
}
+ mWebTextView.requestFocus();
+ }
+
+ /**
+ * Called by WebTextView to find saved form data associated with the
+ * textfield
+ * @param name Name of the textfield.
+ * @param nodePointer Pointer to the node of the textfield, so it can be
+ * compared to the currently focused textfield when the data is
+ * retrieved.
+ */
+ /* package */ void requestFormData(String name, int nodePointer) {
+ if (mWebViewCore.getSettings().getSaveFormData()) {
+ Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
+ update.arg1 = nodePointer;
+ RequestFormData updater = new RequestFormData(name, getUrl(),
+ update);
+ Thread t = new Thread(updater);
+ t.start();
+ }
+ }
+
+ /**
+ * Pass a message to find out the <label> associated with the <input>
+ * identified by nodePointer
+ * @param framePointer Pointer to the frame containing the <input> node
+ * @param nodePointer Pointer to the node for which a <label> is desired.
+ */
+ /* package */ void requestLabel(int framePointer, int nodePointer) {
+ mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer,
+ nodePointer);
}
/*
@@ -3249,6 +3656,46 @@ public class WebView extends AbsoluteLayout
}
}
+ /**
+ * Dump the display tree to "/sdcard/displayTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpDisplayTree() {
+ nativeDumpDisplayTree(getUrl());
+ }
+
+ /**
+ * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
+ * "/sdcard/domTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpDomTree(boolean toFile) {
+ mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
+ }
+
+ /**
+ * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
+ * to "/sdcard/renderTree.txt"
+ *
+ * @hide debug only
+ */
+ public void dumpRenderTree(boolean toFile) {
+ mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
+ }
+
+ /**
+ * Dump the V8 counters to standard output.
+ * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to
+ * true. Otherwise, this will do nothing.
+ *
+ * @hide debug only
+ */
+ public void dumpV8Counters() {
+ mWebViewCore.sendMessage(EventHub.DUMP_V8COUNTERS);
+ }
+
// This is used to determine long press with the center key. Does not
// affect long press with the trackball/touch.
private boolean mGotCenterDown = false;
@@ -3281,27 +3728,32 @@ public class WebView extends AbsoluteLayout
return false;
}
- if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false
- && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
- || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) {
- mExtendSelection = false;
- mShiftIsPressed = true;
- if (nativeHasCursorNode()) {
- Rect rect = nativeCursorNodeBounds();
- mSelectX = contentToViewX(rect.left);
- mSelectY = contentToViewY(rect.top);
- } else {
- mSelectX = mScrollX + (int) mLastTouchX;
- mSelectY = mScrollY + (int) mLastTouchY;
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+ if (nativeFocusIsPlugin()) {
+ mShiftIsPressed = true;
+ } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) {
+ setUpSelectXY();
}
- nativeHideCursor();
- }
+ }
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
- // always handle the navigation keys in the UI thread
switchOutDrawHistory();
- if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) {
+ if (nativeFocusIsPlugin()) {
+ letPluginHandleNavKey(keyCode, event.getEventTime(), true);
+ return true;
+ }
+ if (mShiftIsPressed) {
+ int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+ ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
+ int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
+ -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0;
+ int multiplier = event.getRepeatCount() + 1;
+ moveSelection(xRate * multiplier, yRate * multiplier);
+ return true;
+ }
+ if (navHandledKey(keyCode, 1, false, event.getEventTime())) {
playSoundEffect(keyCodeToSoundsEffect(keyCode));
return true;
}
@@ -3312,6 +3764,9 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
+ if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ return true; // discard press if copy in progress
+ }
mGotCenterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
@@ -3336,24 +3791,15 @@ public class WebView extends AbsoluteLayout
if (getSettings().getNavDump()) {
switch (keyCode) {
case KeyEvent.KEYCODE_4:
- // "/data/data/com.android.browser/displayTree.txt"
- nativeDumpDisplayTree(getUrl());
+ dumpDisplayTree();
break;
case KeyEvent.KEYCODE_5:
case KeyEvent.KEYCODE_6:
- // 5: dump the dom tree to the file
- // "/data/data/com.android.browser/domTree.txt"
- // 6: dump the dom tree to the adb log
- mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE,
- (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0);
+ dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
break;
case KeyEvent.KEYCODE_7:
case KeyEvent.KEYCODE_8:
- // 7: dump the render tree to the file
- // "/data/data/com.android.browser/renderTree.txt"
- // 8: dump the render tree to the adb log
- mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE,
- (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0);
+ dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
break;
case KeyEvent.KEYCODE_9:
nativeInstrumentReport();
@@ -3361,10 +3807,7 @@ public class WebView extends AbsoluteLayout
}
}
- if (nativeCursorIsPlugin()) {
- nativeUpdatePluginReceivesEvents();
- invalidate();
- } else if (nativeCursorIsTextInput()) {
+ if (nativeCursorIsTextInput()) {
// This message will put the node in focus, for the DOM's notion
// of focus, and make the focuscontroller active
mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
@@ -3373,13 +3816,17 @@ public class WebView extends AbsoluteLayout
// our view system's notion of focus
rebuildWebTextView();
// Now we need to pass the event to it
- return mWebTextView.onKeyDown(keyCode, event);
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ return mWebTextView.dispatchKeyEvent(event);
+ }
} else if (nativeHasFocusNode()) {
// In this case, the cursor is not on a text input, but the focus
// might be. Check it, and if so, hand over to the WebTextView.
rebuildWebTextView();
if (inEditingMode()) {
- return mWebTextView.onKeyDown(keyCode, event);
+ mWebTextView.setDefaultSelection();
+ return mWebTextView.dispatchKeyEvent(event);
}
}
@@ -3426,13 +3873,19 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
- if (commitCopy()) {
+ if (nativeFocusIsPlugin()) {
+ mShiftIsPressed = false;
+ } else if (commitCopy()) {
return true;
}
}
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
+ if (nativeFocusIsPlugin()) {
+ letPluginHandleNavKey(keyCode, event.getEventTime(), false);
+ return true;
+ }
// always handle the navigation keys in the UI thread
// Bubble up the key event as WebView doesn't handle it
return false;
@@ -3443,8 +3896,14 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mGotCenterDown = false;
- if (mShiftIsPressed) {
- return false;
+ if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (mExtendSelection) {
+ commitCopy();
+ } else {
+ mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
+ }
+ return true; // discard press if copy in progress
}
// perform the single click
@@ -3454,21 +3913,23 @@ public class WebView extends AbsoluteLayout
if (!nativeCursorIntersects(visibleRect)) {
return false;
}
- nativeSetFollowedLink(true);
- nativeUpdatePluginReceivesEvents();
WebViewCore.CursorData data = cursorData();
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
playSoundEffect(SoundEffectConstants.CLICK);
- boolean isTextInput = nativeCursorIsTextInput();
- if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading(
- nativeCursorText())) {
+ if (nativeCursorIsTextInput()) {
+ rebuildWebTextView();
+ centerKeyPressOnTextField();
+ if (inEditingMode()) {
+ mWebTextView.setDefaultSelection();
+ }
+ return true;
+ }
+ clearTextEntry(true);
+ nativeSetFollowedLink(true);
+ if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) {
mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame,
nativeCursorNodePointer());
}
- if (isTextInput) {
- rebuildWebTextView();
- displaySoftKeyboard(true);
- }
return true;
}
@@ -3484,70 +3945,89 @@ public class WebView extends AbsoluteLayout
return false;
}
+ private void setUpSelectXY() {
+ mExtendSelection = false;
+ mShiftIsPressed = true;
+ if (nativeHasCursorNode()) {
+ Rect rect = nativeCursorNodeBounds();
+ mSelectX = contentToViewX(rect.left);
+ mSelectY = contentToViewY(rect.top);
+ } else if (mLastTouchY > getVisibleTitleHeight()) {
+ mSelectX = mScrollX + (int) mLastTouchX;
+ mSelectY = mScrollY + (int) mLastTouchY;
+ } else {
+ mSelectX = mScrollX + getViewWidth() / 2;
+ mSelectY = mScrollY + getViewHeightWithTitle() / 2;
+ }
+ nativeHideCursor();
+ }
+
/**
- * @hide
+ * Use this method to put the WebView into text selection mode.
+ * Do not rely on this functionality; it will be deprecated in the future.
*/
public void emulateShiftHeld() {
if (0 == mNativeClass) return; // client isn't initialized
- mExtendSelection = false;
- mShiftIsPressed = true;
- nativeHideCursor();
+ setUpSelectXY();
}
private boolean commitCopy() {
boolean copiedSomething = false;
if (mExtendSelection) {
- // copy region so core operates on copy without touching orig.
- Region selection = new Region(nativeGetSelection());
- if (selection.isEmpty() == false) {
+ String selection = nativeGetSelection();
+ if (selection != "") {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "commitCopy \"" + selection + "\"");
+ }
Toast.makeText(mContext
, com.android.internal.R.string.text_copied
, Toast.LENGTH_SHORT).show();
- mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection);
copiedSomething = true;
+ try {
+ IClipboard clip = IClipboard.Stub.asInterface(
+ ServiceManager.getService("clipboard"));
+ clip.setClipboardText(selection);
+ } catch (android.os.RemoteException e) {
+ Log.e(LOGTAG, "Clipboard failed", e);
+ }
}
mExtendSelection = false;
}
mShiftIsPressed = false;
+ invalidate(); // remove selection region and pointer
if (mTouchMode == TOUCH_SELECT_MODE) {
mTouchMode = TOUCH_INIT_MODE;
}
return copiedSomething;
}
- // Set this as a hierarchy change listener so we can know when this view
- // is removed and still have access to our parent.
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- ViewParent parent = getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup p = (ViewGroup) parent;
- p.setOnHierarchyChangeListener(this);
- }
+ if (hasWindowFocus()) setActive(true);
}
@Override
protected void onDetachedFromWindow() {
+ clearTextEntry(false);
+ dismissZoomControl();
+ if (hasWindowFocus()) setActive(false);
super.onDetachedFromWindow();
- ViewParent parent = getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup p = (ViewGroup) parent;
- p.setOnHierarchyChangeListener(null);
- }
-
- // Clean up the zoom controller
- mZoomButtonsController.setVisible(false);
}
- // Implementation for OnHierarchyChangeListener
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Deprecated
public void onChildViewAdded(View parent, View child) {}
- public void onChildViewRemoved(View p, View child) {
- if (child == this) {
- clearTextEntry();
- }
- }
+ /**
+ * @deprecated WebView no longer needs to implement
+ * ViewGroup.OnHierarchyChangeListener. This method does nothing now.
+ */
+ @Deprecated
+ public void onChildViewRemoved(View p, View child) {}
/**
* @deprecated WebView should not have implemented
@@ -3558,11 +4038,8 @@ public class WebView extends AbsoluteLayout
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
}
- // To avoid drawing the cursor ring, and remove the TextView when our window
- // loses focus.
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- if (hasWindowFocus) {
+ private void setActive(boolean active) {
+ if (active) {
if (hasFocus()) {
// If our window regained focus, and we have focus, then begin
// drawing the cursor ring
@@ -3582,7 +4059,9 @@ public class WebView extends AbsoluteLayout
// false for the first parameter
}
} else {
- if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) {
+ if (mWebViewCore != null && getSettings().getBuiltInZoomControls()
+ && (mZoomButtonsController == null ||
+ !mZoomButtonsController.isVisible())) {
/*
* The zoom controls come in their own window, so our window
* loses focus. Our policy is to not draw the cursor ring if
@@ -3595,12 +4074,26 @@ public class WebView extends AbsoluteLayout
}
mGotKeyDown = false;
mShiftIsPressed = false;
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mTouchMode = TOUCH_DONE_MODE;
if (mNativeClass != 0) {
nativeRecordButtons(false, false, true);
}
setFocusControllerInactive();
}
invalidate();
+ }
+
+ // To avoid drawing the cursor ring, and remove the TextView when our window
+ // loses focus.
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ setActive(hasWindowFocus);
+ if (hasWindowFocus) {
+ BrowserFrame.sJavaBridge.setActiveWebView(this);
+ } else {
+ BrowserFrame.sJavaBridge.removeActiveWebView(this);
+ }
super.onWindowFocusChanged(hasWindowFocus);
}
@@ -3652,17 +4145,67 @@ public class WebView extends AbsoluteLayout
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
+ /**
+ * @hide
+ */
+ @Override
+ protected boolean setFrame(int left, int top, int right, int bottom) {
+ boolean changed = super.setFrame(left, top, right, bottom);
+ if (!changed && mHeightCanMeasure) {
+ // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
+ // in WebViewCore after we get the first layout. We do call
+ // requestLayout() when we get contentSizeChanged(). But the View
+ // system won't call onSizeChanged if the dimension is not changed.
+ // In this case, we need to call sendViewSizeZoom() explicitly to
+ // notify the WebKit about the new dimensions.
+ sendViewSizeZoom();
+ }
+ return changed;
+ }
+
+ private static class PostScale implements Runnable {
+ final WebView mWebView;
+ final boolean mUpdateTextWrap;
+
+ public PostScale(WebView webView, boolean updateTextWrap) {
+ mWebView = webView;
+ mUpdateTextWrap = updateTextWrap;
+ }
+
+ public void run() {
+ if (mWebView.mWebViewCore != null) {
+ // we always force, in case our height changed, in which case we
+ // still want to send the notification over to webkit.
+ mWebView.setNewZoomScale(mWebView.mActualScale,
+ mUpdateTextWrap, true);
+ // update the zoom buttons as the scale can be changed
+ if (mWebView.getSettings().getBuiltInZoomControls()) {
+ mWebView.updateZoomButtonsEnabled();
+ }
+ }
+ }
+ }
+
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
// Center zooming to the center of the screen.
if (mZoomScale == 0) { // unless we're already zooming
- mZoomCenterX = getViewWidth() * .5f;
- mZoomCenterY = getViewHeight() * .5f;
+ // To anchor at top left corner.
+ mZoomCenterX = 0;
+ mZoomCenterY = getVisibleTitleHeight();
mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
}
+ // adjust the max viewport width depending on the view dimensions. This
+ // is to ensure the scaling is not going insane. So do not shrink it if
+ // the view size is temporarily smaller, e.g. when soft keyboard is up.
+ int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE);
+ if (newMaxViewportWidth > sMaxViewportWidth) {
+ sMaxViewportWidth = newMaxViewportWidth;
+ }
+
// update mMinZoomScale if the minimum zoom scale is not fixed
if (!mMinZoomScaleFixed) {
// when change from narrow screen to wide screen, the new viewWidth
@@ -3681,25 +4224,36 @@ public class WebView extends AbsoluteLayout
}
}
- // we always force, in case our height changed, in which case we still
- // want to send the notification over to webkit
+ dismissZoomControl();
+
+ // onSizeChanged() is called during WebView layout. And any
+ // requestLayout() is blocked during layout. As setNewZoomScale() will
+ // call its child View to reposition itself through ViewManager's
+ // scaleAll(), we need to post a Runnable to ensure requestLayout().
+ // <b/>
// only update the text wrap scale if width changed.
- setNewZoomScale(mActualScale, w != ow, true);
+ post(new PostScale(this, w != ow));
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
-
sendOurVisibleRect();
+ // update WebKit if visible title bar height changed. The logic is same
+ // as getVisibleTitleHeight.
+ int titleHeight = getTitleHeight();
+ if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
+ sendViewSizeZoom();
+ }
}
-
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean dispatch = true;
- if (!inEditingMode()) {
+ // Textfields and plugins need to receive the shift up key even if
+ // another key was released while the shift key was held down.
+ if (!inEditingMode() && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
mGotKeyDown = true;
} else {
@@ -3732,71 +4286,8 @@ public class WebView extends AbsoluteLayout
private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
- private class ScaleDetectorListener implements
- ScaleGestureDetector.OnScaleGestureListener {
-
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- // cancel the single touch handling
- cancelTouch();
- if (mZoomButtonsController.isVisible()) {
- mZoomButtonsController.setVisible(false);
- }
- // reset the zoom overview mode so that the page won't auto grow
- mInZoomOverview = false;
- // If it is in password mode, turn it off so it does not draw
- // misplaced.
- if (inEditingMode() && nativeFocusCandidateIsPassword()) {
- mWebTextView.setInPassword(false);
- }
- return true;
- }
-
- public void onScaleEnd(ScaleGestureDetector detector) {
- if (mPreviewZoomOnly) {
- mPreviewZoomOnly = false;
- mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
- mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- // don't reflow when zoom in; when zoom out, do reflow if the
- // new scale is almost minimum scale;
- boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f)
- || ((mActualScale <= 0.8 * mTextWrapScale));
- // force zoom after mPreviewZoomOnly is set to false so that the
- // new view size will be passed to the WebKit
- setNewZoomScale(mActualScale, reflowNow, true);
- // call invalidate() to draw without zoom filter
- invalidate();
- }
- // adjust the edit text view if needed
- if (inEditingMode()) {
- adjustTextView(true);
- }
- // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
- // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
- // may trigger the unwanted fling.
- mTouchMode = TOUCH_PINCH_DRAG;
- startTouch(detector.getFocusX(), detector.getFocusY(),
- mLastTouchTime);
- }
-
- public boolean onScale(ScaleGestureDetector detector) {
- float scale = (float) (Math.round(detector.getScaleFactor()
- * mActualScale * 100) / 100.0);
- if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
- mPreviewZoomOnly = true;
- // limit the scale change per step
- if (scale > mActualScale) {
- scale = Math.min(scale, mActualScale * 1.25f);
- } else {
- scale = Math.max(scale, mActualScale * 0.8f);
- }
- mZoomCenterX = detector.getFocusX();
- mZoomCenterY = detector.getFocusY();
- setNewZoomScale(scale, false, false);
- invalidate();
- return true;
- }
- return false;
- }
+ private static int sign(float x) {
+ return x > 0 ? 1 : (x < 0 ? -1 : 0);
}
// if the page can scroll <= this value, we won't allow the drag tracker
@@ -3810,6 +4301,14 @@ public class WebView extends AbsoluteLayout
private final float mMaxDY, mMaxDX;
private float mCurrStretchY, mCurrStretchX;
private int mSX, mSY;
+ private Interpolator mInterp;
+ private float[] mXY = new float[2];
+
+ // inner (non-state) classes can't have enums :(
+ private static final int DRAGGING_STATE = 0;
+ private static final int ANIMATING_STATE = 1;
+ private static final int FINISHED_STATE = 2;
+ private int mState;
public DragTrackerHandler(float x, float y, DragTracker proxy) {
mProxy = proxy;
@@ -3834,6 +4333,7 @@ public class WebView extends AbsoluteLayout
mMinDX = -viewLeft;
mMaxDX = docRight - viewRight;
+ mState = DRAGGING_STATE;
mProxy.onStartDrag(x, y);
// ensure we buildBitmap at least once
@@ -3856,6 +4356,12 @@ public class WebView extends AbsoluteLayout
float sy = computeStretch(mStartY - y, mMinDY, mMaxDY);
float sx = computeStretch(mStartX - x, mMinDX, mMaxDX);
+ if ((mSnapScrollMode & SNAP_X) != 0) {
+ sy = 0;
+ } else if ((mSnapScrollMode & SNAP_Y) != 0) {
+ sx = 0;
+ }
+
if (mCurrStretchX != sx || mCurrStretchY != sy) {
mCurrStretchX = sx;
mCurrStretchY = sy;
@@ -3870,10 +4376,26 @@ public class WebView extends AbsoluteLayout
}
public void stopDrag() {
+ final int DURATION = 200;
+ int now = (int)SystemClock.uptimeMillis();
+ mInterp = new Interpolator(2);
+ mXY[0] = mCurrStretchX;
+ mXY[1] = mCurrStretchY;
+ // float[] blend = new float[] { 0.5f, 0, 0.75f, 1 };
+ float[] blend = new float[] { 0, 0.5f, 0.75f, 1 };
+ mInterp.setKeyFrame(0, now, mXY, blend);
+ float[] zerozero = new float[] { 0, 0 };
+ mInterp.setKeyFrame(1, now + DURATION, zerozero, null);
+ mState = ANIMATING_STATE;
+
if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) {
- Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag");
+ Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag, starting animation");
}
- mProxy.onStopDrag();
+ }
+
+ // Call this after each draw. If it ruturns null, the tracker is done
+ public boolean isFinished() {
+ return mState == FINISHED_STATE;
}
private int hiddenHeightOfTitleBar() {
@@ -3888,19 +4410,29 @@ public class WebView extends AbsoluteLayout
}
/* If the tracker draws, then this returns true, otherwise it will
- return false, and draw nothing.
+ return false, and draw nothing.
*/
public boolean draw(Canvas canvas) {
if (mCurrStretchX != 0 || mCurrStretchY != 0) {
int sx = getScrollX();
int sy = getScrollY() - hiddenHeightOfTitleBar();
-
if (mSX != sx || mSY != sy) {
buildBitmap(sx, sy);
mSX = sx;
mSY = sy;
}
+ if (mState == ANIMATING_STATE) {
+ Interpolator.Result result = mInterp.timeToValues(mXY);
+ if (result == Interpolator.Result.FREEZE_END) {
+ mState = FINISHED_STATE;
+ return false;
+ } else {
+ mProxy.onStretchChange(mXY[0], mXY[1]);
+ invalidate();
+ // fall through to the draw
+ }
+ }
int count = canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(sx, sy);
mProxy.onDraw(canvas);
@@ -3955,6 +4487,102 @@ public class WebView extends AbsoluteLayout
private DragTracker mDragTracker;
private DragTrackerHandler mDragTrackerHandler;
+ private class ScaleDetectorListener implements
+ ScaleGestureDetector.OnScaleGestureListener {
+
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ // cancel the single touch handling
+ cancelTouch();
+ dismissZoomControl();
+ // reset the zoom overview mode so that the page won't auto grow
+ mInZoomOverview = false;
+ // If it is in password mode, turn it off so it does not draw
+ // misplaced.
+ if (inEditingMode() && nativeFocusCandidateIsPassword()) {
+ mWebTextView.setInPassword(false);
+ }
+
+ mViewManager.startZoom();
+
+ return true;
+ }
+
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mPreviewZoomOnly) {
+ mPreviewZoomOnly = false;
+ mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+ mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+ // don't reflow when zoom in; when zoom out, do reflow if the
+ // new scale is almost minimum scale;
+ boolean reflowNow = (mActualScale - mMinZoomScale
+ <= MINIMUM_SCALE_INCREMENT)
+ || ((mActualScale <= 0.8 * mTextWrapScale));
+ // force zoom after mPreviewZoomOnly is set to false so that the
+ // new view size will be passed to the WebKit
+ setNewZoomScale(mActualScale, reflowNow, true);
+ // call invalidate() to draw without zoom filter
+ invalidate();
+ }
+ // adjust the edit text view if needed
+ if (inEditingMode() && didUpdateTextViewBounds(false)
+ && nativeFocusCandidateIsPassword()) {
+ // If it is a password field, start drawing the
+ // WebTextView once again.
+ mWebTextView.setInPassword(true);
+ }
+ // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
+ // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
+ // may trigger the unwanted fling.
+ mTouchMode = TOUCH_PINCH_DRAG;
+ mConfirmMove = true;
+ startTouch(detector.getFocusX(), detector.getFocusY(),
+ mLastTouchTime);
+
+ mViewManager.endZoom();
+ }
+
+ public boolean onScale(ScaleGestureDetector detector) {
+ float scale = (float) (Math.round(detector.getScaleFactor()
+ * mActualScale * 100) / 100.0);
+ if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) {
+ mPreviewZoomOnly = true;
+ // limit the scale change per step
+ if (scale > mActualScale) {
+ scale = Math.min(scale, mActualScale * 1.25f);
+ } else {
+ scale = Math.max(scale, mActualScale * 0.8f);
+ }
+ mZoomCenterX = detector.getFocusX();
+ mZoomCenterY = detector.getFocusY();
+ setNewZoomScale(scale, false, false);
+ invalidate();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private boolean hitFocusedPlugin(int contentX, int contentY) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin());
+ Rect r = nativeFocusNodeBounds();
+ Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top
+ + ", " + r.right + ", " + r.bottom + ")");
+ }
+ return nativeFocusIsPlugin()
+ && nativeFocusNodeBounds().contains(contentX, contentY);
+ }
+
+ private boolean shouldForwardTouchEvent() {
+ return mFullScreenHolder != null || (mForwardTouchEvents
+ && mTouchMode != TOUCH_SELECT_MODE
+ && mPreventDefault != PREVENT_DEFAULT_IGNORE);
+ }
+
+ private boolean inFullScreenMode() {
+ return mFullScreenHolder != null;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -3972,28 +4600,32 @@ public class WebView extends AbsoluteLayout
// FIXME: we may consider to give WebKit an option to handle multi-touch
// events later.
- if (mSupportMultiTouch && mMinZoomScale < mMaxZoomScale
- && ev.getPointerCount() > 1) {
- mScaleDetector.onTouchEvent(ev);
- if (mScaleDetector.isInProgress()) {
- mLastTouchTime = eventTime;
- return true;
- }
- x = mScaleDetector.getFocusX();
- y = mScaleDetector.getFocusY();
- action = ev.getAction() & MotionEvent.ACTION_MASK;
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- cancelTouch();
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP) {
- // set mLastTouchX/Y to the remaining point
- mLastTouchX = x;
- mLastTouchY = y;
- } else if (action == MotionEvent.ACTION_MOVE) {
- // negative x or y indicate it is on the edge, skip it.
- if (x < 0 || y < 0) {
+ if (mSupportMultiTouch && ev.getPointerCount() > 1) {
+ if (mMinZoomScale < mMaxZoomScale) {
+ mScaleDetector.onTouchEvent(ev);
+ if (mScaleDetector.isInProgress()) {
+ mLastTouchTime = eventTime;
return true;
}
+ x = mScaleDetector.getFocusX();
+ y = mScaleDetector.getFocusY();
+ action = ev.getAction() & MotionEvent.ACTION_MASK;
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ cancelTouch();
+ action = MotionEvent.ACTION_DOWN;
+ } else if (action == MotionEvent.ACTION_POINTER_UP) {
+ // set mLastTouchX/Y to the remaining point
+ mLastTouchX = x;
+ mLastTouchY = y;
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ // negative x or y indicate it is on the edge, skip it.
+ if (x < 0 || y < 0) {
+ return true;
+ }
+ }
+ } else {
+ // if the page disallow zoom, skip multi-pointer action
+ return true;
}
} else {
action = ev.getAction();
@@ -4012,40 +4644,35 @@ public class WebView extends AbsoluteLayout
y = getViewHeightWithTitle() - 1;
}
- // pass the touch events from UI thread to WebCore thread
- if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE
- || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) {
- WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData();
- ted.mAction = action;
- ted.mX = viewToContentX((int) x + mScrollX);
- ted.mY = viewToContentY((int) y + mScrollY);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mLastSentTouchTime = eventTime;
- }
-
- int deltaX = (int) (mLastTouchX - x);
- int deltaY = (int) (mLastTouchY - y);
+ float fDeltaX = mLastTouchX - x;
+ float fDeltaY = mLastTouchY - y;
+ int deltaX = (int) fDeltaX;
+ int deltaY = (int) fDeltaY;
+ int contentX = viewToContentX((int) x + mScrollX);
+ int contentY = viewToContentY((int) y + mScrollY);
switch (action) {
case MotionEvent.ACTION_DOWN: {
- mPreventDrag = PREVENT_DRAG_NO;
+ mPreventDefault = PREVENT_DEFAULT_NO;
+ mConfirmMove = false;
if (!mScroller.isFinished()) {
// stop the current scroll animation, but if this is
// the start of a fling, allow it to add to the current
// fling's velocity
mScroller.abortAnimation();
mTouchMode = TOUCH_DRAG_START_MODE;
- mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
- } else if (mShiftIsPressed) {
+ mConfirmMove = true;
+ mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
+ } else if (!inFullScreenMode() && mShiftIsPressed) {
mSelectX = mScrollX + (int) x;
mSelectY = mScrollY + (int) y;
mTouchMode = TOUCH_SELECT_MODE;
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
}
- nativeMoveSelection(viewToContentX(mSelectX),
- viewToContentY(mSelectY), false);
+ nativeMoveSelection(contentX, contentY, false);
mTouchSelection = mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -4053,41 +4680,114 @@ public class WebView extends AbsoluteLayout
} else {
// commit the short press action for the previous tap
doShortPress();
- // continue, mTouchMode should be still TOUCH_INIT_MODE
+ mTouchMode = TOUCH_INIT_MODE;
+ mDeferTouchProcess = (!inFullScreenMode()
+ && mForwardTouchEvents) ? hitFocusedPlugin(
+ contentX, contentY) : false;
}
- } else {
+ } else { // the normal case
mPreviewZoomOnly = false;
mTouchMode = TOUCH_INIT_MODE;
- mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
- : PREVENT_DRAG_NO;
+ mDeferTouchProcess = (!inFullScreenMode()
+ && mForwardTouchEvents) ? hitFocusedPlugin(
+ contentX, contentY) : false;
mWebViewCore.sendMessage(
EventHub.UPDATE_FRAME_CACHE_IF_LOADING);
if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
- EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION,
+ EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
}
}
// Trigger the link
if (mTouchMode == TOUCH_INIT_MODE
|| mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
+ mPrivateHandler.sendEmptyMessageDelayed(
+ SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
+ mPrivateHandler.sendEmptyMessageDelayed(
+ SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
+ if (inFullScreenMode() || mDeferTouchProcess) {
+ mPreventDefault = PREVENT_DEFAULT_YES;
+ } else if (mForwardTouchEvents) {
+ mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
+ } else {
+ mPreventDefault = PREVENT_DEFAULT_NO;
+ }
+ // pass the touch events from UI thread to WebCore thread
+ if (shouldForwardTouchEvent()) {
+ TouchEventData ted = new TouchEventData();
+ ted.mAction = action;
+ ted.mX = contentX;
+ ted.mY = contentY;
+ ted.mMetaState = ev.getMetaState();
+ ted.mReprocess = mDeferTouchProcess;
+ if (mDeferTouchProcess) {
+ // still needs to set them for compute deltaX/Y
+ mLastTouchX = x;
+ mLastTouchY = y;
+ ted.mViewX = x;
+ ted.mViewY = y;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ break;
+ }
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ if (!inFullScreenMode()) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
+ action, 0), TAP_TIMEOUT);
+ }
+ }
}
- // Remember where the motion event started
startTouch(x, y, eventTime);
- if (mDragTracker != null) {
- mDragTrackerHandler = new DragTrackerHandler(x, y,
- mDragTracker);
- }
break;
}
case MotionEvent.ACTION_MOVE: {
- if (mTouchMode == TOUCH_DONE_MODE) {
- // no dragging during scroll zoom animation
+ boolean firstMove = false;
+ if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
+ >= mTouchSlopSquare) {
+ mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mConfirmMove = true;
+ firstMove = true;
+ if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
+ mTouchMode = TOUCH_INIT_MODE;
+ }
+ }
+ // pass the touch events from UI thread to WebCore thread
+ if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
+ || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
+ mLastSentTouchTime = eventTime;
+ TouchEventData ted = new TouchEventData();
+ ted.mAction = action;
+ ted.mX = contentX;
+ ted.mY = contentY;
+ ted.mMetaState = ev.getMetaState();
+ ted.mReprocess = mDeferTouchProcess;
+ if (mDeferTouchProcess) {
+ ted.mViewX = x;
+ ted.mViewY = y;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ break;
+ }
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ if (firstMove && !inFullScreenMode()) {
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
+ action, 0), TAP_TIMEOUT);
+ }
+ }
+ if (mTouchMode == TOUCH_DONE_MODE
+ || mPreventDefault == PREVENT_DEFAULT_YES) {
+ // no dragging during scroll zoom animation, or when prevent
+ // default is yes
break;
}
+ if (mVelocityTracker == null) {
+ Log.e(LOGTAG, "Got null mVelocityTracker when "
+ + "mPreventDefault = " + mPreventDefault
+ + " mDeferTouchProcess = " + mDeferTouchProcess
+ + " mTouchMode = " + mTouchMode);
+ }
mVelocityTracker.addMovement(ev);
-
if (mTouchMode != TOUCH_DRAG_MODE) {
if (mTouchMode == TOUCH_SELECT_MODE) {
mSelectX = mScrollX + (int) x;
@@ -4095,28 +4795,20 @@ public class WebView extends AbsoluteLayout
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
}
- nativeMoveSelection(viewToContentX(mSelectX),
- viewToContentY(mSelectY), true);
+ nativeMoveSelection(contentX, contentY, true);
invalidate();
break;
}
- if ((deltaX * deltaX + deltaY * deltaY) < mTouchSlopSquare) {
+ if (!mConfirmMove) {
break;
}
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
+ if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
+ || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
// track mLastTouchTime as we may need to do fling at
// ACTION_UP
mLastTouchTime = eventTime;
break;
}
- if (mTouchMode == TOUCH_SHORTPRESS_MODE
- || mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- } else if (mTouchMode == TOUCH_INIT_MODE
- || mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- }
-
// if it starts nearly horizontal or vertical, enforce it
int ax = Math.abs(deltaX);
int ay = Math.abs(deltaY);
@@ -4129,34 +4821,39 @@ public class WebView extends AbsoluteLayout
}
mTouchMode = TOUCH_DRAG_MODE;
- WebViewCore.pauseUpdate(mWebViewCore);
- if (!mDragFromTextInput) {
- nativeHideCursor();
- }
- WebSettings settings = getSettings();
- if (settings.supportZoom()
- && settings.getBuiltInZoomControls()
- && !mZoomButtonsController.isVisible()
- && mMinZoomScale < mMaxZoomScale) {
- mZoomButtonsController.setVisible(true);
- int count = settings.getDoubleTapToastCount();
- if (mInZoomOverview && count > 0) {
- settings.setDoubleTapToastCount(--count);
- Toast.makeText(mContext,
- com.android.internal.R.string.double_tap_toast,
- Toast.LENGTH_LONG).show();
- }
- }
+ mLastTouchX = x;
+ mLastTouchY = y;
+ fDeltaX = 0.0f;
+ fDeltaY = 0.0f;
+ deltaX = 0;
+ deltaY = 0;
+
+ startDrag();
+ }
+
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.dragTo(x, y);
}
// do pan
int newScrollX = pinLocX(mScrollX + deltaX);
- deltaX = newScrollX - mScrollX;
+ int newDeltaX = newScrollX - mScrollX;
+ if (deltaX != newDeltaX) {
+ deltaX = newDeltaX;
+ fDeltaX = (float) newDeltaX;
+ }
int newScrollY = pinLocY(mScrollY + deltaY);
- deltaY = newScrollY - mScrollY;
+ int newDeltaY = newScrollY - mScrollY;
+ if (deltaY != newDeltaY) {
+ deltaY = newDeltaY;
+ fDeltaY = (float) newDeltaY;
+ }
boolean done = false;
- if (deltaX == 0 && deltaY == 0) {
- done = true;
+ boolean keepScrollBarsVisible = false;
+ if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
+ mLastTouchX = x;
+ mLastTouchY = y;
+ keepScrollBarsVisible = done = true;
} else {
if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) {
int ax = Math.abs(deltaX);
@@ -4168,99 +4865,105 @@ public class WebView extends AbsoluteLayout
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay) &&
- ((mSnapPositive &&
- deltaX < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaX > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_X_LOCK;
+ if (ax > MAX_SLOPE_FOR_DIAG * ay &&
+ (mSnapPositive
+ ? deltaX < -mMinLockSnapReverseDistance
+ : deltaX > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
} else {
// radical change means getting out of snap mode
- if ((ax > MAX_SLOPE_FOR_DIAG * ay)
+ if (ax > MAX_SLOPE_FOR_DIAG * ay
&& ax > MIN_BREAK_SNAP_CROSS_DISTANCE) {
mSnapScrollMode = SNAP_NONE;
}
// reverse direction means lock in the snap mode
- if ((ay > MAX_SLOPE_FOR_DIAG * ax) &&
- ((mSnapPositive &&
- deltaY < -mMinLockSnapReverseDistance)
- || (!mSnapPositive &&
- deltaY > mMinLockSnapReverseDistance))) {
- mSnapScrollMode = SNAP_Y_LOCK;
+ if (ay > MAX_SLOPE_FOR_DIAG * ax &&
+ (mSnapPositive
+ ? deltaY < -mMinLockSnapReverseDistance
+ : deltaY > mMinLockSnapReverseDistance)) {
+ mSnapScrollMode |= SNAP_LOCK;
}
}
}
-
- if (mSnapScrollMode == SNAP_X
- || mSnapScrollMode == SNAP_X_LOCK) {
- if (deltaX == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
+ if (mSnapScrollMode != SNAP_NONE) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
+ deltaY = 0;
} else {
- scrollBy(deltaX, 0);
+ deltaX = 0;
}
- mLastTouchX = x;
- } else if (mSnapScrollMode == SNAP_Y
- || mSnapScrollMode == SNAP_Y_LOCK) {
- if (deltaY == 0) {
- // keep the scrollbar on the screen even there is no
- // scroll
- awakenScrollBars(ViewConfiguration
- .getScrollDefaultDelay(), false);
- } else {
- scrollBy(0, deltaY);
+ }
+ if ((deltaX | deltaY) != 0) {
+ if (deltaX != 0) {
+ mLastTouchX = x;
}
- mLastTouchY = y;
+ if (deltaY != 0) {
+ mLastTouchY = y;
+ }
+ mHeldMotionless = MOTIONLESS_FALSE;
} else {
- scrollBy(deltaX, deltaY);
+ // keep the scrollbar on the screen even there is no
+ // scroll
mLastTouchX = x;
mLastTouchY = y;
+ keepScrollBarsVisible = true;
}
mLastTouchTime = eventTime;
mUserScroll = true;
}
- if (!getSettings().getBuiltInZoomControls()) {
- boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
- if (mZoomControls != null && showPlusMinus) {
- if (mZoomControls.getVisibility() == View.VISIBLE) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- } else {
- mZoomControls.show(showPlusMinus, false);
- }
- mPrivateHandler.postDelayed(mZoomControlRunnable,
- ZOOM_CONTROLS_TIMEOUT);
- }
- }
-
- if (mDragTrackerHandler != null) {
- mDragTrackerHandler.dragTo(x, y);
- }
+ doDrag(deltaX, deltaY);
- if (done) {
+ if (keepScrollBarsVisible) {
+ if (mHeldMotionless != MOTIONLESS_TRUE) {
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ }
// keep the scrollbar on the screen even there is no scroll
awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
false);
// return false to indicate that we can't pan out of the
// view space
- return false;
+ return !done;
}
break;
}
case MotionEvent.ACTION_UP: {
- if (mDragTrackerHandler != null) {
- mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
+ // pass the touch events from UI thread to WebCore thread
+ if (shouldForwardTouchEvent()) {
+ TouchEventData ted = new TouchEventData();
+ ted.mAction = action;
+ ted.mX = contentX;
+ ted.mY = contentY;
+ ted.mMetaState = ev.getMetaState();
+ ted.mReprocess = mDeferTouchProcess;
+ if (mDeferTouchProcess) {
+ ted.mViewX = x;
+ ted.mViewY = y;
+ }
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
}
mLastTouchUpTime = eventTime;
switch (mTouchMode) {
case TOUCH_DOUBLE_TAP_MODE: // double tap
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mTouchMode = TOUCH_DONE_MODE;
- doDoubleTap();
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ if (inFullScreenMode() || mDeferTouchProcess) {
+ TouchEventData ted = new TouchEventData();
+ ted.mAction = WebViewCore.ACTION_DOUBLETAP;
+ ted.mX = contentX;
+ ted.mY = contentY;
+ ted.mMetaState = ev.getMetaState();
+ ted.mReprocess = mDeferTouchProcess;
+ if (mDeferTouchProcess) {
+ ted.mViewX = x;
+ ted.mViewY = y;
+ }
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ } else if (mPreventDefault != PREVENT_DEFAULT_YES){
+ doDoubleTap();
+ mTouchMode = TOUCH_DONE_MODE;
+ }
break;
case TOUCH_SELECT_MODE:
commitCopy();
@@ -4271,69 +4974,82 @@ public class WebView extends AbsoluteLayout
case TOUCH_SHORTPRESS_MODE:
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquare) {
+ if (mConfirmMove) {
Log.w(LOGTAG, "Miss a drag as we are waiting for" +
" WebCore's response for touch down.");
- if (computeHorizontalScrollExtent() < computeHorizontalScrollRange()
- || computeVerticalScrollExtent() < computeVerticalScrollRange()) {
+ if (mPreventDefault != PREVENT_DEFAULT_YES
+ && (computeMaxScrollX() > 0
+ || computeMaxScrollY() > 0)) {
+ // UI takes control back, cancel WebCore touch
+ cancelWebCoreTouchEvent(contentX, contentY,
+ true);
// we will not rewrite drag code here, but we
// will try fling if it applies.
- WebViewCore.pauseUpdate(mWebViewCore);
+ WebViewCore.reducePriority();
+ // to get better performance, pause updating the
+ // picture
+ WebViewCore.pauseUpdatePicture(mWebViewCore);
// fall through to TOUCH_DRAG_MODE
} else {
+ // WebKit may consume the touch event and modify
+ // DOM. drawContentPicture() will be called with
+ // animateSroll as true for better performance.
+ // Force redraw in high-quality.
+ invalidate();
break;
}
} else {
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- // if mPreventDrag is not confirmed, treat it as
- // no so that it won't block tap or double tap.
- mPreventDrag = PREVENT_DRAG_NO;
- }
- if (mPreventDrag == PREVENT_DRAG_NO) {
- if (mTouchMode == TOUCH_INIT_MODE) {
- mPrivateHandler.sendMessageDelayed(
- mPrivateHandler.obtainMessage(
- RELEASE_SINGLE_TAP),
- ViewConfiguration.getDoubleTapTimeout());
- } else {
- mTouchMode = TOUCH_DONE_MODE;
- doShortPress();
- }
+ if (mTouchMode == TOUCH_INIT_MODE) {
+ mPrivateHandler.sendEmptyMessageDelayed(
+ RELEASE_SINGLE_TAP, ViewConfiguration
+ .getDoubleTapTimeout());
+ } else {
+ doShortPress();
}
break;
}
case TOUCH_DRAG_MODE:
- // redraw in high-quality, as we're done dragging
- invalidate();
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
// if the user waits a while w/o moving before the
// up, we don't want to do a fling
if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
+ if (mVelocityTracker == null) {
+ Log.e(LOGTAG, "Got null mVelocityTracker when "
+ + "mPreventDefault = "
+ + mPreventDefault
+ + " mDeferTouchProcess = "
+ + mDeferTouchProcess);
+ }
mVelocityTracker.addMovement(ev);
+ // set to MOTIONLESS_IGNORE so that it won't keep
+ // removing and sending message in
+ // drawCoreAndCursorRing()
+ mHeldMotionless = MOTIONLESS_IGNORE;
doFling();
break;
}
- mLastVelocity = 0;
- WebViewCore.resumeUpdate(mWebViewCore);
- break;
+ // redraw in high-quality, as we're done dragging
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ // fall through
case TOUCH_DRAG_START_MODE:
- case TOUCH_DONE_MODE:
- // do nothing
+ // TOUCH_DRAG_START_MODE should not happen for the real
+ // device as we almost certain will get a MOVE. But this
+ // is possible on emulator.
+ mLastVelocity = 0;
+ WebViewCore.resumePriority();
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
break;
}
- // we also use mVelocityTracker == null to tell us that we are
- // not "moving around", so we can take the slower/prettier
- // mode in the drawing code
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
+ stopTouch();
break;
}
case MotionEvent.ACTION_CANCEL: {
- if (mDragTrackerHandler != null) {
- mDragTrackerHandler.stopDrag();
- mDragTrackerHandler = null;
+ if (mTouchMode == TOUCH_DRAG_MODE) {
+ invalidate();
}
+ cancelWebCoreTouchEvent(contentX, contentY, false);
cancelTouch();
break;
}
@@ -4341,27 +5057,108 @@ public class WebView extends AbsoluteLayout
return true;
}
+ private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
+ if (shouldForwardTouchEvent()) {
+ if (removeEvents) {
+ mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
+ }
+ TouchEventData ted = new TouchEventData();
+ ted.mX = x;
+ ted.mY = y;
+ ted.mAction = MotionEvent.ACTION_CANCEL;
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ mPreventDefault = PREVENT_DEFAULT_IGNORE;
+ }
+ }
+
private void startTouch(float x, float y, long eventTime) {
+ // Remember where the motion event started
mLastTouchX = x;
mLastTouchY = y;
mLastTouchTime = eventTime;
mVelocityTracker = VelocityTracker.obtain();
mSnapScrollMode = SNAP_NONE;
+ if (mDragTracker != null) {
+ mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker);
+ }
+ }
+
+ private void startDrag() {
+ WebViewCore.reducePriority();
+ // to get better performance, pause updating the picture
+ WebViewCore.pauseUpdatePicture(mWebViewCore);
+ if (!mDragFromTextInput) {
+ nativeHideCursor();
+ }
+ WebSettings settings = getSettings();
+ if (settings.supportZoom()
+ && settings.getBuiltInZoomControls()
+ && !getZoomButtonsController().isVisible()
+ && mMinZoomScale < mMaxZoomScale
+ && (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
+ || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF)) {
+ mZoomButtonsController.setVisible(true);
+ int count = settings.getDoubleTapToastCount();
+ if (mInZoomOverview && count > 0) {
+ settings.setDoubleTapToastCount(--count);
+ Toast.makeText(mContext,
+ com.android.internal.R.string.double_tap_toast,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private void doDrag(int deltaX, int deltaY) {
+ if ((deltaX | deltaY) != 0) {
+ scrollBy(deltaX, deltaY);
+ }
+ if (!getSettings().getBuiltInZoomControls()) {
+ boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
+ if (mZoomControls != null && showPlusMinus) {
+ if (mZoomControls.getVisibility() == View.VISIBLE) {
+ mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+ } else {
+ mZoomControls.show(showPlusMinus, false);
+ }
+ mPrivateHandler.postDelayed(mZoomControlRunnable,
+ ZOOM_CONTROLS_TIMEOUT);
+ }
+ }
+ }
+
+ private void stopTouch() {
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.stopDrag();
+ }
+ // we also use mVelocityTracker == null to tell us that we are
+ // not "moving around", so we can take the slower/prettier
+ // mode in the drawing code
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
}
private void cancelTouch() {
- // we also use mVelocityTracker == null to tell us that we are not
- // "moving around", so we can take the slower/prettier mode in the
- // drawing code
+ if (mDragTrackerHandler != null) {
+ mDragTrackerHandler.stopDrag();
+ }
+ // we also use mVelocityTracker == null to tell us that we are
+ // not "moving around", so we can take the slower/prettier
+ // mode in the drawing code
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
if (mTouchMode == TOUCH_DRAG_MODE) {
- WebViewCore.resumeUpdate(mWebViewCore);
+ WebViewCore.resumePriority();
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
}
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+ mHeldMotionless = MOTIONLESS_TRUE;
mTouchMode = TOUCH_DONE_MODE;
nativeHideCursor();
}
@@ -4384,6 +5181,7 @@ public class WebView extends AbsoluteLayout
private static final int SELECT_CURSOR_OFFSET = 16;
private int mSelectX = 0;
private int mSelectY = 0;
+ private boolean mFocusSizeChanged = false;
private boolean mShiftIsPressed = false;
private boolean mTrackballDown = false;
private long mTrackballUpTime = 0;
@@ -4411,8 +5209,10 @@ public class WebView extends AbsoluteLayout
if (ev.getY() < 0) pageUp(true);
return true;
}
+ boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0
+ || !nativeFocusIsPlugin());
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mShiftIsPressed) {
+ if (shiftPressed) {
return true; // discard press if copy in progress
}
mTrackballDown = true;
@@ -4437,11 +5237,12 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mTrackballDown = false;
mTrackballUpTime = time;
- if (mShiftIsPressed) {
+ if (shiftPressed) {
if (mExtendSelection) {
commitCopy();
} else {
mExtendSelection = true;
+ invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
}
@@ -4489,8 +5290,8 @@ public class WebView extends AbsoluteLayout
return;
int width = getViewWidth();
int height = getViewHeight();
- mSelectX += scaleTrackballX(xRate, width);
- mSelectY += scaleTrackballY(yRate, height);
+ mSelectX += xRate;
+ mSelectY += yRate;
int maxX = width + mScrollX;
int maxY = height + mScrollY;
mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET
@@ -4572,8 +5373,11 @@ public class WebView extends AbsoluteLayout
}
float xRate = mTrackballRemainsX * 1000 / elapsed;
float yRate = mTrackballRemainsY * 1000 / elapsed;
- if (mShiftIsPressed) {
- moveSelection(xRate, yRate);
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeight();
+ if (mShiftIsPressed && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
+ moveSelection(scaleTrackballX(xRate, viewWidth),
+ scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
return;
}
@@ -4587,8 +5391,8 @@ public class WebView extends AbsoluteLayout
+ " mTrackballRemainsX=" + mTrackballRemainsX
+ " mTrackballRemainsY=" + mTrackballRemainsY);
}
- int width = mContentWidth - getViewWidth();
- int height = mContentHeight - getViewHeight();
+ int width = mContentWidth - viewWidth;
+ int height = mContentHeight - viewHeight;
if (width < 0) width = 0;
if (height < 0) height = 0;
ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
@@ -4609,7 +5413,12 @@ public class WebView extends AbsoluteLayout
+ " mTrackballRemainsX=" + mTrackballRemainsX
+ " mTrackballRemainsY=" + mTrackballRemainsY);
}
- if (navHandledKey(selectKeyCode, count, false, time, false)) {
+ if (mNativeClass != 0 && nativeFocusIsPlugin()) {
+ for (int i = 0; i < count; i++) {
+ letPluginHandleNavKey(selectKeyCode, time, true);
+ }
+ letPluginHandleNavKey(selectKeyCode, time, false);
+ } else if (navHandledKey(selectKeyCode, count, false, time)) {
playSoundEffect(keyCodeToSoundsEffect(selectKeyCode));
}
mTrackballRemainsX = mTrackballRemainsY = 0;
@@ -4638,16 +5447,18 @@ public class WebView extends AbsoluteLayout
}
}
+ private int computeMaxScrollX() {
+ return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ }
+
private int computeMaxScrollY() {
- int maxContentH = computeVerticalScrollRange() + getTitleHeight();
- return Math.max(maxContentH - getViewHeightWithTitle(), getTitleHeight());
+ return Math.max(computeVerticalScrollRange() + getTitleHeight()
+ - getViewHeightWithTitle(), 0);
}
public void flingScroll(int vx, int vy) {
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
- int maxY = computeMaxScrollY();
-
- mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY);
+ mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
+ computeMaxScrollY());
invalidate();
}
@@ -4655,7 +5466,7 @@ public class WebView extends AbsoluteLayout
if (mVelocityTracker == null) {
return;
}
- int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ int maxX = computeMaxScrollX();
int maxY = computeMaxScrollY();
mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
@@ -4663,20 +5474,20 @@ public class WebView extends AbsoluteLayout
int vy = (int) mVelocityTracker.getYVelocity();
if (mSnapScrollMode != SNAP_NONE) {
- if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) {
+ if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
vy = 0;
} else {
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.resumeUpdate(mWebViewCore);
+ WebViewCore.resumePriority();
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
return;
}
float currentVelocity = mScroller.getCurrVelocity();
@@ -4710,22 +5521,22 @@ public class WebView extends AbsoluteLayout
// want to calculate how long the animation is going to run to precisely
// resume the webcore update.
final int time = mScroller.getDuration();
- mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
+ mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
awakenScrollBars(time);
invalidate();
}
- private boolean zoomWithPreview(float scale) {
+ private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) {
float oldScale = mActualScale;
mInitialScrollX = mScrollX;
mInitialScrollY = mScrollY;
// snap to DEFAULT_SCALE if it is close
- if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) {
+ if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) {
scale = mDefaultScale;
}
- setNewZoomScale(scale, true, false);
+ setNewZoomScale(scale, updateTextWrapScale, false);
if (oldScale != mActualScale) {
// use mZoomPickerScale to see zoom preview first
@@ -4733,6 +5544,7 @@ public class WebView extends AbsoluteLayout
mInvInitialZoomScale = 1.0f / oldScale;
mInvFinalZoomScale = 1.0f / mActualScale;
mZoomScale = mActualScale;
+ WebViewCore.pauseUpdatePicture(mWebViewCore);
invalidate();
return true;
} else {
@@ -4820,6 +5632,19 @@ public class WebView extends AbsoluteLayout
* @hide
*/
public ZoomButtonsController getZoomButtonsController() {
+ if (mZoomButtonsController == null) {
+ mZoomButtonsController = new ZoomButtonsController(this);
+ mZoomButtonsController.setOnZoomListener(mZoomListener);
+ // ZoomButtonsController positions the buttons at the bottom, but in
+ // the middle. Change their layout parameters so they appear on the
+ // right.
+ View controls = mZoomButtonsController.getZoomControls();
+ ViewGroup.LayoutParams params = controls.getLayoutParams();
+ if (params instanceof FrameLayout.LayoutParams) {
+ FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) params;
+ frameParams.gravity = Gravity.RIGHT;
+ }
+ }
return mZoomButtonsController;
}
@@ -4836,7 +5661,7 @@ public class WebView extends AbsoluteLayout
mZoomCenterY = getViewHeight() * .5f;
mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- return zoomWithPreview(mActualScale * 1.25f);
+ return zoomWithPreview(mActualScale * 1.25f, true);
}
/**
@@ -4851,7 +5676,7 @@ public class WebView extends AbsoluteLayout
mZoomCenterY = getViewHeight() * .5f;
mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
- return zoomWithPreview(mActualScale * 0.8f);
+ return zoomWithPreview(mActualScale * 0.8f, true);
}
private void updateSelection() {
@@ -4895,7 +5720,7 @@ public class WebView extends AbsoluteLayout
mLastTouchTime = eventTime;
if (!mScroller.isFinished()) {
abortAnimation();
- mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+ mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
}
mSnapScrollMode = SNAP_NONE;
mVelocityTracker = VelocityTracker.obtain();
@@ -4919,7 +5744,7 @@ public class WebView extends AbsoluteLayout
}
/**
- * Do a touch up from a WebTextView. This will be handled by webkit to
+ * Due a touch up from a WebTextView. This will be handled by webkit to
* change the selection.
* @param event MotionEvent in the WebTextView's coordinates.
*/
@@ -4929,44 +5754,159 @@ public class WebView extends AbsoluteLayout
}
int x = viewToContentX((int) event.getX() + mWebTextView.getLeft());
int y = viewToContentY((int) event.getY() + mWebTextView.getTop());
- // In case the soft keyboard has been dismissed, bring it back up.
- InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView,
- 0);
- if (nativeFocusNodePointer() != nativeCursorNodePointer()) {
- nativeMotionUp(x, y, mNavSlop);
- }
- nativeTextInputMotionUp(x, y);
+ nativeMotionUp(x, y, mNavSlop);
}
- /*package*/ void shortPressOnTextField() {
- if (inEditingMode()) {
- View v = mWebTextView;
- int x = viewToContentX((v.getLeft() + v.getRight()) >> 1);
- int y = viewToContentY((v.getTop() + v.getBottom()) >> 1);
- displaySoftKeyboard(true);
- nativeTextInputMotionUp(x, y);
- }
+ /**
+ * Called when pressing the center key or trackball on a textfield.
+ */
+ /*package*/ void centerKeyPressOnTextField() {
+ mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(),
+ nativeCursorNodePointer());
}
private void doShortPress() {
if (mNativeClass == 0) {
return;
}
+ if (mPreventDefault == PREVENT_DEFAULT_YES) {
+ return;
+ }
+ mTouchMode = TOUCH_DONE_MODE;
switchOutDrawHistory();
// mLastTouchX and mLastTouchY are the point in the current viewport
int contentX = viewToContentX((int) mLastTouchX + mScrollX);
int contentY = viewToContentY((int) mLastTouchY + mScrollY);
- if (nativeMotionUp(contentX, contentY, mNavSlop)) {
- if (mLogEvent) {
- Checkin.updateStats(mContext.getContentResolver(),
- Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0);
- }
+ if (nativePointInNavCache(contentX, contentY, mNavSlop)) {
+ WebViewCore.MotionUpData motionUpData = new WebViewCore
+ .MotionUpData();
+ motionUpData.mFrame = nativeCacheHitFramePointer();
+ motionUpData.mNode = nativeCacheHitNodePointer();
+ motionUpData.mBounds = nativeCacheHitNodeBounds();
+ motionUpData.mX = contentX;
+ motionUpData.mY = contentY;
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS,
+ motionUpData);
+ } else {
+ doMotionUp(contentX, contentY);
+ }
+ }
+
+ private void doMotionUp(int contentX, int contentY) {
+ if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) {
+ EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER);
}
if (nativeHasCursorNode() && !nativeCursorIsTextInput()) {
playSoundEffect(SoundEffectConstants.CLICK);
}
}
+ /*
+ * Return true if the view (Plugin) is fully visible and maximized inside
+ * the WebView.
+ */
+ private boolean isPluginFitOnScreen(ViewManager.ChildView view) {
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeightWithTitle();
+ float scale = Math.min((float) viewWidth / view.width,
+ (float) viewHeight / view.height);
+ if (scale < mMinZoomScale) {
+ scale = mMinZoomScale;
+ } else if (scale > mMaxZoomScale) {
+ scale = mMaxZoomScale;
+ }
+ if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
+ if (contentToViewX(view.x) >= mScrollX
+ && contentToViewX(view.x + view.width) <= mScrollX
+ + viewWidth
+ && contentToViewY(view.y) >= mScrollY
+ && contentToViewY(view.y + view.height) <= mScrollY
+ + viewHeight) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Maximize and center the rectangle, specified in the document coordinate
+ * space, inside the WebView. If the zoom doesn't need to be changed, do an
+ * animated scroll to center it. If the zoom needs to be changed, find the
+ * zoom center and do a smooth zoom transition.
+ */
+ private void centerFitRect(int docX, int docY, int docWidth, int docHeight) {
+ int viewWidth = getViewWidth();
+ int viewHeight = getViewHeightWithTitle();
+ float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight
+ / docHeight);
+ if (scale < mMinZoomScale) {
+ scale = mMinZoomScale;
+ } else if (scale > mMaxZoomScale) {
+ scale = mMaxZoomScale;
+ }
+ if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
+ pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2,
+ contentToViewY(docY + docHeight / 2) - viewHeight / 2,
+ true, 0);
+ } else {
+ float oldScreenX = docX * mActualScale - mScrollX;
+ float rectViewX = docX * scale;
+ float rectViewWidth = docWidth * scale;
+ float newMaxWidth = mContentWidth * scale;
+ float newScreenX = (viewWidth - rectViewWidth) / 2;
+ // pin the newX to the WebView
+ if (newScreenX > rectViewX) {
+ newScreenX = rectViewX;
+ } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
+ newScreenX = viewWidth - (newMaxWidth - rectViewX);
+ }
+ mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale)
+ / (scale - mActualScale);
+ float oldScreenY = docY * mActualScale + getTitleHeight()
+ - mScrollY;
+ float rectViewY = docY * scale + getTitleHeight();
+ float rectViewHeight = docHeight * scale;
+ float newMaxHeight = mContentHeight * scale + getTitleHeight();
+ float newScreenY = (viewHeight - rectViewHeight) / 2;
+ // pin the newY to the WebView
+ if (newScreenY > rectViewY) {
+ newScreenY = rectViewY;
+ } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
+ newScreenY = viewHeight - (newMaxHeight - rectViewY);
+ }
+ mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale)
+ / (scale - mActualScale);
+ zoomWithPreview(scale, false);
+ }
+ }
+
+ void dismissZoomControl() {
+ if (mWebViewCore == null) {
+ // maybe called after WebView's destroy(). As we can't get settings,
+ // just hide zoom control for both styles.
+ if (mZoomButtonsController != null) {
+ mZoomButtonsController.setVisible(false);
+ }
+ if (mZoomControls != null) {
+ mZoomControls.hide();
+ }
+ return;
+ }
+ WebSettings settings = getSettings();
+ if (settings.getBuiltInZoomControls()) {
+ if (mZoomButtonsController != null) {
+ mZoomButtonsController.setVisible(false);
+ }
+ } else {
+ if (mZoomControlRunnable != null) {
+ mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+ }
+ if (mZoomControls != null) {
+ mZoomControls.hide();
+ }
+ }
+ }
+
// Rule for double tap:
// 1. if the current scale is not same as the text wrap scale and layout
// algorithm is NARROW_COLUMNS, fit to column;
@@ -4981,36 +5921,39 @@ public class WebView extends AbsoluteLayout
mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
WebSettings settings = getSettings();
+ settings.setDoubleTapToastCount(0);
// remove the zoom control after double tap
- if (settings.getBuiltInZoomControls()) {
- if (mZoomButtonsController.isVisible()) {
- mZoomButtonsController.setVisible(false);
- }
- } else {
- if (mZoomControlRunnable != null) {
- mPrivateHandler.removeCallbacks(mZoomControlRunnable);
- }
- if (mZoomControls != null) {
- mZoomControls.hide();
+ dismissZoomControl();
+ ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY);
+ if (plugin != null) {
+ if (isPluginFitOnScreen(plugin)) {
+ mInZoomOverview = true;
+ // Force the titlebar fully reveal in overview mode
+ if (mScrollY < getTitleHeight()) mScrollY = 0;
+ zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth,
+ true);
+ } else {
+ mInZoomOverview = false;
+ centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height);
}
+ return;
}
- settings.setDoubleTapToastCount(0);
boolean zoomToDefault = false;
if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
- && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) {
+ && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) {
setNewZoomScale(mActualScale, true, true);
float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
- if (Math.abs(mActualScale - overviewScale) < 0.01f) {
+ if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) {
mInZoomOverview = true;
}
} else if (!mInZoomOverview) {
float newScale = (float) getViewWidth() / mZoomOverviewWidth;
- if (Math.abs(mActualScale - newScale) >= 0.01f) {
+ if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) {
mInZoomOverview = true;
// Force the titlebar fully reveal in overview mode
if (mScrollY < getTitleHeight()) mScrollY = 0;
- zoomWithPreview(newScale);
- } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) {
+ zoomWithPreview(newScale, true);
+ } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) {
zoomToDefault = true;
}
} else {
@@ -5033,7 +5976,7 @@ public class WebView extends AbsoluteLayout
mZoomCenterX = 0;
}
}
- zoomWithPreview(mDefaultScale);
+ zoomWithPreview(mDefaultScale, true);
}
}
@@ -5043,15 +5986,6 @@ public class WebView extends AbsoluteLayout
mCallbackProxy.uiOverrideUrlLoading(url);
}
- // called by JNI
- private void sendPluginState(int state) {
- WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData();
- psd.mFrame = nativeCursorFramePointer();
- psd.mNode = nativeCursorNodePointer();
- psd.mState = state;
- mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd);
- }
-
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
boolean result = false;
@@ -5082,7 +6016,7 @@ public class WebView extends AbsoluteLayout
return result;
}
if (mNativeClass != 0 && !nativeHasCursorNode()) {
- navHandledKey(fakeKeyDirection, 1, true, 0, true);
+ navHandledKey(fakeKeyDirection, 1, true, 0);
}
}
}
@@ -5144,10 +6078,15 @@ public class WebView extends AbsoluteLayout
rect.offset(child.getLeft() - child.getScrollX(),
child.getTop() - child.getScrollY());
- int height = getViewHeightWithTitle();
- int screenTop = mScrollY;
- int screenBottom = screenTop + height;
-
+ Rect content = new Rect(viewToContentX(mScrollX),
+ viewToContentY(mScrollY),
+ viewToContentX(mScrollX + getWidth()
+ - getVerticalScrollbarWidth()),
+ viewToContentY(mScrollY + getViewHeightWithTitle()));
+ content = nativeSubtractLayers(content);
+ int screenTop = contentToViewY(content.top);
+ int screenBottom = contentToViewY(content.bottom);
+ int height = screenBottom - screenTop;
int scrollYDelta = 0;
if (rect.bottom > screenBottom) {
@@ -5165,10 +6104,9 @@ public class WebView extends AbsoluteLayout
scrollYDelta = rect.top - screenTop;
}
- int width = getWidth() - getVerticalScrollbarWidth();
- int screenLeft = mScrollX;
- int screenRight = screenLeft + width;
-
+ int screenLeft = contentToViewX(content.left);
+ int screenRight = contentToViewX(content.right);
+ int width = screenRight - screenLeft;
int scrollXDelta = 0;
if (rect.right > screenRight && rect.left > screenLeft) {
@@ -5200,14 +6138,6 @@ public class WebView extends AbsoluteLayout
}
/* package */ void passToJavaScript(String currentText, KeyEvent event) {
- if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) {
- mWebViewCore.sendMessage(EventHub.CLICK);
- if (mWebTextView.mOkayForFocusNotToMatch) {
- int select = nativeFocusCandidateIsTextField() ?
- nativeFocusCandidateMaxLength() : 0;
- setSelection(select, select);
- }
- }
WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
arg.mEvent = event;
arg.mCurrentText = currentText;
@@ -5223,7 +6153,7 @@ public class WebView extends AbsoluteLayout
cursorData(), 1000);
}
- /* package */ WebViewCore getWebViewCore() {
+ /* package */ synchronized WebViewCore getWebViewCore() {
return mWebViewCore;
}
@@ -5238,11 +6168,19 @@ public class WebView extends AbsoluteLayout
class PrivateHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD
- || msg.what > SHOW_RECT_MSG_ID ? Integer
- .toString(msg.what) : HandlerDebugString[msg.what
- - REMEMBER_PASSWORD]);
+ // exclude INVAL_RECT_MSG_ID since it is frequently output
+ if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
+ if (msg.what >= FIRST_PRIVATE_MSG_ID
+ && msg.what <= LAST_PRIVATE_MSG_ID) {
+ Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
+ - FIRST_PRIVATE_MSG_ID]);
+ } else if (msg.what >= FIRST_PACKAGE_MSG_ID
+ && msg.what <= LAST_PACKAGE_MSG_ID) {
+ Log.v(LOGTAG, HandlerPackageDebugString[msg.what
+ - FIRST_PACKAGE_MSG_ID]);
+ } else {
+ Log.v(LOGTAG, Integer.toString(msg.what));
+ }
}
if (mWebViewCore == null) {
// after WebView's destroy() is called, skip handling messages.
@@ -5263,22 +6201,53 @@ public class WebView extends AbsoluteLayout
((Message) msg.obj).sendToTarget();
break;
}
- case SWITCH_TO_SHORTPRESS: {
- // if mPreventDrag is not confirmed, treat it as no so that
- // it won't block panning the page.
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- mPreventDrag = PREVENT_DRAG_NO;
+ case PREVENT_DEFAULT_TIMEOUT: {
+ // if timeout happens, cancel it so that it won't block UI
+ // to continue handling touch events
+ if ((msg.arg1 == MotionEvent.ACTION_DOWN
+ && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES)
+ || (msg.arg1 == MotionEvent.ACTION_MOVE
+ && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) {
+ cancelWebCoreTouchEvent(
+ viewToContentX((int) mLastTouchX + mScrollX),
+ viewToContentY((int) mLastTouchY + mScrollY),
+ true);
}
+ break;
+ }
+ case SWITCH_TO_SHORTPRESS: {
if (mTouchMode == TOUCH_INIT_MODE) {
- mTouchMode = TOUCH_SHORTPRESS_START_MODE;
- updateSelection();
+ if (mPreventDefault != PREVENT_DEFAULT_YES) {
+ mTouchMode = TOUCH_SHORTPRESS_START_MODE;
+ updateSelection();
+ } else {
+ // set to TOUCH_SHORTPRESS_MODE so that it won't
+ // trigger double tap any more
+ mTouchMode = TOUCH_SHORTPRESS_MODE;
+ }
} else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
mTouchMode = TOUCH_DONE_MODE;
}
break;
}
case SWITCH_TO_LONGPRESS: {
- if (mPreventDrag == PREVENT_DRAG_NO) {
+ if (inFullScreenMode() || mDeferTouchProcess) {
+ TouchEventData ted = new TouchEventData();
+ ted.mAction = WebViewCore.ACTION_LONGPRESS;
+ ted.mX = viewToContentX((int) mLastTouchX + mScrollX);
+ ted.mY = viewToContentY((int) mLastTouchY + mScrollY);
+ // metaState for long press is tricky. Should it be the
+ // state when the press started or when the press was
+ // released? Or some intermediary key state? For
+ // simplicity for now, we don't set it.
+ ted.mMetaState = 0;
+ ted.mReprocess = mDeferTouchProcess;
+ if (mDeferTouchProcess) {
+ ted.mViewX = mLastTouchX;
+ ted.mViewY = mLastTouchY;
+ }
+ mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
+ } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
mTouchMode = TOUCH_DONE_MODE;
performLongClick();
rebuildWebTextView();
@@ -5286,10 +6255,7 @@ public class WebView extends AbsoluteLayout
break;
}
case RELEASE_SINGLE_TAP: {
- if (mPreventDrag == PREVENT_DRAG_NO) {
- mTouchMode = TOUCH_DONE_MODE;
- doShortPress();
- }
+ doShortPress();
break;
}
case SCROLL_BY_MSG_ID:
@@ -5325,6 +6291,12 @@ public class WebView extends AbsoluteLayout
break;
}
case NEW_PICTURE_MSG_ID: {
+ // If we've previously delayed deleting a root
+ // layer, do it now.
+ if (mDelayedDeleteRootLayer) {
+ mDelayedDeleteRootLayer = false;
+ nativeSetRootLayer(0);
+ }
WebSettings settings = mWebViewCore.getSettings();
// called for new content
final int viewWidth = getViewWidth();
@@ -5333,38 +6305,47 @@ public class WebView extends AbsoluteLayout
final Point viewSize = draw.mViewPoint;
boolean useWideViewport = settings.getUseWideViewPort();
WebViewCore.RestoreState restoreState = draw.mRestoreState;
- if (restoreState != null) {
- mInZoomOverview = false;
+ boolean hasRestoreState = restoreState != null;
+ if (hasRestoreState) {
updateZoomRange(restoreState, viewSize.x,
draw.mMinPrefWidth, true);
- if (mInitialScaleInPercent > 0) {
- setNewZoomScale(mInitialScaleInPercent / 100.0f,
+ if (!mDrawHistory) {
+ mInZoomOverview = false;
+
+ if (mInitialScaleInPercent > 0) {
+ setNewZoomScale(mInitialScaleInPercent / 100.0f,
mInitialScaleInPercent != mTextWrapScale * 100,
false);
- } else if (restoreState.mViewScale > 0) {
- mTextWrapScale = restoreState.mTextWrapScale;
- setNewZoomScale(restoreState.mViewScale, false,
+ } else if (restoreState.mViewScale > 0) {
+ mTextWrapScale = restoreState.mTextWrapScale;
+ setNewZoomScale(restoreState.mViewScale, false,
false);
- } else {
- mInZoomOverview = useWideViewport
- && settings.getLoadWithOverviewMode();
- float scale;
- if (mInZoomOverview) {
- scale = (float) viewWidth
- / WebViewCore.DEFAULT_VIEWPORT_WIDTH;
} else {
- scale = restoreState.mTextWrapScale;
+ mInZoomOverview = useWideViewport
+ && settings.getLoadWithOverviewMode();
+ float scale;
+ if (mInZoomOverview) {
+ scale = (float) viewWidth
+ / DEFAULT_VIEWPORT_WIDTH;
+ } else {
+ scale = restoreState.mTextWrapScale;
+ }
+ setNewZoomScale(scale, Math.abs(scale
+ - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT,
+ false);
}
- setNewZoomScale(scale, Math.abs(scale
- - mTextWrapScale) >= 0.01f, false);
- }
- setContentScrollTo(restoreState.mScrollX,
+ setContentScrollTo(restoreState.mScrollX,
restoreState.mScrollY);
- // 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();
+ // 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(false);
+ // update the zoom buttons as the scale can be changed
+ if (getSettings().getBuiltInZoomControls()) {
+ updateZoomButtonsEnabled();
+ }
+ }
}
// We update the layout (i.e. request a layout from the
// view system) if the last view size that we sent to
@@ -5385,9 +6366,14 @@ public class WebView extends AbsoluteLayout
mPictureListener.onNewPicture(WebView.this, capturePicture());
}
if (useWideViewport) {
- mZoomOverviewWidth = Math.max(
- (int) (viewWidth / mDefaultScale), Math.max(
- draw.mMinPrefWidth, draw.mViewPoint.x));
+ // 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.
+ mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
+ .max((int) (viewWidth / mDefaultScale), Math
+ .max(draw.mMinPrefWidth,
+ draw.mViewPoint.x)));
}
if (!mMinZoomScaleFixed) {
mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5399,9 +6385,16 @@ public class WebView extends AbsoluteLayout
- mZoomOverviewWidth) > 1) {
setNewZoomScale((float) viewWidth
/ mZoomOverviewWidth, Math.abs(mActualScale
- - mTextWrapScale) < 0.01f, false);
+ - mTextWrapScale) < MINIMUM_SCALE_INCREMENT,
+ false);
}
}
+ if (draw.mFocusSizeChanged && inEditingMode()) {
+ mFocusSizeChanged = true;
+ }
+ if (hasRestoreState) {
+ mViewManager.postReadyToDrawAll();
+ }
break;
}
case WEBCORE_INITIALIZED_MSG_ID:
@@ -5431,20 +6424,35 @@ public class WebView extends AbsoluteLayout
}
}
break;
+ case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID:
+ displaySoftKeyboard(true);
+ updateTextSelectionFromMessage(msg.arg1, msg.arg2,
+ (WebViewCore.TextSelectionData) msg.obj);
+ break;
case UPDATE_TEXT_SELECTION_MSG_ID:
+ // If no textfield was in focus, and the user touched one,
+ // causing it to send this message, then WebTextView has not
+ // been set up yet. Rebuild it so it can set its selection.
+ rebuildWebTextView();
+ updateTextSelectionFromMessage(msg.arg1, msg.arg2,
+ (WebViewCore.TextSelectionData) msg.obj);
+ break;
+ case RETURN_LABEL:
if (inEditingMode()
- && mWebTextView.isSameTextField(msg.arg1)
- && msg.arg2 == mTextGeneration) {
- WebViewCore.TextSelectionData tData
- = (WebViewCore.TextSelectionData) msg.obj;
- mWebTextView.setSelectionFromWebKit(tData.mStart,
- tData.mEnd);
+ && mWebTextView.isSameTextField(msg.arg1)) {
+ mWebTextView.setHint((String) msg.obj);
+ InputMethodManager imm
+ = InputMethodManager.peekInstance();
+ // The hint is propagated to the IME in
+ // onCreateInputConnection. If the IME is already
+ // active, restart it so that its hint text is updated.
+ if (imm != null && imm.isActive(mWebTextView)) {
+ imm.restartInput(mWebTextView);
+ }
}
break;
case MOVE_OUT_OF_PLUGIN:
- if (nativePluginEatsNavKey()) {
- navHandledKey(msg.arg1, 1, false, 0, true);
- }
+ navHandledKey(msg.arg1, 1, false, 0);
break;
case UPDATE_TEXT_ENTRY_MSG_ID:
// this is sent after finishing resize in WebViewCore. Make
@@ -5455,7 +6463,7 @@ public class WebView extends AbsoluteLayout
}
break;
case CLEAR_TEXT_ENTRY:
- clearTextEntry();
+ clearTextEntry(false);
break;
case INVAL_RECT_MSG_ID: {
Rect r = (Rect)msg.obj;
@@ -5468,27 +6476,32 @@ public class WebView extends AbsoluteLayout
}
break;
}
+ case IMMEDIATE_REPAINT_MSG_ID: {
+ invalidate();
+ break;
+ }
+ case SET_ROOT_LAYER_MSG_ID: {
+ if (0 == msg.arg1) {
+ // Null indicates deleting the old layer, but
+ // don't actually do so until we've got the
+ // new page to display.
+ mDelayedDeleteRootLayer = true;
+ } else {
+ mDelayedDeleteRootLayer = false;
+ nativeSetRootLayer(msg.arg1);
+ invalidate();
+ }
+ break;
+ }
case REQUEST_FORM_DATA:
AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj;
if (mWebTextView.isSameTextField(msg.arg1)) {
mWebTextView.setAdapterCustom(adapter);
}
break;
- case UPDATE_CLIPBOARD:
- String str = (String) msg.obj;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str);
- }
- try {
- IClipboard clip = IClipboard.Stub.asInterface(
- ServiceManager.getService("clipboard"));
- clip.setClipboardText(str);
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Clipboard failed", e);
- }
- break;
- case RESUME_WEBCORE_UPDATE:
- WebViewCore.resumeUpdate(mWebViewCore);
+ case RESUME_WEBCORE_PRIORITY:
+ WebViewCore.resumePriority();
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
break;
case LONG_PRESS_CENTER:
@@ -5496,13 +6509,7 @@ public class WebView extends AbsoluteLayout
// the states
mGotCenterDown = false;
mTrackballDown = false;
- // LONG_PRESS_CENTER is sent as a delayed message. If we
- // switch to windows overview, the WebView will be
- // temporarily removed from the view system. In that case,
- // do nothing.
- if (getParent() != null) {
- performLongClick();
- }
+ performLongClick();
break;
case WEBCORE_NEED_TOUCH_EVENTS:
@@ -5510,15 +6517,79 @@ public class WebView extends AbsoluteLayout
break;
case PREVENT_TOUCH_ID:
- if (msg.arg1 == MotionEvent.ACTION_DOWN) {
- // dont override if mPreventDrag has been set to no due
- // to time out
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- mPreventDrag = msg.arg2 == 1 ? PREVENT_DRAG_YES
- : PREVENT_DRAG_NO;
- if (mPreventDrag == PREVENT_DRAG_YES) {
- mTouchMode = TOUCH_DONE_MODE;
+ if (inFullScreenMode()) {
+ break;
+ }
+ if (msg.obj == null) {
+ if (msg.arg1 == MotionEvent.ACTION_DOWN
+ && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) {
+ // if prevent default is called from WebCore, UI
+ // will not handle the rest of the touch events any
+ // more.
+ mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES
+ : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN;
+ } else if (msg.arg1 == MotionEvent.ACTION_MOVE
+ && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
+ // the return for the first ACTION_MOVE will decide
+ // whether UI will handle touch or not. Currently no
+ // support for alternating prevent default
+ mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES
+ : PREVENT_DEFAULT_NO;
+ }
+ } else if (msg.arg2 == 0) {
+ // prevent default is not called in WebCore, so the
+ // message needs to be reprocessed in UI
+ TouchEventData ted = (TouchEventData) msg.obj;
+ switch (ted.mAction) {
+ case MotionEvent.ACTION_DOWN:
+ mLastDeferTouchX = ted.mViewX;
+ mLastDeferTouchY = ted.mViewY;
+ mDeferTouchMode = TOUCH_INIT_MODE;
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ // no snapping in defer process
+ if (mDeferTouchMode != TOUCH_DRAG_MODE) {
+ mDeferTouchMode = TOUCH_DRAG_MODE;
+ mLastDeferTouchX = ted.mViewX;
+ mLastDeferTouchY = ted.mViewY;
+ startDrag();
+ }
+ int deltaX = pinLocX((int) (mScrollX
+ + mLastDeferTouchX - ted.mViewX))
+ - mScrollX;
+ int deltaY = pinLocY((int) (mScrollY
+ + mLastDeferTouchY - ted.mViewY))
+ - mScrollY;
+ doDrag(deltaX, deltaY);
+ if (deltaX != 0) mLastDeferTouchX = ted.mViewX;
+ if (deltaY != 0) mLastDeferTouchY = ted.mViewY;
+ break;
}
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mDeferTouchMode == TOUCH_DRAG_MODE) {
+ // no fling in defer process
+ WebViewCore.resumePriority();
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
+ }
+ mDeferTouchMode = TOUCH_DONE_MODE;
+ break;
+ case WebViewCore.ACTION_DOUBLETAP:
+ // doDoubleTap() needs mLastTouchX/Y as anchor
+ mLastTouchX = ted.mViewX;
+ mLastTouchY = ted.mViewY;
+ doDoubleTap();
+ mDeferTouchMode = TOUCH_DONE_MODE;
+ break;
+ case WebViewCore.ACTION_LONGPRESS:
+ HitTestResult hitTest = getHitTestResult();
+ if (hitTest != null && hitTest.mType
+ != HitTestResult.UNKNOWN_TYPE) {
+ performLongClick();
+ rebuildWebTextView();
+ }
+ mDeferTouchMode = TOUCH_DONE_MODE;
+ break;
}
}
break;
@@ -5531,10 +6602,67 @@ public class WebView extends AbsoluteLayout
}
break;
- case SHOW_RECT_MSG_ID:
+ case FIND_AGAIN:
+ // Ignore if find has been dismissed.
+ if (mFindIsUp) {
+ findAll(mLastFind);
+ }
+ break;
+
+ case DRAG_HELD_MOTIONLESS:
+ mHeldMotionless = MOTIONLESS_TRUE;
+ invalidate();
+ // fall through to keep scrollbars awake
+
+ case AWAKEN_SCROLL_BARS:
+ if (mTouchMode == TOUCH_DRAG_MODE
+ && mHeldMotionless == MOTIONLESS_TRUE) {
+ awakenScrollBars(ViewConfiguration
+ .getScrollDefaultDelay(), false);
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
+ }
+ break;
+
+ case DO_MOTION_UP:
+ doMotionUp(msg.arg1, msg.arg2);
+ break;
+
+ case SHOW_FULLSCREEN: {
+ View view = (View) msg.obj;
+ int npp = msg.arg1;
+
+ if (mFullScreenHolder != null) {
+ Log.w(LOGTAG, "Should not have another full screen.");
+ mFullScreenHolder.dismiss();
+ }
+ mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp);
+ mFullScreenHolder.setContentView(view);
+ mFullScreenHolder.setCancelable(false);
+ mFullScreenHolder.setCanceledOnTouchOutside(false);
+ mFullScreenHolder.show();
+
+ break;
+ }
+ case HIDE_FULLSCREEN:
+ if (inFullScreenMode()) {
+ mFullScreenHolder.dismiss();
+ mFullScreenHolder = null;
+ }
+ break;
+
+ case DOM_FOCUS_CHANGED:
+ if (inEditingMode()) {
+ nativeClearCursor();
+ rebuildWebTextView();
+ }
+ break;
+
+ case SHOW_RECT_MSG_ID: {
WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
int x = mScrollX;
- int left = contentToViewDimension(data.mLeft);
+ int left = contentToViewX(data.mLeft);
int width = contentToViewDimension(data.mWidth);
int maxWidth = contentToViewDimension(data.mContentWidth);
int viewWidth = getViewWidth();
@@ -5545,27 +6673,50 @@ public class WebView extends AbsoluteLayout
x += (int) (left + data.mXPercentInDoc * width
- mScrollX - data.mXPercentInView * viewWidth);
}
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
+ width + ",maxWidth=" + maxWidth +
+ ",viewWidth=" + viewWidth + ",x="
+ + x + ",xPercentInDoc=" + data.mXPercentInDoc +
+ ",xPercentInView=" + data.mXPercentInView+ ")");
+ }
// use the passing content width to cap x as the current
// mContentWidth may not be updated yet
x = Math.max(0,
(Math.min(maxWidth, x + viewWidth)) - viewWidth);
- int y = mScrollY;
- int top = contentToViewDimension(data.mTop);
+ int top = contentToViewY(data.mTop);
int height = contentToViewDimension(data.mHeight);
int maxHeight = contentToViewDimension(data.mContentHeight);
int viewHeight = getViewHeight();
- if (height < viewHeight) {
- // middle align
- y += top + height / 2 - mScrollY - viewHeight / 2;
- } else {
- y += (int) (top + data.mYPercentInDoc * height
- - mScrollY - data.mYPercentInView * viewHeight);
+ int y = (int) (top + data.mYPercentInDoc * height -
+ data.mYPercentInView * viewHeight);
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
+ height + ",maxHeight=" + maxHeight +
+ ",viewHeight=" + viewHeight + ",y="
+ + y + ",yPercentInDoc=" + data.mYPercentInDoc +
+ ",yPercentInView=" + data.mYPercentInView+ ")");
}
// use the passing content height to cap y as the current
// mContentHeight may not be updated yet
y = Math.max(0,
(Math.min(maxHeight, y + viewHeight) - viewHeight));
+ // We need to take into account the visible title height
+ // when scrolling since y is an absolute view position.
+ y = Math.max(0, y - getVisibleTitleHeight());
scrollTo(x, y);
+ }
+ break;
+
+ case CENTER_FIT_RECT:
+ Rect r = (Rect)msg.obj;
+ mInZoomOverview = false;
+ centerFitRect(r.left, r.top, r.width(), r.height());
+ break;
+
+ case SET_SCROLLBAR_MODES:
+ mHorizontalScrollBarMode = msg.arg1;
+ mVerticalScrollBarMode = msg.arg2;
break;
default:
@@ -5575,6 +6726,19 @@ public class WebView extends AbsoluteLayout
}
}
+ /**
+ * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
+ * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView.
+ */
+ private void updateTextSelectionFromMessage(int nodePointer,
+ int textGeneration, WebViewCore.TextSelectionData data) {
+ if (inEditingMode()
+ && mWebTextView.isSameTextField(nodePointer)
+ && textGeneration == mTextGeneration) {
+ mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
+ }
+ }
+
// Class used to use a dropdown for a <select> element
private class InvokeListBox implements Runnable {
// Whether the listbox allows multiple selection.
@@ -5591,8 +6755,16 @@ public class WebView extends AbsoluteLayout
// Need these to provide stable ids to my ArrayAdapter,
// which normally does not have stable ids. (Bug 1250098)
private class Container extends Object {
+ /**
+ * Possible values for mEnabled. Keep in sync with OptionStatus in
+ * WebViewCore.cpp
+ */
+ final static int OPTGROUP = -1;
+ final static int OPTION_DISABLED = 0;
+ final static int OPTION_ENABLED = 1;
+
String mString;
- boolean mEnabled;
+ int mEnabled;
int mId;
public String toString() {
@@ -5613,6 +6785,54 @@ public class WebView extends AbsoluteLayout
}
@Override
+ public View getView(int position, View convertView,
+ ViewGroup parent) {
+ // Always pass in null so that we will get a new CheckedTextView
+ // Otherwise, an item which was previously used as an <optgroup>
+ // element (i.e. has no check), could get used as an <option>
+ // element, which needs a checkbox/radio, but it would not have
+ // one.
+ convertView = super.getView(position, null, parent);
+ Container c = item(position);
+ if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
+ // ListView does not draw dividers between disabled and
+ // enabled elements. Use a LinearLayout to provide dividers
+ LinearLayout layout = new LinearLayout(mContext);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ if (position > 0) {
+ View dividerTop = new View(mContext);
+ dividerTop.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerTop);
+ }
+
+ if (Container.OPTGROUP == c.mEnabled) {
+ // Currently select_dialog_multichoice and
+ // select_dialog_singlechoice are CheckedTextViews. If
+ // that changes, the class cast will no longer be valid.
+ Assert.assertTrue(
+ convertView instanceof CheckedTextView);
+ ((CheckedTextView) convertView).setCheckMarkDrawable(
+ null);
+ } else {
+ // c.mEnabled == Container.OPTION_DISABLED
+ // Draw the disabled element in a disabled state.
+ convertView.setEnabled(false);
+ }
+
+ layout.addView(convertView);
+ if (position < getCount() - 1) {
+ View dividerBottom = new View(mContext);
+ dividerBottom.setBackgroundResource(
+ android.R.drawable.divider_horizontal_bright);
+ layout.addView(dividerBottom);
+ }
+ return layout;
+ }
+ return convertView;
+ }
+
+ @Override
public boolean hasStableIds() {
// AdapterView's onChanged method uses this to determine whether
// to restore the old state. Return false so that the old (out
@@ -5647,12 +6867,11 @@ public class WebView extends AbsoluteLayout
if (item == null) {
return false;
}
- return item.mEnabled;
+ return Container.OPTION_ENABLED == item.mEnabled;
}
}
- private InvokeListBox(String[] array,
- boolean[] enabled, int[] selected) {
+ private InvokeListBox(String[] array, int[] enabled, int[] selected) {
mMultiple = true;
mSelectedArray = selected;
@@ -5666,8 +6885,7 @@ public class WebView extends AbsoluteLayout
}
}
- private InvokeListBox(String[] array, boolean[] enabled, int
- selection) {
+ private InvokeListBox(String[] array, int[] enabled, int selection) {
mSelection = selection;
mMultiple = false;
@@ -5798,10 +7016,11 @@ public class WebView extends AbsoluteLayout
* Request a dropdown menu for a listbox with multiple selection.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selectedArray Which positions are initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int[]
+ void requestListBox(String[] array, int[] enabledArray, int[]
selectedArray) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selectedArray));
@@ -5843,15 +7062,22 @@ public class WebView extends AbsoluteLayout
* <select> element.
*
* @param array Labels for the listbox.
- * @param enabledArray Which positions are enabled.
+ * @param enabledArray State for each element in the list. See static
+ * integers in Container class.
* @param selection Which position is initally selected.
*/
- void requestListBox(String[] array, boolean[]enabledArray, int selection) {
+ void requestListBox(String[] array, int[] enabledArray, int selection) {
mPrivateHandler.post(
new InvokeListBox(array, enabledArray, selection));
}
// called by JNI
+ private void sendMoveFocus(int frame, int node) {
+ mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS,
+ new WebViewCore.CursorData(frame, node, 0, 0));
+ }
+
+ // called by JNI
private void sendMoveMouse(int frame, int node, int x, int y) {
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE,
new WebViewCore.CursorData(frame, node, x, y));
@@ -5869,8 +7095,7 @@ public class WebView extends AbsoluteLayout
*/
private void sendMoveMouseIfLatest(boolean removeFocus) {
if (removeFocus) {
- clearTextEntry();
- setFocusControllerInactive();
+ clearTextEntry(true);
}
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST,
cursorData());
@@ -5924,21 +7149,34 @@ public class WebView extends AbsoluteLayout
invalidate();
}
- // return true if the key was handled
- private boolean navHandledKey(int keyCode, int count, boolean noScroll,
- long time, boolean ignorePlugin) {
- if (mNativeClass == 0) {
- return false;
+ /**
+ * Pass the key to the plugin. This assumes that nativeFocusIsPlugin()
+ * returned true.
+ */
+ private void letPluginHandleNavKey(int keyCode, long time, boolean down) {
+ int keyEventAction;
+ int eventHubAction;
+ if (down) {
+ keyEventAction = KeyEvent.ACTION_DOWN;
+ eventHubAction = EventHub.KEY_DOWN;
+ playSoundEffect(keyCodeToSoundsEffect(keyCode));
+ } else {
+ keyEventAction = KeyEvent.ACTION_UP;
+ eventHubAction = EventHub.KEY_UP;
}
- if (ignorePlugin == false && nativePluginEatsNavKey()) {
- KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN
- , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
+ KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
+ 1, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0)
| (false ? KeyEvent.META_ALT_ON : 0) // FIXME
| (false ? KeyEvent.META_SYM_ON : 0) // FIXME
, 0, 0, 0);
- mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
- mWebViewCore.sendMessage(EventHub.KEY_UP, event);
- return true;
+ mWebViewCore.sendMessage(eventHubAction, event);
+ }
+
+ // return true if the key was handled
+ private boolean navHandledKey(int keyCode, int count, boolean noScroll,
+ long time) {
+ if (mNativeClass == 0) {
+ return false;
}
mLastCursorTime = time;
mLastCursorBounds = nativeGetCursorRingBounds();
@@ -6005,6 +7243,27 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Draw the HTML page into the specified canvas. This call ignores any
+ * view-specific zoom, scroll offset, or other changes. It does not draw
+ * any view-specific chrome, such as progress or URL bars.
+ *
+ * @hide only needs to be accessible to Browser and testing
+ */
+ public void drawPage(Canvas canvas) {
+ mWebViewCore.drawContentPicture(canvas, 0, false, false);
+ }
+
+ /**
+ * Set the time to wait between passing touches to WebCore. See also the
+ * TOUCH_SENT_INTERVAL member for further discussion.
+ *
+ * @hide This is only used by the DRT test application.
+ */
+ public void setTouchInterval(int interval) {
+ mCurrentTouchInterval = interval;
+ }
+
+ /**
* Update our cache with updatedText.
* @param updatedText The new text to put in our cache.
*/
@@ -6014,15 +7273,17 @@ public class WebView extends AbsoluteLayout
nativeUpdateCachedTextfield(updatedText, mTextGeneration);
}
+ private native int nativeCacheHitFramePointer();
+ private native Rect nativeCacheHitNodeBounds();
+ private native int nativeCacheHitNodePointer();
/* package */ native void nativeClearCursor();
private native void nativeCreate(int ptr);
private native int nativeCursorFramePointer();
private native Rect nativeCursorNodeBounds();
- /* package */ native int nativeCursorNodePointer();
+ private native int nativeCursorNodePointer();
/* package */ native boolean nativeCursorMatchesFocus();
private native boolean nativeCursorIntersects(Rect visibleRect);
private native boolean nativeCursorIsAnchor();
- private native boolean nativeCursorIsPlugin();
private native boolean nativeCursorIsTextInput();
private native Point nativeCursorPosition();
private native String nativeCursorText();
@@ -6033,33 +7294,38 @@ public class WebView extends AbsoluteLayout
private native boolean nativeCursorWantsKeyEvents();
private native void nativeDebugDump();
private native void nativeDestroy();
- private native void nativeDrawCursorRing(Canvas content);
- private native void nativeDrawMatches(Canvas canvas);
- private native void nativeDrawSelection(Canvas content, float scale,
- int offset, int x, int y, boolean extendSelection);
- private native void nativeDrawSelectionRegion(Canvas content);
+ private native boolean nativeEvaluateLayersAnimations();
+ private native void nativeDrawExtras(Canvas canvas, int extra);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
private native void nativeFindNext(boolean forward);
- private native boolean nativeFocusCandidateIsPassword();
+ /* package */ native int nativeFocusCandidateFramePointer();
+ /* package */ native boolean nativeFocusCandidateHasNextTextfield();
+ /* package */ native boolean nativeFocusCandidateIsPassword();
private native boolean nativeFocusCandidateIsRtlText();
- private native boolean nativeFocusCandidateIsTextField();
private native boolean nativeFocusCandidateIsTextInput();
- private native int nativeFocusCandidateMaxLength();
+ /* package */ native int nativeFocusCandidateMaxLength();
/* package */ native String nativeFocusCandidateName();
private native Rect nativeFocusCandidateNodeBounds();
- /* package */ native int nativeFocusCandidatePointer();
+ /* package */ native int nativeFocusCandidatePointer();
private native String nativeFocusCandidateText();
private native int nativeFocusCandidateTextSize();
+ /**
+ * Returns an integer corresponding to WebView.cpp::type.
+ * See WebTextView.setType()
+ */
+ private native int nativeFocusCandidateType();
+ private native boolean nativeFocusIsPlugin();
+ private native Rect nativeFocusNodeBounds();
/* package */ native int nativeFocusNodePointer();
private native Rect nativeGetCursorRingBounds();
- private native Region nativeGetSelection();
+ private native String nativeGetSelection();
private native boolean nativeHasCursorNode();
private native boolean nativeHasFocusNode();
private native void nativeHideCursor();
private native String nativeImageURI(int x, int y);
private native void nativeInstrumentReport();
- /* package */ native void nativeMoveCursorToNextTextInput();
+ /* package */ native boolean nativeMoveCursorToNextTextInput();
// return true if the page has been scrolled
private native boolean nativeMotionUp(int x, int y, int slop);
// returns false if it handled the key
@@ -6068,29 +7334,26 @@ public class WebView extends AbsoluteLayout
private native int nativeMoveGeneration();
private native void nativeMoveSelection(int x, int y,
boolean extendSelection);
- private native boolean nativePluginEatsNavKey();
+ private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
boolean pressed, boolean invalidate);
private native void nativeSelectBestAt(Rect rect);
- private native void nativeSetFindIsDown();
+ private native void nativeSetFindIsEmpty();
+ private native void nativeSetFindIsUp(boolean isUp);
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
- // Returns a value corresponding to CachedFrame::ImeAction
- /* package */ native int nativeTextFieldAction();
- /**
- * Perform a click on a currently focused text input. Since it is already
- * focused, there is no need to go through the nativeMotionUp code, which
- * may change the Cursor.
- */
- private native void nativeTextInputMotionUp(int x, int y);
+ private native void nativeSetRootLayer(int layer);
+ private native void nativeSetSelectionPointer(boolean set,
+ float scale, int x, int y, boolean extendSelection);
+ private native void nativeSetSelectionRegion(boolean set);
+ private native Rect nativeSubtractLayers(Rect content);
private native int nativeTextGeneration();
// Never call this version except by updateCachedTextfield(String) -
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
- private native void nativeUpdatePluginReceivesEvents();
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 30dea74..02c7210 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -86,6 +86,8 @@ public class WebViewClient {
* @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
+ * @deprecated This method is no longer called. When the WebView encounters
+ * a redirect loop, it will cancel the load.
*/
public void onTooManyRedirects(WebView view, Message cancelMsg,
Message continueMsg) {
@@ -173,8 +175,6 @@ public class WebViewClient {
* @param handler An SslErrorHandler object that will handle the user's
* response.
* @param error The SSL error object.
- * @hide - hide this because it contains a parameter of type SslError,
- * which is located in a hidden package.
*/
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6aae794..4118119 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -18,6 +18,8 @@ package android.webkit;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.DrawFilter;
import android.graphics.Paint;
@@ -26,11 +28,13 @@ import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.provider.Browser;
+import android.provider.OpenableColumns;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
@@ -120,10 +124,6 @@ final class WebViewCore {
private int mWebkitScrollX = 0;
private int mWebkitScrollY = 0;
- // If the site doesn't use viewport meta tag to specify the viewport, use
- // DEFAULT_VIEWPORT_WIDTH as default viewport width
- static final int DEFAULT_VIEWPORT_WIDTH = 800;
-
// The thread name used to identify the WebCore thread and for use in
// debugging other classes that require operation within the WebCore thread.
/* package */ static final String THREAD_NAME = "WebViewCoreThread";
@@ -260,9 +260,12 @@ final class WebViewCore {
* @param message The message to add
* @param lineNumber the line on which the error occurred
* @param sourceID the filename of the source that caused the error.
+ * @param msgLevel the log level of this message. This is a value casted to int
+ * from WebCore::MessageLevel in WebCore/page/Console.h.
*/
- protected void addMessageToConsole(String message, int lineNumber, String sourceID) {
- mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID);
+ protected void addMessageToConsole(String message, int lineNumber, String sourceID,
+ int msgLevel) {
+ mCallbackProxy.addMessageToConsole(message, lineNumber, sourceID, msgLevel);
}
/**
@@ -273,6 +276,39 @@ final class WebViewCore {
mCallbackProxy.onJsAlert(url, message);
}
+
+ /**
+ * Called by JNI. Open a file chooser to upload a file.
+ * @return String version of the URI plus the name of the file.
+ * FIXME: Just return the URI here, and in FileSystem::pathGetFileName, call
+ * into Java to get the filename.
+ */
+ private String openFileChooser() {
+ Uri uri = mCallbackProxy.openFileChooser();
+ if (uri == null) return "";
+ // Find out the name, and append it to the URI.
+ // Webkit will treat the name as the filename, and
+ // the URI as the path. The URI will be used
+ // in BrowserFrame to get the actual data.
+ Cursor cursor = mContext.getContentResolver().query(
+ uri,
+ new String[] { OpenableColumns.DISPLAY_NAME },
+ null,
+ null,
+ null);
+ String name = "";
+ if (cursor != null) {
+ try {
+ if (cursor.moveToNext()) {
+ name = cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return uri.toString() + "/" + name;
+ }
+
/**
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
@@ -422,6 +458,8 @@ final class WebViewCore {
*/
private native boolean nativeRecordContent(Region invalRegion, Point wh);
+ private native boolean nativeFocusBoundsChanged();
+
/**
* Splits slow parts of the picture set. Called from the webkit
* thread after nativeDrawContent returns true.
@@ -465,17 +503,20 @@ final class WebViewCore {
private native void nativeSaveDocumentState(int frame);
+ private native void nativeMoveFocus(int framePtr, int nodePointer);
private native void nativeMoveMouse(int framePtr, int x, int y);
private native void nativeMoveMouseIfLatest(int moveGeneration,
int framePtr, int x, int y);
private native String nativeRetrieveHref(int framePtr, int nodePtr);
+ private native String nativeRetrieveAnchorText(int framePtr, int nodePtr);
private native void nativeTouchUp(int touchGeneration,
int framePtr, int nodePtr, int x, int y);
- private native boolean nativeHandleTouchEvent(int action, int x, int y);
+ private native boolean nativeHandleTouchEvent(int action, int x, int y,
+ int metaState);
private native void nativeUpdateFrameCache();
@@ -487,6 +528,8 @@ final class WebViewCore {
private native void nativeDumpNavTree();
+ private native void nativeDumpV8Counters();
+
private native void nativeSetJsFlags(String flags);
/**
@@ -508,8 +551,6 @@ final class WebViewCore {
*/
private native void nativeSetSelection(int start, int end);
- private native String nativeGetSelection(Region sel);
-
// Register a scheme to be treated as local scheme so that it can access
// local asset files for resources
private native void nativeRegisterURLSchemeAsLocal(String scheme);
@@ -522,8 +563,6 @@ final class WebViewCore {
*/
private native void nativeSetNewStorageLimit(long limit);
- private native void nativeUpdatePluginState(int framePtr, int nodePtr, int state);
-
/**
* Provide WebCore with a Geolocation permission state for the specified
* origin.
@@ -549,13 +588,6 @@ final class WebViewCore {
private static final int INITIALIZE = 0;
private static final int REDUCE_PRIORITY = 1;
private static final int RESUME_PRIORITY = 2;
- private static final int CACHE_TICKER = 3;
- private static final int BLOCK_CACHE_TICKER = 4;
- private static final int RESUME_CACHE_TICKER = 5;
-
- private static final int CACHE_TICKER_INTERVAL = 60 * 1000; // 1 minute
-
- private static boolean mCacheTickersBlocked = true;
public void run() {
Looper.prepare();
@@ -581,28 +613,6 @@ final class WebViewCore {
Process.setThreadPriority(
Process.THREAD_PRIORITY_DEFAULT);
break;
-
- case CACHE_TICKER:
- if (!mCacheTickersBlocked) {
- CacheManager.endCacheTransaction();
- CacheManager.startCacheTransaction();
- sendMessageDelayed(
- obtainMessage(CACHE_TICKER),
- CACHE_TICKER_INTERVAL);
- }
- break;
-
- case BLOCK_CACHE_TICKER:
- if (CacheManager.endCacheTransaction()) {
- mCacheTickersBlocked = true;
- }
- break;
-
- case RESUME_CACHE_TICKER:
- if (CacheManager.startCacheTransaction()) {
- mCacheTickersBlocked = false;
- }
- break;
}
}
};
@@ -617,18 +627,20 @@ final class WebViewCore {
String mData;
String mMimeType;
String mEncoding;
- String mFailUrl;
+ String mHistoryUrl;
}
static class CursorData {
CursorData() {}
CursorData(int frame, int node, int x, int y) {
mFrame = frame;
+ mNode = node;
mX = x;
mY = y;
}
int mMoveGeneration;
int mFrame;
+ int mNode;
int mX;
int mY;
}
@@ -643,6 +655,19 @@ final class WebViewCore {
KeyEvent mEvent;
}
+ static class MotionUpData {
+ int mFrame;
+ int mNode;
+ Rect mBounds;
+ int mX;
+ int mY;
+ }
+
+ static class GetUrlData {
+ String mUrl;
+ Map<String, String> mExtraHeaders;
+ }
+
static class PostUrlData {
String mUrl;
byte[] mPostData;
@@ -672,16 +697,19 @@ final class WebViewCore {
int mY;
}
+ // mAction of TouchEventData can be MotionEvent.getAction() which uses the
+ // last two bytes or one of the following values
+ static final int ACTION_LONGPRESS = 0x100;
+ static final int ACTION_DOUBLETAP = 0x200;
+
static class TouchEventData {
- int mAction; // MotionEvent.getAction()
+ int mAction;
int mX;
int mY;
- }
-
- static class PluginStateData {
- int mFrame;
- int mNode;
- int mState;
+ int mMetaState;
+ boolean mReprocess;
+ float mViewX;
+ float mViewY;
}
static class GeolocationPermissionsData {
@@ -690,7 +718,10 @@ final class WebViewCore {
boolean mRemember;
}
+
+
static final String[] HandlerDebugString = {
+ "REQUEST_LABEL", // 97
"UPDATE_FRAME_CACHE_IF_LOADING", // = 98
"SCROLL_TEXT_INPUT", // = 99
"LOAD_URL", // = 100;
@@ -720,9 +751,9 @@ final class WebViewCore {
"SINGLE_LISTBOX_CHOICE", // = 124;
"MESSAGE_RELAY", // = 125;
"SET_BACKGROUND_COLOR", // = 126;
- "PLUGIN_STATE", // = 127;
+ "SET_MOVE_FOCUS", // = 127
"SAVE_DOCUMENT_STATE", // = 128;
- "GET_SELECTION", // = 129;
+ "129", // = 129;
"WEBKIT_DRAW", // = 130;
"SYNC_SCROLL", // = 131;
"POST_URL", // = 132;
@@ -739,10 +770,12 @@ final class WebViewCore {
"ON_PAUSE", // = 143
"ON_RESUME", // = 144
"FREE_MEMORY", // = 145
+ "VALID_NODE_BOUNDS", // = 146
};
class EventHub {
// Message Ids
+ static final int REQUEST_LABEL = 97;
static final int UPDATE_FRAME_CACHE_IF_LOADING = 98;
static final int SCROLL_TEXT_INPUT = 99;
static final int LOAD_URL = 100;
@@ -771,9 +804,9 @@ final class WebViewCore {
static final int SINGLE_LISTBOX_CHOICE = 124;
static final int MESSAGE_RELAY = 125;
static final int SET_BACKGROUND_COLOR = 126;
- static final int PLUGIN_STATE = 127; // plugin notifications
+ static final int SET_MOVE_FOCUS = 127;
static final int SAVE_DOCUMENT_STATE = 128;
- static final int GET_SELECTION = 129;
+
static final int WEBKIT_DRAW = 130;
static final int SYNC_SCROLL = 131;
static final int POST_URL = 132;
@@ -802,6 +835,7 @@ final class WebViewCore {
static final int ON_PAUSE = 143;
static final int ON_RESUME = 144;
static final int FREE_MEMORY = 145;
+ static final int VALID_NODE_BOUNDS = 146;
// Network-based messaging
static final int CLEAR_SSL_PREF_TABLE = 150;
@@ -814,13 +848,23 @@ final class WebViewCore {
static final int DUMP_DOMTREE = 170;
static final int DUMP_RENDERTREE = 171;
static final int DUMP_NAVTREE = 172;
+ static final int DUMP_V8COUNTERS = 173;
- static final int SET_JS_FLAGS = 173;
+ static final int SET_JS_FLAGS = 174;
// Geolocation
static final int GEOLOCATION_PERMISSIONS_PROVIDE = 180;
static final int POPULATE_VISITED_LINKS = 181;
+ static final int HIDE_FULLSCREEN = 182;
+
+ static final int SET_NETWORK_TYPE = 183;
+
+ // navigator.isApplicationInstalled()
+ static final int ADD_PACKAGE_NAMES = 184;
+ static final int ADD_PACKAGE_NAME = 185;
+ static final int REMOVE_PACKAGE_NAME = 186;
+
// private message ids
private static final int DESTROY = 200;
@@ -852,11 +896,11 @@ final class WebViewCore {
@Override
public void handleMessage(Message msg) {
if (DebugFlags.WEB_VIEW_CORE) {
- Log.v(LOGTAG, (msg.what < UPDATE_FRAME_CACHE_IF_LOADING
+ Log.v(LOGTAG, (msg.what < REQUEST_LABEL
|| msg.what
- > FREE_MEMORY ? Integer.toString(msg.what)
+ > VALID_NODE_BOUNDS ? Integer.toString(msg.what)
: HandlerDebugString[msg.what
- - UPDATE_FRAME_CACHE_IF_LOADING])
+ - REQUEST_LABEL])
+ " arg1=" + msg.arg1 + " arg2=" + msg.arg2
+ " obj=" + msg.obj);
}
@@ -877,6 +921,19 @@ final class WebViewCore {
}
break;
+ case REQUEST_LABEL:
+ if (mWebView != null) {
+ int nodePointer = msg.arg2;
+ String label = nativeRequestLabel(msg.arg1,
+ nodePointer);
+ if (label != null && label.length() > 0) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.RETURN_LABEL, nodePointer,
+ 0, label).sendToTarget();
+ }
+ }
+ break;
+
case UPDATE_FRAME_CACHE_IF_LOADING:
nativeUpdateFrameCacheIfLoading();
break;
@@ -886,9 +943,11 @@ final class WebViewCore {
((Float) msg.obj).floatValue(), msg.arg1);
break;
- case LOAD_URL:
- loadUrl((String) msg.obj);
+ case LOAD_URL: {
+ GetUrlData param = (GetUrlData) msg.obj;
+ loadUrl(param.mUrl, param.mExtraHeaders);
break;
+ }
case POST_URL: {
PostUrlData param = (PostUrlData) msg.obj;
@@ -924,7 +983,7 @@ final class WebViewCore {
loadParams.mData,
loadParams.mMimeType,
loadParams.mEncoding,
- loadParams.mFailUrl);
+ loadParams.mHistoryUrl);
break;
case STOP_LOADING:
@@ -1000,23 +1059,15 @@ final class WebViewCore {
Process.setThreadPriority(mTid,
Process.THREAD_PRIORITY_BACKGROUND);
pauseTimers();
- if (CacheManager.disableTransaction()) {
- WebCoreThread.mCacheTickersBlocked = true;
- sWebCoreHandler.removeMessages(
- WebCoreThread.CACHE_TICKER);
- }
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_PAUSE_CACHE_TRANSACTION);
break;
case RESUME_TIMERS:
Process.setThreadPriority(mTid, mSavedPriority);
resumeTimers();
- if (CacheManager.enableTransaction()) {
- WebCoreThread.mCacheTickersBlocked = false;
- sWebCoreHandler.sendMessageDelayed(
- sWebCoreHandler.obtainMessage(
- WebCoreThread.CACHE_TICKER),
- WebCoreThread.CACHE_TICKER_INTERVAL);
- }
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_RESUME_CACHE_TRANSACTION);
break;
case ON_PAUSE:
@@ -1032,11 +1083,6 @@ final class WebViewCore {
nativeFreeMemory();
break;
- case PLUGIN_STATE:
- PluginStateData psd = (PluginStateData) msg.obj;
- nativeUpdatePluginState(psd.mFrame, psd.mNode, psd.mState);
- break;
-
case SET_NETWORK_STATE:
if (BrowserFrame.sJavaBridge == null) {
throw new IllegalStateException("No WebView " +
@@ -1046,6 +1092,16 @@ final class WebViewCore {
.setNetworkOnLine(msg.arg1 == 1);
break;
+ case SET_NETWORK_TYPE:
+ if (BrowserFrame.sJavaBridge == null) {
+ throw new IllegalStateException("No WebView " +
+ "has been created in this process!");
+ }
+ Map<String, String> map = (Map<String, String>) msg.obj;
+ BrowserFrame.sJavaBridge
+ .setNetworkType(map.get("type"), map.get("subtype"));
+ break;
+
case CLEAR_CACHE:
clearCache(msg.arg1 == 1);
break;
@@ -1100,9 +1156,11 @@ final class WebViewCore {
TouchEventData ted = (TouchEventData) msg.obj;
Message.obtain(
mWebView.mPrivateHandler,
- WebView.PREVENT_TOUCH_ID, ted.mAction,
+ WebView.PREVENT_TOUCH_ID,
+ ted.mAction,
nativeHandleTouchEvent(ted.mAction, ted.mX,
- ted.mY) ? 1 : 0).sendToTarget();
+ ted.mY, ted.mMetaState) ? 1 : 0,
+ ted.mReprocess ? ted : null).sendToTarget();
break;
}
@@ -1125,6 +1183,11 @@ final class WebViewCore {
mBrowserFrame.documentAsText((Message) msg.obj);
break;
+ case SET_MOVE_FOCUS:
+ CursorData focusData = (CursorData) msg.obj;
+ nativeMoveFocus(focusData.mFrame, focusData.mNode);
+ break;
+
case SET_MOVE_MOUSE:
CursorData cursorData = (CursorData) msg.obj;
nativeMoveMouse(cursorData.mFrame,
@@ -1140,8 +1203,10 @@ final class WebViewCore {
case REQUEST_CURSOR_HREF: {
Message hrefMsg = (Message) msg.obj;
- String res = nativeRetrieveHref(msg.arg1, msg.arg2);
- hrefMsg.getData().putString("url", res);
+ hrefMsg.getData().putString("url",
+ nativeRetrieveHref(msg.arg1, msg.arg2));
+ hrefMsg.getData().putString("title",
+ nativeRetrieveAnchorText(msg.arg1, msg.arg2));
hrefMsg.sendToTarget();
break;
}
@@ -1193,13 +1258,6 @@ final class WebViewCore {
nativeSetBackgroundColor(msg.arg1);
break;
- case GET_SELECTION:
- String str = nativeGetSelection((Region) msg.obj);
- Message.obtain(mWebView.mPrivateHandler
- , WebView.UPDATE_CLIPBOARD, str)
- .sendToTarget();
- break;
-
case DUMP_DOMTREE:
nativeDumpDomTree(msg.arg1 == 1);
break;
@@ -1212,6 +1270,10 @@ final class WebViewCore {
nativeDumpNavTree();
break;
+ case DUMP_V8COUNTERS:
+ nativeDumpV8Counters();
+ break;
+
case SET_JS_FLAGS:
nativeSetJsFlags((String)msg.obj);
break;
@@ -1249,6 +1311,52 @@ final class WebViewCore {
case POPULATE_VISITED_LINKS:
nativeProvideVisitedHistory((String[])msg.obj);
break;
+
+ case VALID_NODE_BOUNDS: {
+ MotionUpData motionUpData = (MotionUpData) msg.obj;
+ if (!nativeValidNodeAndBounds(
+ motionUpData.mFrame, motionUpData.mNode,
+ motionUpData.mBounds)) {
+ nativeUpdateFrameCache();
+ }
+ Message message = mWebView.mPrivateHandler
+ .obtainMessage(WebView.DO_MOTION_UP,
+ motionUpData.mX, motionUpData.mY);
+ mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(
+ message);
+ break;
+ }
+
+ case HIDE_FULLSCREEN:
+ nativeFullScreenPluginHidden(msg.arg1);
+ break;
+
+ case ADD_PACKAGE_NAMES:
+ if (BrowserFrame.sJavaBridge == null) {
+ throw new IllegalStateException("No WebView " +
+ "has been created in this process!");
+ }
+ BrowserFrame.sJavaBridge.addPackageNames(
+ (Set<String>) msg.obj);
+ break;
+
+ case ADD_PACKAGE_NAME:
+ if (BrowserFrame.sJavaBridge == null) {
+ throw new IllegalStateException("No WebView " +
+ "has been created in this process!");
+ }
+ BrowserFrame.sJavaBridge.addPackageName(
+ (String) msg.obj);
+ break;
+
+ case REMOVE_PACKAGE_NAME:
+ if (BrowserFrame.sJavaBridge == null) {
+ throw new IllegalStateException("No WebView " +
+ "has been created in this process!");
+ }
+ BrowserFrame.sJavaBridge.removePackageName(
+ (String) msg.obj);
+ break;
}
}
};
@@ -1392,6 +1500,11 @@ final class WebViewCore {
mEventHub.sendMessage(Message.obtain(null, what, arg1, arg2, obj));
}
+ void sendMessageAtFrontOfQueue(int what, Object obj) {
+ mEventHub.sendMessageAtFrontOfQueue(Message.obtain(
+ null, what, obj));
+ }
+
void sendMessageDelayed(int what, Object obj, long delay) {
mEventHub.sendMessageDelayed(Message.obtain(null, what, obj), delay);
}
@@ -1444,9 +1557,9 @@ final class WebViewCore {
}
}
- private void loadUrl(String url) {
+ private void loadUrl(String url, Map<String, String> extraHeaders) {
if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, " CORE loadUrl " + url);
- mBrowserFrame.loadUrl(url);
+ mBrowserFrame.loadUrl(url, extraHeaders);
}
private void key(KeyEvent evt, boolean isDown) {
@@ -1498,7 +1611,7 @@ final class WebViewCore {
if (mViewportWidth == -1) {
if (mSettings.getLayoutAlgorithm() ==
WebSettings.LayoutAlgorithm.NORMAL) {
- width = DEFAULT_VIEWPORT_WIDTH;
+ width = WebView.DEFAULT_VIEWPORT_WIDTH;
} else {
/*
* if a page's minimum preferred width is wider than the
@@ -1512,8 +1625,9 @@ final class WebViewCore {
* In the worse case, the native width will be adjusted when
* next zoom or screen orientation change happens.
*/
- width = Math.max(w, Math.max(DEFAULT_VIEWPORT_WIDTH,
- nativeGetContentMinPrefWidth()));
+ width = Math.min(WebView.sMaxViewportWidth, Math.max(w,
+ Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
+ nativeGetContentMinPrefWidth())));
}
} else if (mViewportWidth > 0) {
width = Math.max(w, mViewportWidth);
@@ -1596,6 +1710,7 @@ final class WebViewCore {
int mMinPrefWidth;
RestoreState mRestoreState; // only non-null if it is for the first
// picture set after the first layout
+ boolean mFocusSizeChanged;
}
private void webkitDraw() {
@@ -1610,10 +1725,11 @@ final class WebViewCore {
if (mWebView != null) {
// Send the native view size that was used during the most recent
// layout.
+ draw.mFocusSizeChanged = nativeFocusBoundsChanged();
draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (mSettings.getUseWideViewPort()) {
draw.mMinPrefWidth = Math.max(
- mViewportWidth == -1 ? DEFAULT_VIEWPORT_WIDTH
+ mViewportWidth == -1 ? WebView.DEFAULT_VIEWPORT_WIDTH
: (mViewportWidth == 0 ? mCurrentViewWidth
: mViewportWidth),
nativeGetContentMinPrefWidth());
@@ -1646,6 +1762,7 @@ final class WebViewCore {
final DrawFilter mZoomFilter =
new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
+ // If we need to trade better quality for speed, set mScrollFilter to null
final DrawFilter mScrollFilter =
new PaintFlagsDrawFilter(SCROLL_BITS, 0);
@@ -1679,49 +1796,47 @@ final class WebViewCore {
return result;
}
- static void pauseUpdate(WebViewCore core) {
+ static void reducePriority() {
// remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.REDUCE_PRIORITY));
- // Note: there is one possible failure mode. If pauseUpdate() is called
- // from UI thread while in webcore thread WEBKIT_DRAW is just pulled out
- // of the queue and about to be executed. mDrawIsScheduled may be set to
- // false in webkitDraw(). So update won't be blocked. But at least the
- // webcore thread priority is still lowered.
- if (core != null) {
- synchronized (core) {
- core.mDrawIsPaused = true;
- core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
- }
- }
}
- static void resumeUpdate(WebViewCore core) {
+ static void resumePriority() {
// remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
sWebCoreHandler.sendMessageAtFrontOfQueue(sWebCoreHandler
.obtainMessage(WebCoreThread.RESUME_PRIORITY));
+ }
+
+ static void pauseUpdatePicture(WebViewCore core) {
+ // Note: there is one possible failure mode. If pauseUpdatePicture() is
+ // called from UI thread while WEBKIT_DRAW is just pulled out of the
+ // queue in WebCore thread to be executed. Then update won't be blocked.
if (core != null) {
synchronized (core) {
- core.mDrawIsScheduled = false;
- core.mDrawIsPaused = false;
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "resumeUpdate");
- core.contentDraw();
+ core.mDrawIsPaused = true;
+ if (core.mDrawIsScheduled) {
+ core.mEventHub.removeMessages(EventHub.WEBKIT_DRAW);
+ }
}
}
- }
- static void startCacheTransaction() {
- sWebCoreHandler.sendMessage(sWebCoreHandler
- .obtainMessage(WebCoreThread.RESUME_CACHE_TICKER));
}
- static void endCacheTransaction() {
- sWebCoreHandler.sendMessage(sWebCoreHandler
- .obtainMessage(WebCoreThread.BLOCK_CACHE_TICKER));
+ static void resumeUpdatePicture(WebViewCore core) {
+ if (core != null) {
+ synchronized (core) {
+ core.mDrawIsPaused = false;
+ if (core.mDrawIsScheduled) {
+ core.mDrawIsScheduled = false;
+ core.contentDraw();
+ }
+ }
+ }
}
//////////////////////////////////////////////////////////////////////////
@@ -1750,10 +1865,9 @@ final class WebViewCore {
}
// only fire an event if this is our first request
synchronized (this) {
- if (mDrawIsPaused || mDrawIsScheduled) {
- return;
- }
+ if (mDrawIsScheduled) return;
mDrawIsScheduled = true;
+ if (mDrawIsPaused) return;
mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
}
}
@@ -1829,9 +1943,10 @@ final class WebViewCore {
sendUpdateTextEntry();
// as CacheManager can behave based on database transaction, we need to
// call tick() to trigger endTransaction
- sWebCoreHandler.removeMessages(WebCoreThread.CACHE_TICKER);
- sWebCoreHandler.sendMessage(sWebCoreHandler
- .obtainMessage(WebCoreThread.CACHE_TICKER));
+ WebViewWorker.getHandler().removeMessages(
+ WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
+ WebViewWorker.getHandler().sendEmptyMessage(
+ WebViewWorker.MSG_CACHE_TRANSACTION_TICKER);
contentDraw();
}
@@ -1847,6 +1962,33 @@ final class WebViewCore {
}
}
+ private static boolean mRepaintScheduled = false;
+
+ /*
+ * Called by the WebView thread
+ */
+ /* package */ void signalRepaintDone() {
+ mRepaintScheduled = false;
+ }
+
+ // called by JNI
+ private void sendImmediateRepaint() {
+ if (mWebView != null && !mRepaintScheduled) {
+ mRepaintScheduled = true;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.IMMEDIATE_REPAINT_MSG_ID).sendToTarget();
+ }
+ }
+
+ // called by JNI
+ private void setRootLayer(int layer) {
+ if (mWebView != null) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.SET_ROOT_LAYER_MSG_ID,
+ layer, 0).sendToTarget();
+ }
+ }
+
/* package */ WebView getWebView() {
return mWebView;
}
@@ -1863,7 +2005,14 @@ final class WebViewCore {
if (mWebView == null) return;
- setupViewport(standardLoad || mRestoredScale > 0);
+ boolean updateRestoreState = standardLoad || mRestoredScale > 0;
+ setupViewport(updateRestoreState);
+ // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
+ // be called after the WebView restore the state. If updateRestoreState
+ // is false, start to draw now as it is ready.
+ if (!updateRestoreState) {
+ mWebView.mViewManager.postReadyToDrawAll();
+ }
// reset the scroll position, the restored offset and scales
mWebkitScrollX = mWebkitScrollY = mRestoredX = mRestoredY
@@ -2029,11 +2178,25 @@ final class WebViewCore {
// know the exact scale. If mRestoredScale is non-zero, use it;
// otherwise just use mTextWrapScale as the initial scale.
data.mScale = mRestoreState.mViewScale == 0
- ? (mRestoredScale > 0 ? mRestoredScale
+ ? (mRestoredScale > 0 ? mRestoredScale / 100.0f
: mRestoreState.mTextWrapScale)
: mRestoreState.mViewScale;
+ if (DebugFlags.WEB_VIEW_CORE) {
+ Log.v(LOGTAG, "setupViewport"
+ + " mRestoredScale=" + mRestoredScale
+ + " mViewScale=" + mRestoreState.mViewScale
+ + " mTextWrapScale=" + mRestoreState.mTextWrapScale
+ );
+ }
data.mWidth = Math.round(webViewWidth / data.mScale);
- data.mHeight = mCurrentViewHeight * data.mWidth / viewportWidth;
+ // We may get a call here when mCurrentViewHeight == 0 if webcore completes the
+ // first layout before we sync our webview dimensions to it. In that case, we
+ // request the real height of the webview. This is not a perfect solution as we
+ // are calling a WebView method from the WebCore thread. But this is preferable
+ // to syncing an incorrect height.
+ data.mHeight = mCurrentViewHeight == 0 ?
+ Math.round(mWebView.getViewHeight() / data.mScale)
+ : mCurrentViewHeight * data.mWidth / viewportWidth;
data.mTextWrapWidth = Math.round(webViewWidth
/ mRestoreState.mTextWrapScale);
data.mIgnoreHeight = false;
@@ -2103,8 +2266,15 @@ final class WebViewCore {
WebView.CLEAR_TEXT_ENTRY).sendToTarget();
}
- private native void nativeUpdateFrameCacheIfLoading();
+ // called by JNI
+ private void sendFindAgain() {
+ if (mWebView == null) return;
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.FIND_AGAIN).sendToTarget();
+ }
+ private native void nativeUpdateFrameCacheIfLoading();
+ private native String nativeRequestLabel(int framePtr, int nodePtr);
/**
* Scroll the focused textfield to (xPercent, y) in document space
*/
@@ -2116,7 +2286,7 @@ final class WebViewCore {
private native void nativeSetGlobalBounds(int x, int y, int w, int h);
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int[] selectedArray) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selectedArray);
@@ -2124,7 +2294,7 @@ final class WebViewCore {
}
// called by JNI
- private void requestListBox(String[] array, boolean[] enabledArray,
+ private void requestListBox(String[] array, int[] enabledArray,
int selection) {
if (mWebView != null) {
mWebView.requestListBox(array, enabledArray, selection);
@@ -2133,6 +2303,17 @@ final class WebViewCore {
}
// called by JNI
+ private void requestKeyboardWithSelection(int pointer, int selStart,
+ int selEnd, int textGeneration) {
+ if (mWebView != null) {
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, pointer,
+ textGeneration, new TextSelectionData(selStart, selEnd))
+ .sendToTarget();
+ }
+ }
+
+ // called by JNI
private void requestKeyboard(boolean showKeyboard) {
if (mWebView != null) {
Message.obtain(mWebView.mPrivateHandler,
@@ -2141,55 +2322,90 @@ final class WebViewCore {
}
}
- // called by JNI. PluginWidget function to launch an activity and overlays
- // the activity with the View provided by the plugin class.
- private void startFullScreenPluginActivity(String libName, String clsName, int npp) {
+ // called by JNI
+ private Context getContext() {
+ return mContext;
+ }
+
+ // called by JNI
+ private Class<?> getPluginClass(String libName, String clsName) {
+
if (mWebView == null) {
- return;
+ return null;
}
+
+ PluginManager pluginManager = PluginManager.getInstance(null);
- String pkgName = PluginManager.getInstance(null).getPluginsAPKName(libName);
+ String pkgName = pluginManager.getPluginsAPKName(libName);
if (pkgName == null) {
Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
+ return null;
+ }
+
+ try {
+ return pluginManager.getPluginClass(pkgName, clsName);
+ } catch (NameNotFoundException e) {
+ Log.e(LOGTAG, "Unable to find plugin classloader for the apk (" + pkgName + ")");
+ } catch (ClassNotFoundException e) {
+ Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
+ ") in the apk (" + pkgName + ")");
+ }
+
+ return null;
+ }
+
+ // 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) {
+ if (mWebView == null) {
return;
}
- Intent intent = new Intent("android.intent.webkit.PLUGIN");
- intent.putExtra(PluginActivity.INTENT_EXTRA_PACKAGE_NAME, pkgName);
- intent.putExtra(PluginActivity.INTENT_EXTRA_CLASS_NAME, clsName);
- intent.putExtra(PluginActivity.INTENT_EXTRA_NPP_INSTANCE, npp);
- mWebView.getContext().startActivity(intent);
+ Message message = mWebView.mPrivateHandler.obtainMessage(WebView.SHOW_FULLSCREEN);
+ message.obj = childView.mView;
+ message.arg1 = npp;
+ message.sendToTarget();
+ }
+
+ // called by JNI
+ private void hideFullScreenPlugin() {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.mPrivateHandler.obtainMessage(WebView.HIDE_FULLSCREEN)
+ .sendToTarget();
}
// called by JNI. PluginWidget functions for creating an embedded View for
// the surface drawing model.
- private ViewManager.ChildView createSurface(String libName, String clsName,
- int npp, int x, int y, int width, int height) {
+ private ViewManager.ChildView addSurface(View pluginView, int x, int y,
+ int width, int height) {
if (mWebView == null) {
return null;
}
- String pkgName = PluginManager.getInstance(null).getPluginsAPKName(libName);
- if (pkgName == null) {
- Log.w(LOGTAG, "Unable to resolve " + libName + " to a plugin APK");
+ if (pluginView == null) {
+ Log.e(LOGTAG, "Attempted to add an empty plugin view to the view hierarchy");
return null;
}
- PluginStub stub =PluginUtil.getPluginStub(mWebView.getContext(),pkgName, clsName);
- if (stub == null) {
- Log.e(LOGTAG, "Unable to find plugin class (" + clsName +
- ") in the apk (" + pkgName + ")");
- return null;
- }
+ // ensures the view system knows the view can redraw itself
+ pluginView.setWillNotDraw(false);
- View pluginView = stub.getEmbeddedView(npp, mWebView.getContext());
+ if(pluginView instanceof SurfaceView)
+ ((SurfaceView)pluginView).setZOrderOnTop(true);
ViewManager.ChildView view = mWebView.mViewManager.createView();
view.mView = pluginView;
view.attachView(x, y, width, height);
return view;
}
-
+
+ private void updateSurface(ViewManager.ChildView childView, int x, int y,
+ int width, int height) {
+ childView.attachView(x, y, width, height);
+ }
+
private void destroySurface(ViewManager.ChildView childView) {
childView.removeView();
}
@@ -2228,7 +2444,29 @@ final class WebViewCore {
}
}
+ // called by JNI
+ private void centerFitRect(int x, int y, int width, int height) {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.mPrivateHandler.obtainMessage(WebView.CENTER_FIT_RECT,
+ new Rect(x, y, x + width, y + height)).sendToTarget();
+ }
+
+ // called by JNI
+ private void setScrollbarModes(int hMode, int vMode) {
+ if (mWebView == null) {
+ return;
+ }
+ mWebView.mPrivateHandler.obtainMessage(WebView.SET_SCROLLBAR_MODES,
+ hMode, vMode).sendToTarget();
+ }
+
private native void nativePause();
private native void nativeResume();
private native void nativeFreeMemory();
+ private native void nativeFullScreenPluginHidden(int npp);
+ private native boolean nativeValidNodeAndBounds(int frame, int node,
+ Rect bounds);
+
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 110e4f8..b18419d 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -19,6 +19,7 @@ package android.webkit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
@@ -50,9 +51,10 @@ public class WebViewDatabase {
// 7 -> 8 Move cache to its own db
// 8 -> 9 Store both scheme and host when storing passwords
// 9 -> 10 Update httpauth table UNIQUE
- private static final int CACHE_DATABASE_VERSION = 3;
+ private static final int CACHE_DATABASE_VERSION = 4;
// 1 -> 2 Add expires String
// 2 -> 3 Add content-disposition
+ // 3 -> 4 Add crossdomain (For x-permitted-cross-domain-policies header)
private static WebViewDatabase mInstance = null;
@@ -125,6 +127,8 @@ public class WebViewDatabase {
private static final String CACHE_CONTENTDISPOSITION_COL = "contentdisposition";
+ private static final String CACHE_CROSSDOMAIN_COL = "crossdomain";
+
// column id strings for "password" table
private static final String PASSWORD_HOST_COL = "host";
@@ -165,6 +169,7 @@ public class WebViewDatabase {
private static int mCacheLocationColIndex;
private static int mCacheContentLengthColIndex;
private static int mCacheContentDispositionColIndex;
+ private static int mCacheCrossDomainColIndex;
private static int mCacheTransactionRefcount;
@@ -234,6 +239,13 @@ public class WebViewDatabase {
}
if (mCacheDatabase != null) {
+ // use read_uncommitted to speed up READ
+ mCacheDatabase.execSQL("PRAGMA read_uncommitted = true;");
+ // as only READ can be called in the non-WebViewWorkerThread,
+ // and read_uncommitted is used, we can turn off database lock
+ // to use transaction.
+ mCacheDatabase.setLockingEnabled(false);
+
// use InsertHelper for faster insertion
mCacheInserter = new DatabaseUtils.InsertHelper(mCacheDatabase,
"cache");
@@ -261,6 +273,8 @@ public class WebViewDatabase {
.getColumnIndex(CACHE_CONTENTLENGTH_COL);
mCacheContentDispositionColIndex = mCacheInserter
.getColumnIndex(CACHE_CONTENTDISPOSITION_COL);
+ mCacheCrossDomainColIndex = mCacheInserter
+ .getColumnIndex(CACHE_CROSSDOMAIN_COL);
}
}
@@ -370,6 +384,7 @@ public class WebViewDatabase {
+ " TEXT," + CACHE_HTTP_STATUS_COL + " INTEGER, "
+ CACHE_LOCATION_COL + " TEXT, " + CACHE_CONTENTLENGTH_COL
+ " INTEGER, " + CACHE_CONTENTDISPOSITION_COL + " TEXT, "
+ + CACHE_CROSSDOMAIN_COL + " TEXT,"
+ " UNIQUE (" + CACHE_URL_COL + ") ON CONFLICT REPLACE);");
mCacheDatabase.execSQL("CREATE INDEX cacheUrlIndex ON cache ("
+ CACHE_URL_COL + ")");
@@ -381,10 +396,17 @@ public class WebViewDatabase {
return false;
}
- Cursor cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION,
- null, null, null, null, null);
- boolean ret = cursor.moveToFirst() == true;
- cursor.close();
+ Cursor cursor = null;
+ boolean ret = false;
+ try {
+ cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION,
+ null, null, null, null, null);
+ ret = cursor.moveToFirst() == true;
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "hasEntries", e);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
return ret;
}
@@ -412,33 +434,39 @@ public class WebViewDatabase {
};
final String selection = "(" + COOKIES_DOMAIN_COL
+ " GLOB '*' || ?)";
- Cursor cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
- columns, selection, new String[] { domain }, null, null,
- null);
- if (cursor.moveToFirst()) {
- int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
- int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
- int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
- int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
- int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
- int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
- do {
- Cookie cookie = new Cookie();
- cookie.domain = cursor.getString(domainCol);
- cookie.path = cursor.getString(pathCol);
- cookie.name = cursor.getString(nameCol);
- cookie.value = cursor.getString(valueCol);
- if (cursor.isNull(expiresCol)) {
- cookie.expires = -1;
- } else {
- cookie.expires = cursor.getLong(expiresCol);
- }
- cookie.secure = cursor.getShort(secureCol) != 0;
- cookie.mode = Cookie.MODE_NORMAL;
- list.add(cookie);
- } while (cursor.moveToNext());
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(mTableNames[TABLE_COOKIES_ID],
+ columns, selection, new String[] { domain }, null, null,
+ null);
+ if (cursor.moveToFirst()) {
+ int domainCol = cursor.getColumnIndex(COOKIES_DOMAIN_COL);
+ int pathCol = cursor.getColumnIndex(COOKIES_PATH_COL);
+ int nameCol = cursor.getColumnIndex(COOKIES_NAME_COL);
+ int valueCol = cursor.getColumnIndex(COOKIES_VALUE_COL);
+ int expiresCol = cursor.getColumnIndex(COOKIES_EXPIRES_COL);
+ int secureCol = cursor.getColumnIndex(COOKIES_SECURE_COL);
+ do {
+ Cookie cookie = new Cookie();
+ cookie.domain = cursor.getString(domainCol);
+ cookie.path = cursor.getString(pathCol);
+ cookie.name = cursor.getString(nameCol);
+ cookie.value = cursor.getString(valueCol);
+ if (cursor.isNull(expiresCol)) {
+ cookie.expires = -1;
+ } else {
+ cookie.expires = cursor.getLong(expiresCol);
+ }
+ cookie.secure = cursor.getShort(secureCol) != 0;
+ cookie.mode = Cookie.MODE_NORMAL;
+ list.add(cookie);
+ } while (cursor.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getCookiesForDomain", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return list;
}
}
@@ -548,19 +576,33 @@ public class WebViewDatabase {
}
//
- // cache functions, can only be called from WebCoreThread
+ // cache functions
//
+ // only called from WebViewWorkerThread
boolean startCacheTransaction() {
if (++mCacheTransactionRefcount == 1) {
+ if (!Thread.currentThread().equals(
+ WebViewWorker.getHandler().getLooper().getThread())) {
+ Log.w(LOGTAG, "startCacheTransaction should be called from "
+ + "WebViewWorkerThread instead of from "
+ + Thread.currentThread().getName());
+ }
mCacheDatabase.beginTransaction();
return true;
}
return false;
}
+ // only called from WebViewWorkerThread
boolean endCacheTransaction() {
if (--mCacheTransactionRefcount == 0) {
+ if (!Thread.currentThread().equals(
+ WebViewWorker.getHandler().getLooper().getThread())) {
+ Log.w(LOGTAG, "endCacheTransaction should be called from "
+ + "WebViewWorkerThread instead of from "
+ + Thread.currentThread().getName());
+ }
try {
mCacheDatabase.setTransactionSuccessful();
} finally {
@@ -582,12 +624,12 @@ public class WebViewDatabase {
return null;
}
- Cursor cursor = mCacheDatabase.rawQuery("SELECT filepath, lastmodify, etag, expires, "
- + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
- + "contentdisposition FROM cache WHERE url = ?",
- new String[] { url });
-
+ Cursor cursor = null;
+ final String query = "SELECT filepath, lastmodify, etag, expires, "
+ + "expiresstring, mimetype, encoding, httpstatus, location, contentlength, "
+ + "contentdisposition, crossdomain FROM cache WHERE url = ?";
try {
+ cursor = mCacheDatabase.rawQuery(query, new String[] { url });
if (cursor.moveToFirst()) {
CacheResult ret = new CacheResult();
ret.localPath = cursor.getString(0);
@@ -601,8 +643,11 @@ public class WebViewDatabase {
ret.location = cursor.getString(8);
ret.contentLength = cursor.getLong(9);
ret.contentdisposition = cursor.getString(10);
+ ret.crossDomain = cursor.getString(11);
return ret;
}
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getCache", e);
} finally {
if (cursor != null) cursor.close();
}
@@ -647,6 +692,7 @@ public class WebViewDatabase {
mCacheInserter.bind(mCacheContentLengthColIndex, c.contentLength);
mCacheInserter.bind(mCacheContentDispositionColIndex,
c.contentdisposition);
+ mCacheInserter.bind(mCacheCrossDomainColIndex, c.crossDomain);
mCacheInserter.execute();
}
@@ -666,64 +712,108 @@ public class WebViewDatabase {
return false;
}
- Cursor cursor = mCacheDatabase.query("cache", ID_PROJECTION,
- null, null, null, null, null);
- boolean ret = cursor.moveToFirst() == true;
- cursor.close();
+ Cursor cursor = null;
+ boolean ret = false;
+ try {
+ cursor = mCacheDatabase.query("cache", ID_PROJECTION,
+ null, null, null, null, null);
+ ret = cursor.moveToFirst() == true;
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "hasCache", e);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
return ret;
}
long getCacheTotalSize() {
long size = 0;
- Cursor cursor = mCacheDatabase.rawQuery(
- "SELECT SUM(contentlength) as sum FROM cache", null);
- if (cursor.moveToFirst()) {
- size = cursor.getLong(0);
+ Cursor cursor = null;
+ final String query = "SELECT SUM(contentlength) as sum FROM cache";
+ try {
+ cursor = mCacheDatabase.rawQuery(query, null);
+ if (cursor.moveToFirst()) {
+ size = cursor.getLong(0);
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getCacheTotalSize", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return size;
}
- ArrayList<String> trimCache(long amount) {
+ List<String> trimCache(long amount) {
ArrayList<String> pathList = new ArrayList<String>(100);
- Cursor cursor = mCacheDatabase.rawQuery(
- "SELECT contentlength, filepath FROM cache ORDER BY expires ASC",
- null);
- if (cursor.moveToFirst()) {
- int batchSize = 100;
- StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
- pathStr.append("DELETE FROM cache WHERE filepath IN (?");
- for (int i = 1; i < batchSize; i++) {
- pathStr.append(", ?");
- }
- pathStr.append(")");
- SQLiteStatement statement = mCacheDatabase.compileStatement(pathStr
- .toString());
- // as bindString() uses 1-based index, initialize index to 1
- int index = 1;
- do {
- long length = cursor.getLong(0);
- if (length == 0) {
- continue;
+ Cursor cursor = null;
+ final String query = "SELECT contentlength, filepath FROM cache ORDER BY expires ASC";
+ try {
+ cursor = mCacheDatabase.rawQuery(query, null);
+ if (cursor.moveToFirst()) {
+ int batchSize = 100;
+ StringBuilder pathStr = new StringBuilder(20 + 16 * batchSize);
+ pathStr.append("DELETE FROM cache WHERE filepath IN (?");
+ for (int i = 1; i < batchSize; i++) {
+ pathStr.append(", ?");
}
- amount -= length;
- String filePath = cursor.getString(1);
- statement.bindString(index, filePath);
- pathList.add(filePath);
- if (index++ == batchSize) {
- statement.execute();
- statement.clearBindings();
- index = 1;
+ pathStr.append(")");
+ SQLiteStatement statement = null;
+ try {
+ statement = mCacheDatabase.compileStatement(
+ pathStr.toString());
+ // as bindString() uses 1-based index, initialize index to 1
+ int index = 1;
+ do {
+ long length = cursor.getLong(0);
+ if (length == 0) {
+ continue;
+ }
+ amount -= length;
+ String filePath = cursor.getString(1);
+ statement.bindString(index, filePath);
+ pathList.add(filePath);
+ if (index++ == batchSize) {
+ statement.execute();
+ statement.clearBindings();
+ index = 1;
+ }
+ } while (cursor.moveToNext() && amount > 0);
+ if (index > 1) {
+ // there may be old bindings from the previous statement
+ // if index is less than batchSize, which is Ok.
+ statement.execute();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "trimCache SQLiteStatement", e);
+ } finally {
+ if (statement != null) statement.close();
}
- } while (cursor.moveToNext() && amount > 0);
- if (index > 1) {
- // there may be old bindings from the previous statement if
- // index is less than batchSize, which is Ok.
- statement.execute();
}
- statement.close();
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "trimCache Cursor", e);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ return pathList;
+ }
+
+ List<String> getAllCacheFileNames() {
+ ArrayList<String> pathList = null;
+ Cursor cursor = null;
+ try {
+ cursor = mCacheDatabase.rawQuery("SELECT filepath FROM cache",
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ pathList = new ArrayList<String>(cursor.getCount());
+ do {
+ pathList.add(cursor.getString(0));
+ } while (cursor.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getAllCacheFileNames", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return pathList;
}
@@ -773,17 +863,23 @@ public class WebViewDatabase {
final String selection = "(" + PASSWORD_HOST_COL + " == ?)";
synchronized (mPasswordLock) {
String[] ret = null;
- Cursor cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
- columns, selection, new String[] { schemePlusHost }, null,
- null, null);
- if (cursor.moveToFirst()) {
- ret = new String[2];
- ret[0] = cursor.getString(
- cursor.getColumnIndex(PASSWORD_USERNAME_COL));
- ret[1] = cursor.getString(
- cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID],
+ columns, selection, new String[] { schemePlusHost }, null,
+ null, null);
+ if (cursor.moveToFirst()) {
+ ret = new String[2];
+ ret[0] = cursor.getString(
+ cursor.getColumnIndex(PASSWORD_USERNAME_COL));
+ ret[1] = cursor.getString(
+ cursor.getColumnIndex(PASSWORD_PASSWORD_COL));
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getUsernamePassword", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return ret;
}
}
@@ -864,17 +960,23 @@ public class WebViewDatabase {
+ HTTPAUTH_REALM_COL + " == ?)";
synchronized (mHttpAuthLock) {
String[] ret = null;
- Cursor cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
- columns, selection, new String[] { host, realm }, null,
- null, null);
- if (cursor.moveToFirst()) {
- ret = new String[2];
- ret[0] = cursor.getString(
- cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
- ret[1] = cursor.getString(
- cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID],
+ columns, selection, new String[] { host, realm }, null,
+ null, null);
+ if (cursor.moveToFirst()) {
+ ret = new String[2];
+ ret[0] = cursor.getString(
+ cursor.getColumnIndex(HTTPAUTH_USERNAME_COL));
+ ret[1] = cursor.getString(
+ cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL));
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getHttpAuthUsernamePassword", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return ret;
}
}
@@ -922,18 +1024,24 @@ public class WebViewDatabase {
final String selection = "(" + FORMURL_URL_COL + " == ?)";
synchronized (mFormLock) {
long urlid = -1;
- Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
- ID_PROJECTION, selection, new String[] { url }, null, null,
- null);
- if (cursor.moveToFirst()) {
- urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
- } else {
- ContentValues c = new ContentValues();
- c.put(FORMURL_URL_COL, url);
- urlid = mDatabase.insert(
- mTableNames[TABLE_FORMURL_ID], null, c);
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
+ ID_PROJECTION, selection, new String[] { url }, null, null,
+ null);
+ if (cursor.moveToFirst()) {
+ urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
+ } else {
+ ContentValues c = new ContentValues();
+ c.put(FORMURL_URL_COL, url);
+ urlid = mDatabase.insert(
+ mTableNames[TABLE_FORMURL_ID], null, c);
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "setFormData", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
if (urlid >= 0) {
Set<Entry<String, String>> set = formdata.entrySet();
Iterator<Entry<String, String>> iter = set.iterator();
@@ -966,27 +1074,39 @@ public class WebViewDatabase {
final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND ("
+ FORMDATA_NAME_COL + " == ?)";
synchronized (mFormLock) {
- Cursor cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
- ID_PROJECTION, urlSelection, new String[] { url }, null,
- null, null);
- if (cursor.moveToFirst()) {
- long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
- Cursor dataCursor = mDatabase.query(
- mTableNames[TABLE_FORMDATA_ID],
- new String[] { ID_COL, FORMDATA_VALUE_COL },
- dataSelection,
- new String[] { Long.toString(urlid), name }, null,
+ Cursor cursor = null;
+ try {
+ cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
+ ID_PROJECTION, urlSelection, new String[] { url }, null,
null, null);
- if (dataCursor.moveToFirst()) {
- int valueCol =
- dataCursor.getColumnIndex(FORMDATA_VALUE_COL);
- do {
- values.add(dataCursor.getString(valueCol));
- } while (dataCursor.moveToNext());
+ if (cursor.moveToFirst()) {
+ long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
+ Cursor dataCursor = null;
+ try {
+ dataCursor = mDatabase.query(
+ mTableNames[TABLE_FORMDATA_ID],
+ new String[] { ID_COL, FORMDATA_VALUE_COL },
+ dataSelection,
+ new String[] { Long.toString(urlid), name },
+ null, null, null);
+ if (dataCursor.moveToFirst()) {
+ int valueCol = dataCursor.getColumnIndex(
+ FORMDATA_VALUE_COL);
+ do {
+ values.add(dataCursor.getString(valueCol));
+ } while (dataCursor.moveToNext());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getFormData dataCursor", e);
+ } finally {
+ if (dataCursor != null) dataCursor.close();
+ }
}
- dataCursor.close();
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getFormData cursor", e);
+ } finally {
+ if (cursor != null) cursor.close();
}
- cursor.close();
return values;
}
}
diff --git a/core/java/android/webkit/WebViewWorker.java b/core/java/android/webkit/WebViewWorker.java
new file mode 100644
index 0000000..c488150
--- /dev/null
+++ b/core/java/android/webkit/WebViewWorker.java
@@ -0,0 +1,223 @@
+/*
+ * 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.webkit;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.net.http.Headers;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * WebViewWorker executes in a separate thread other than UI and WebViewCore. To
+ * avoid blocking UI or WebKit's execution, the caller can send a message to
+ * WebViewWorker.getHandler() and it will be handled in the WebViewWorkerThread.
+ */
+final class WebViewWorker extends Handler {
+
+ private static final String THREAD_NAME = "WebViewWorkerThread";
+
+ private static WebViewWorker sWorkerHandler;
+
+ private static Map<LoadListener, CacheManager.CacheResult> mCacheResultMap
+ = new HashMap<LoadListener, CacheManager.CacheResult>();
+
+ /**
+ * Package level class to be used while creating a cache entry.
+ */
+ static class CacheCreateData {
+ LoadListener mListener;
+ String mUrl;
+ String mMimeType;
+ int mStatusCode;
+ long mPostId;
+ Headers mHeaders;
+ }
+
+ /**
+ * Package level class to be used while saving a cache entry.
+ */
+ static class CacheSaveData {
+ LoadListener mListener;
+ String mUrl;
+ long mPostId;
+ }
+
+ /**
+ * Package level class to be used while updating a cache entry's encoding.
+ */
+ static class CacheEncoding {
+ LoadListener mListener;
+ String mEncoding;
+ }
+
+ /**
+ * Package level class to be used while appending data to a cache entry.
+ */
+ static class CacheData {
+ LoadListener mListener;
+ ByteArrayBuilder.Chunk mChunk;
+ }
+
+ static synchronized WebViewWorker getHandler() {
+ if (sWorkerHandler == null) {
+ HandlerThread thread = new HandlerThread(THREAD_NAME,
+ android.os.Process.THREAD_PRIORITY_DEFAULT
+ + android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ thread.start();
+ sWorkerHandler = new WebViewWorker(thread.getLooper());
+ }
+ return sWorkerHandler;
+ }
+
+ private WebViewWorker(Looper looper) {
+ super(looper);
+ }
+
+ // trigger transaction once a minute
+ private static final int CACHE_TRANSACTION_TICKER_INTERVAL = 60 * 1000;
+
+ private static boolean mCacheTickersBlocked = true;
+
+ // message ids
+ static final int MSG_ADD_STREAMLOADER = 101;
+ static final int MSG_ADD_HTTPLOADER = 102;
+ static final int MSG_CREATE_CACHE = 103;
+ static final int MSG_UPDATE_CACHE_ENCODING = 104;
+ static final int MSG_APPEND_CACHE = 105;
+ static final int MSG_SAVE_CACHE = 106;
+ static final int MSG_REMOVE_CACHE = 107;
+ static final int MSG_TRIM_CACHE = 108;
+ static final int MSG_CLEAR_CACHE = 109;
+ static final int MSG_CACHE_TRANSACTION_TICKER = 110;
+ static final int MSG_PAUSE_CACHE_TRANSACTION = 111;
+ static final int MSG_RESUME_CACHE_TRANSACTION = 112;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_ADD_STREAMLOADER: {
+ StreamLoader loader = (StreamLoader) msg.obj;
+ loader.load();
+ break;
+ }
+ case MSG_ADD_HTTPLOADER: {
+ FrameLoader loader = (FrameLoader) msg.obj;
+ loader.handleHTTPLoad();
+ break;
+ }
+ case MSG_CREATE_CACHE: {
+ CacheCreateData data = (CacheCreateData) msg.obj;
+ CacheManager.CacheResult cache = CacheManager.createCacheFile(
+ data.mUrl, data.mStatusCode, data.mHeaders,
+ data.mMimeType, data.mPostId, false);
+ if (cache != null) {
+ mCacheResultMap.put(data.mListener, cache);
+ } else {
+ mCacheResultMap.remove(data.mListener);
+ }
+ break;
+ }
+ case MSG_UPDATE_CACHE_ENCODING: {
+ CacheEncoding data = (CacheEncoding) msg.obj;
+ CacheManager.CacheResult cache = mCacheResultMap
+ .get(data.mListener);
+ if (cache != null) {
+ cache.encoding = data.mEncoding;
+ }
+ break;
+ }
+ case MSG_APPEND_CACHE: {
+ CacheData data = (CacheData) msg.obj;
+ CacheManager.CacheResult cache = mCacheResultMap
+ .get(data.mListener);
+ if (cache != null) {
+ cache.contentLength += data.mChunk.mLength;
+ if (cache.contentLength > CacheManager.CACHE_MAX_SIZE) {
+ CacheManager.cleanupCacheFile(cache);
+ mCacheResultMap.remove(data.mListener);
+ } else {
+ try {
+ cache.outStream.write(data.mChunk.mArray, 0,
+ data.mChunk.mLength);
+ } catch (IOException e) {
+ CacheManager.cleanupCacheFile(cache);
+ mCacheResultMap.remove(data.mListener);
+ }
+ }
+ }
+ data.mChunk.release();
+ break;
+ }
+ case MSG_SAVE_CACHE: {
+ CacheSaveData data = (CacheSaveData) msg.obj;
+ CacheManager.CacheResult cache = mCacheResultMap
+ .get(data.mListener);
+ if (cache != null) {
+ CacheManager.saveCacheFile(data.mUrl, data.mPostId, cache);
+ mCacheResultMap.remove(data.mListener);
+ }
+ break;
+ }
+ case MSG_REMOVE_CACHE: {
+ LoadListener listener = (LoadListener) msg.obj;
+ CacheManager.CacheResult cache = mCacheResultMap.get(listener);
+ if (cache != null) {
+ CacheManager.cleanupCacheFile(cache);
+ mCacheResultMap.remove(listener);
+ }
+ break;
+ }
+ case MSG_TRIM_CACHE: {
+ CacheManager.trimCacheIfNeeded();
+ break;
+ }
+ case MSG_CLEAR_CACHE: {
+ CacheManager.clearCache();
+ break;
+ }
+ case MSG_CACHE_TRANSACTION_TICKER: {
+ if (!mCacheTickersBlocked) {
+ CacheManager.endTransaction();
+ CacheManager.startTransaction();
+ sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
+ CACHE_TRANSACTION_TICKER_INTERVAL);
+ }
+ break;
+ }
+ case MSG_PAUSE_CACHE_TRANSACTION: {
+ if (CacheManager.disableTransaction()) {
+ mCacheTickersBlocked = true;
+ removeMessages(MSG_CACHE_TRANSACTION_TICKER);
+ }
+ break;
+ }
+ case MSG_RESUME_CACHE_TRANSACTION: {
+ if (CacheManager.enableTransaction()) {
+ mCacheTickersBlocked = false;
+ sendEmptyMessageDelayed(MSG_CACHE_TRANSACTION_TICKER,
+ CACHE_TRANSACTION_TICKER_INTERVAL);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 271989a..48e7f79 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.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.Canvas;
@@ -30,6 +32,7 @@ import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -41,14 +44,12 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
-import android.view.ContextMenu.ContextMenuInfo;
-
-import com.android.internal.R;
import java.util.ArrayList;
import java.util.List;
@@ -120,18 +121,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Indicates the touch gesture is a scroll
*/
static final int TOUCH_MODE_SCROLL = 3;
-
+
/**
* Indicates the view is in the process of being flung
*/
static final int TOUCH_MODE_FLING = 4;
/**
- * Indicates that the user is currently dragging the fast scroll thumb
- */
- static final int TOUCH_MODE_FAST_SCROLL = 5;
-
- /**
* Regular layout - usually an unsolicited layout from the view system
*/
static final int LAYOUT_NORMAL = 0;
@@ -300,6 +296,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Handles one frame of a fling
*/
private FlingRunnable mFlingRunnable;
+
+ /**
+ * Handles scrolling between positions within the list.
+ */
+ private PositionScroller mPositionScroller;
/**
* The offset in pixels form the top of the AdapterView to the top
@@ -366,7 +367,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int mResurrectToPosition = INVALID_POSITION;
private ContextMenuInfo mContextMenuInfo = null;
-
+
/**
* Used to request a layout when we changed touch mode
*/
@@ -440,6 +441,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private Runnable mClearScrollingCache;
private int mMinimumVelocity;
private int mMaximumVelocity;
+
+ final boolean[] mIsScrap = new boolean[1];
+
+ // True when the popup should be hidden because of a call to
+ // dispatchDisplayHint()
+ private boolean mPopupHidden;
+
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
/**
* Interface definition for a callback to be invoked when the list or grid
@@ -1031,7 +1050,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int top = view.getTop();
int height = view.getHeight();
if (height > 0) {
- return Math.max(firstPosition * 100 - (top * 100) / height, 0);
+ return Math.max(firstPosition * 100 - (top * 100) / height +
+ (int)((float)mScrollY / getHeight() * mItemCount * 100), 0);
}
} else {
int index;
@@ -1051,7 +1071,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
protected int computeVerticalScrollRange() {
- return mSmoothScrollbarEnabled ? Math.max(mItemCount * 100, 0) : mItemCount;
+ int result;
+ if (mSmoothScrollbarEnabled) {
+ result = Math.max(mItemCount * 100, 0);
+ } else {
+ result = mItemCount;
+ }
+ return result;
}
@Override
@@ -1110,6 +1136,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
+ if (changed) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ getChildAt(i).forceLayout();
+ }
+ mRecycler.markChildrenDirty();
+ }
+
layoutChildren();
mInLayout = false;
}
@@ -1239,9 +1273,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* converting an old view or making a new one.
*
* @param position The position to display
+ * @param isScrap Array of at least 1 boolean, the first entry will become true if
+ * the returned view was taken from the scrap heap, false if otherwise.
+ *
* @return A view displaying the data associated with the specified position
*/
- View obtainView(int position) {
+ View obtainView(int position, boolean[] isScrap) {
+ isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getScrapView(position);
@@ -1269,6 +1307,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
position, -1);
}
+ } else {
+ isScrap[0] = true;
+ child.dispatchFinishTemporaryDetach();
}
} else {
child = mAdapter.getView(position, null, this);
@@ -1543,6 +1584,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Dismiss the popup in case onSaveInstanceState() was not invoked
dismissPopup();
+ // Detach any view left in the scrap heap
+ mRecycler.clear();
+
final ViewTreeObserver treeObserver = getViewTreeObserver();
if (treeObserver != null) {
treeObserver.removeOnTouchModeChangeListener(this);
@@ -1561,7 +1605,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (!hasWindowFocus) {
setChildrenDrawingCacheEnabled(false);
- removeCallbacks(mFlingRunnable);
+ if (mFlingRunnable != null) {
+ removeCallbacks(mFlingRunnable);
+ // let the fling runnable report it's new state which
+ // should be idle
+ mFlingRunnable.endFling();
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ invalidate();
+ }
+ }
// Always hide the type filter
dismissPopup();
@@ -1570,7 +1623,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mResurrectToPosition = mSelectedPosition;
}
} else {
- if (mFiltered) {
+ if (mFiltered && !mPopupHidden) {
// Show the type filter only if a filter is in effect
showPopup();
}
@@ -1635,10 +1688,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// bail out before bad things happen
if (mDataChanged) return;
- if (mAdapter != null && mItemCount > 0 &&
- mClickMotionPosition < mAdapter.getCount() && sameWindow()) {
- performItemClick(mChild, mClickMotionPosition, getAdapter().getItemId(
- mClickMotionPosition));
+ final ListAdapter adapter = mAdapter;
+ final int motionPosition = mClickMotionPosition;
+ if (adapter != null && mItemCount > 0 &&
+ motionPosition != INVALID_POSITION &&
+ motionPosition < adapter.getCount() && sameWindow()) {
+ performItemClick(mChild, motionPosition, adapter.getItemId(motionPosition));
}
}
}
@@ -1754,9 +1809,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mSelectedPosition < mAdapter.getCount()) {
final View view = getChildAt(mSelectedPosition - mFirstPosition);
- performItemClick(view, mSelectedPosition, mSelectedRowId);
+ if (view != null) {
+ performItemClick(view, mSelectedPosition, mSelectedRowId);
+ view.setPressed(false);
+ }
setPressed(false);
- if (view != null) view.setPressed(false);
return true;
}
break;
@@ -1900,7 +1957,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (getHeight() > 0 && getChildCount() > 0) {
// We do not lose focus initiating a touch (since AbsListView is focusable in
// touch mode). Force an initial layout to get rid of the selection.
- mLayoutMode = LAYOUT_NORMAL;
layoutChildren();
}
}
@@ -1922,8 +1978,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
final int action = ev.getAction();
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
View v;
int deltaY;
@@ -1933,8 +1987,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
mVelocityTracker.addMovement(ev);
- switch (action) {
+ switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
+ mActivePointerId = ev.getPointerId(0);
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);
if (!mDataChanged) {
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
@@ -1954,12 +2011,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// code in ViewRoot to try to find a nearby view to select
return false;
}
- // User clicked on whitespace, or stopped a fling. It is a scroll.
- createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
- mMotionCorrection = 0;
- motionPosition = findMotionRow(y);
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+
+ if (mTouchMode == TOUCH_MODE_FLING) {
+ // Stopped a fling. It is a scroll.
+ createScrollingCache();
+ mTouchMode = TOUCH_MODE_SCROLL;
+ mMotionCorrection = 0;
+ motionPosition = findMotionRow(y);
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ }
}
}
@@ -1967,15 +2027,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Remember where the motion event started
v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();
- mMotionX = x;
- mMotionY = y;
- mMotionPosition = motionPosition;
}
+ mMotionX = x;
+ mMotionY = y;
+ mMotionPosition = motionPosition;
mLastY = Integer.MIN_VALUE;
break;
}
case MotionEvent.ACTION_MOVE: {
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final int y = (int) ev.getY(pointerIndex);
deltaY = y - mMotionY;
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
@@ -1996,27 +2058,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (y != mLastY) {
deltaY -= mMotionCorrection;
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+
// No need to do all this work if we're not going to move anyway
+ boolean atEdge = false;
if (incrementalDeltaY != 0) {
- trackMotionScroll(deltaY, incrementalDeltaY);
+ atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
}
// Check to see if we have bumped into the scroll limit
- View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- // Check if the top of the motion view is where it is
- // supposed to be
- if (motionView.getTop() != mMotionViewNewTop) {
- // We did not scroll the full amount. Treat this essentially like the
- // start of a new touch scroll
- final int motionPosition = findMotionRow(y);
-
- mMotionCorrection = 0;
- motionView = getChildAt(motionPosition - mFirstPosition);
+ if (atEdge && getChildCount() > 0) {
+ // Treat this like we're starting a new scroll from the current
+ // position. This will let the user start scrolling back into
+ // content immediately rather than needing to scroll back to the
+ // point where they hit the limit first.
+ int motionPosition = findMotionRow(y);
+ if (motionPosition >= 0) {
+ final View motionView = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = motionView.getTop();
- mMotionY = y;
- mMotionPosition = motionPosition;
}
+ mMotionY = y;
+ mMotionPosition = motionPosition;
+ invalidate();
}
mLastY = y;
}
@@ -2056,8 +2118,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mPendingCheckForTap : mPendingCheckForLongPress);
}
mLayoutMode = LAYOUT_NORMAL;
- mTouchMode = TOUCH_MODE_TAP;
- if (!mDataChanged) {
+ if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
+ mTouchMode = TOUCH_MODE_TAP;
setSelectedPositionInt(mMotionPosition);
layoutChildren();
child.setPressed(true);
@@ -2079,12 +2141,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_REST;
}
}, ViewConfiguration.getPressedStateDuration());
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
}
return true;
- } else {
- if (!mDataChanged) {
- post(performClick);
- }
+ } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
+ post(performClick);
}
}
mTouchMode = TOUCH_MODE_REST;
@@ -2101,20 +2163,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} else {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- final int initialVelocity = (int) velocityTracker.getYVelocity();
+ final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
if (Math.abs(initialVelocity) > mMinimumVelocity) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+
mFlingRunnable.start(-initialVelocity);
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
}
} else {
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
+ break;
}
setPressed(false);
@@ -2131,6 +2198,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
+ mActivePointerId = INVALID_POINTER;
if (PROFILE_SCROLLING) {
if (mScrollProfilingStarted) {
@@ -2159,8 +2228,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
+ mActivePointerId = INVALID_POINTER;
+ break;
+ }
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onSecondaryPointerUp(ev);
+ final int x = mMotionX;
+ final int y = mMotionY;
+ final int motionPosition = pointToPosition(x, y);
+ if (motionPosition >= 0) {
+ // Remember where the motion event started
+ v = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = v.getTop();
+ mMotionPosition = motionPosition;
+ }
+ mLastY = y;
+ break;
}
-
}
return true;
@@ -2177,8 +2263,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
- int x = (int) ev.getX();
- int y = (int) ev.getY();
View v;
if (mFastScroller != null) {
@@ -2188,10 +2272,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- switch (action) {
+ switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
+ int touchMode = mTouchMode;
+
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+
int motionPosition = findMotionRow(y);
- if (mTouchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
+ if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
// User clicked on an actual view (and was not stopping a fling).
// Remember where the motion event started
v = getChildAt(motionPosition - mFirstPosition);
@@ -2203,7 +2293,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
clearScrollingCache();
}
mLastY = Integer.MIN_VALUE;
- if (mTouchMode == TOUCH_MODE_FLING) {
+ if (touchMode == TOUCH_MODE_FLING) {
return true;
}
break;
@@ -2212,6 +2302,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
+ final int pointerIndex = ev.findPointerIndex(mActivePointerId);
+ final int y = (int) ev.getY(pointerIndex);
if (startScrollIfNeeded(y - mMotionY)) {
return true;
}
@@ -2222,13 +2314,37 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case MotionEvent.ACTION_UP: {
mTouchMode = TOUCH_MODE_REST;
+ mActivePointerId = INVALID_POINTER;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
break;
}
+
+ case MotionEvent.ACTION_POINTER_UP: {
+ onSecondaryPointerUp(ev);
+ break;
+ }
}
return false;
}
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mMotionX = (int) ev.getX(newPointerIndex);
+ mMotionY = (int) ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
/**
* {@inheritDoc}
@@ -2278,18 +2394,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Tracks the decay of a fling scroll
*/
- private Scroller mScroller;
+ private final Scroller mScroller;
/**
* Y value reported by mScroller on the previous fling
*/
private int mLastFlingY;
- public FlingRunnable() {
+ FlingRunnable() {
mScroller = new Scroller(getContext());
}
- public void start(int initialVelocity) {
+ void start(int initialVelocity) {
int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
mScroller.fling(0, initialY, 0, initialVelocity,
@@ -2305,77 +2421,372 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ void startScroll(int distance, int duration) {
+ int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
+ mLastFlingY = initialY;
+ mScroller.startScroll(0, initialY, 0, distance, duration);
+ mTouchMode = TOUCH_MODE_FLING;
+ post(this);
+ }
+
private void endFling() {
mTouchMode = TOUCH_MODE_REST;
+
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
clearScrollingCache();
+
+ removeCallbacks(this);
+
+ if (mPositionScroller != null) {
+ removeCallbacks(mPositionScroller);
+ }
}
public void run() {
- if (mTouchMode != TOUCH_MODE_FLING) {
+ switch (mTouchMode) {
+ default:
return;
+
+ case TOUCH_MODE_FLING: {
+ if (mItemCount == 0 || getChildCount() == 0) {
+ endFling();
+ return;
+ }
+
+ final Scroller scroller = mScroller;
+ boolean more = scroller.computeScrollOffset();
+ final int y = scroller.getCurrY();
+
+ // Flip sign to convert finger direction to list items direction
+ // (e.g. finger moving down means list is moving towards the top)
+ int delta = mLastFlingY - y;
+
+ // Pretend that each frame of a fling scroll is a touch scroll
+ if (delta > 0) {
+ // List is moving towards the top. Use first view as mMotionPosition
+ mMotionPosition = mFirstPosition;
+ final View firstView = getChildAt(0);
+ mMotionViewOriginalTop = firstView.getTop();
+
+ // Don't fling more than 1 screen
+ delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
+ } else {
+ // List is moving towards the bottom. Use last view as mMotionPosition
+ int offsetToLast = getChildCount() - 1;
+ mMotionPosition = mFirstPosition + offsetToLast;
+
+ final View lastView = getChildAt(offsetToLast);
+ mMotionViewOriginalTop = lastView.getTop();
+
+ // Don't fling more than 1 screen
+ delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
+ }
+
+ final boolean atEnd = trackMotionScroll(delta, delta);
+
+ if (more && !atEnd) {
+ invalidate();
+ mLastFlingY = y;
+ post(this);
+ } else {
+ endFling();
+
+ if (PROFILE_FLINGING) {
+ if (mFlingProfilingStarted) {
+ Debug.stopMethodTracing();
+ mFlingProfilingStarted = false;
+ }
+ }
+ }
+ break;
+ }
}
- if (mItemCount == 0 || getChildCount() == 0) {
- endFling();
+ }
+ }
+
+
+ class PositionScroller implements Runnable {
+ private static final int SCROLL_DURATION = 400;
+
+ private static final int MOVE_DOWN_POS = 1;
+ private static final int MOVE_UP_POS = 2;
+ private static final int MOVE_DOWN_BOUND = 3;
+ private static final int MOVE_UP_BOUND = 4;
+
+ private int mMode;
+ private int mTargetPos;
+ private int mBoundPos;
+ private int mLastSeenPos;
+ private int mScrollDuration;
+ private final int mExtraScroll;
+
+ PositionScroller() {
+ mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
+ }
+
+ void start(int position) {
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + getChildCount() - 1;
+
+ int viewTravelCount = 0;
+ if (position <= firstPos) {
+ viewTravelCount = firstPos - position + 1;
+ mMode = MOVE_UP_POS;
+ } else if (position >= lastPos) {
+ viewTravelCount = position - lastPos + 1;
+ mMode = MOVE_DOWN_POS;
+ } else {
+ // Already on screen, nothing to do
+ return;
+ }
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = position;
+ mBoundPos = INVALID_POSITION;
+ mLastSeenPos = INVALID_POSITION;
+
+ post(this);
+ }
+
+ void start(int position, int boundPosition) {
+ if (boundPosition == INVALID_POSITION) {
+ start(position);
+ return;
+ }
+
+ final int firstPos = mFirstPosition;
+ final int lastPos = firstPos + getChildCount() - 1;
+
+ int viewTravelCount = 0;
+ if (position <= firstPos) {
+ final int boundPosFromLast = lastPos - boundPosition;
+ if (boundPosFromLast < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = firstPos - position + 1;
+ final int boundTravel = boundPosFromLast - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_UP_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_UP_POS;
+ }
+ } else if (position >= lastPos) {
+ final int boundPosFromFirst = boundPosition - firstPos;
+ if (boundPosFromFirst < 1) {
+ // Moving would shift our bound position off the screen. Abort.
+ return;
+ }
+
+ final int posTravel = position - lastPos + 1;
+ final int boundTravel = boundPosFromFirst - 1;
+ if (boundTravel < posTravel) {
+ viewTravelCount = boundTravel;
+ mMode = MOVE_DOWN_BOUND;
+ } else {
+ viewTravelCount = posTravel;
+ mMode = MOVE_DOWN_POS;
+ }
+ } else {
+ // Already on screen, nothing to do
return;
}
+
+ if (viewTravelCount > 0) {
+ mScrollDuration = SCROLL_DURATION / viewTravelCount;
+ } else {
+ mScrollDuration = SCROLL_DURATION;
+ }
+ mTargetPos = position;
+ mBoundPos = boundPosition;
+ mLastSeenPos = INVALID_POSITION;
+
+ post(this);
+ }
+
+ void stop() {
+ removeCallbacks(this);
+ }
+
+ public void run() {
+ final int listHeight = getHeight();
+ final int firstPos = mFirstPosition;
+
+ switch (mMode) {
+ case MOVE_DOWN_POS: {
+ final int lastViewIndex = getChildCount() - 1;
+ final int lastPos = firstPos + lastViewIndex;
+
+ if (lastViewIndex < 0) {
+ return;
+ }
- final Scroller scroller = mScroller;
- boolean more = scroller.computeScrollOffset();
- final int y = scroller.getCurrY();
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
- // Flip sign to convert finger direction to list items direction
- // (e.g. finger moving down means list is moving towards the top)
- int delta = mLastFlingY - y;
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ final int extraScroll = lastPos < mItemCount - 1 ? mExtraScroll : mListPadding.bottom;
- // Pretend that each frame of a fling scroll is a touch scroll
- if (delta > 0) {
- // List is moving towards the top. Use first view as mMotionPosition
- mMotionPosition = mFirstPosition;
- final View firstView = getChildAt(0);
- mMotionViewOriginalTop = firstView.getTop();
+ smoothScrollBy(lastViewHeight - lastViewPixelsShowing + extraScroll,
+ mScrollDuration);
- // Don't fling more than 1 screen
- delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
- } else {
- // List is moving towards the bottom. Use last view as mMotionPosition
- int offsetToLast = getChildCount() - 1;
- mMotionPosition = mFirstPosition + offsetToLast;
+ mLastSeenPos = lastPos;
+ if (lastPos != mTargetPos) {
+ post(this);
+ }
+ break;
+ }
+
+ case MOVE_DOWN_BOUND: {
+ final int nextViewIndex = 1;
+ final int childCount = getChildCount();
+
+ if (firstPos == mBoundPos || childCount <= nextViewIndex
+ || firstPos + childCount >= mItemCount) {
+ return;
+ }
+ final int nextPos = firstPos + nextViewIndex;
+
+ if (nextPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
- final View lastView = getChildAt(offsetToLast);
- mMotionViewOriginalTop = lastView.getTop();
+ final View nextView = getChildAt(nextViewIndex);
+ final int nextViewHeight = nextView.getHeight();
+ final int nextViewTop = nextView.getTop();
+ final int extraScroll = mExtraScroll;
+ if (nextPos != mBoundPos) {
+ smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll),
+ mScrollDuration);
- // Don't fling more than 1 screen
- delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
+ mLastSeenPos = nextPos;
+
+ post(this);
+ } else {
+ if (nextViewTop > extraScroll) {
+ smoothScrollBy(nextViewTop - extraScroll, mScrollDuration);
+ }
+ }
+ break;
}
+
+ case MOVE_UP_POS: {
+ if (firstPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
- trackMotionScroll(delta, delta);
+ final View firstView = getChildAt(0);
+ if (firstView == null) {
+ return;
+ }
+ final int firstViewTop = firstView.getTop();
+ final int extraScroll = firstPos > 0 ? mExtraScroll : mListPadding.top;
- // Check to see if we have bumped into the scroll limit
- View motionView = getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- // Check if the top of the motion view is where it is
- // supposed to be
- if (motionView.getTop() != mMotionViewNewTop) {
- more = false;
+ smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
+
+ mLastSeenPos = firstPos;
+
+ if (firstPos != mTargetPos) {
+ post(this);
}
+ break;
}
+
+ case MOVE_UP_BOUND: {
+ final int lastViewIndex = getChildCount() - 2;
+ if (lastViewIndex < 0) {
+ return;
+ }
+ final int lastPos = firstPos + lastViewIndex;
- if (more) {
- invalidate();
- mLastFlingY = y;
- post(this);
- } else {
- endFling();
- if (PROFILE_FLINGING) {
- if (mFlingProfilingStarted) {
- Debug.stopMethodTracing();
- mFlingProfilingStarted = false;
+ if (lastPos == mLastSeenPos) {
+ // No new views, let things keep going.
+ post(this);
+ return;
+ }
+
+ final View lastView = getChildAt(lastViewIndex);
+ final int lastViewHeight = lastView.getHeight();
+ final int lastViewTop = lastView.getTop();
+ final int lastViewPixelsShowing = listHeight - lastViewTop;
+ mLastSeenPos = lastPos;
+ if (lastPos != mBoundPos) {
+ smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration);
+ post(this);
+ } else {
+ final int bottom = listHeight - mExtraScroll;
+ final int lastViewBottom = lastViewTop + lastViewHeight;
+ if (bottom > lastViewBottom) {
+ smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration);
}
}
+ break;
}
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed.
+ * @param position Scroll to this adapter position.
+ */
+ public void smoothScrollToPosition(int position) {
+ if (mPositionScroller == null) {
+ mPositionScroller = new PositionScroller();
+ }
+ mPositionScroller.start(position);
+ }
+
+ /**
+ * Smoothly scroll to the specified adapter position. The view will
+ * scroll such that the indicated position is displayed, but it will
+ * stop early if scrolling further would scroll boundPosition out of
+ * view.
+ * @param position Scroll to this adapter position.
+ * @param boundPosition Do not scroll if it would move this adapter
+ * position out of view.
+ */
+ public void smoothScrollToPosition(int position, int boundPosition) {
+ if (mPositionScroller == null) {
+ mPositionScroller = new PositionScroller();
+ }
+ mPositionScroller.start(position, boundPosition);
+ }
+
+ /**
+ * Smoothly scroll by distance pixels over duration milliseconds.
+ * @param distance Distance to scroll in pixels.
+ * @param duration Duration of the scroll animation in milliseconds.
+ */
+ public void smoothScrollBy(int distance, int duration) {
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ } else {
+ mFlingRunnable.endFling();
}
+ mFlingRunnable.startScroll(distance, duration);
}
private void createScrollingCache() {
@@ -2412,11 +2823,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
* began. Positive numbers mean the user's finger is moving down the screen.
* @param incrementalDeltaY Change in deltaY from the previous event.
+ * @return true if we're already at the beginning/end of the list and have nothing to do.
*/
- void trackMotionScroll(int deltaY, int incrementalDeltaY) {
+ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
- return;
+ return true;
}
final int firstTop = getChildAt(0).getTop();
@@ -2442,98 +2854,109 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}
- final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
-
- if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
- hideSelector();
- offsetChildrenTopAndBottom(incrementalDeltaY);
- if (!awakenScrollBars()) {
- invalidate();
- }
- mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
- } else {
- final int firstPosition = mFirstPosition;
+ final int firstPosition = mFirstPosition;
- if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) {
- // Don't need to move views down if the top of the first position is already visible
- return;
- }
+ if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
+ // Don't need to move views down if the top of the first position
+ // is already visible
+ return true;
+ }
- if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
- // Don't need to move views up if the bottom of the last position is already visible
- return;
- }
+ if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
+ // Don't need to move views up if the bottom of the last position
+ // is already visible
+ return true;
+ }
- final boolean down = incrementalDeltaY < 0;
+ final boolean down = incrementalDeltaY < 0;
+ final boolean inTouchMode = isInTouchMode();
+ if (inTouchMode) {
hideSelector();
+ }
- final int headerViewsCount = getHeaderViewsCount();
- final int footerViewsStart = mItemCount - getFooterViewsCount();
+ final int headerViewsCount = getHeaderViewsCount();
+ final int footerViewsStart = mItemCount - getFooterViewsCount();
- int start = 0;
- int count = 0;
+ int start = 0;
+ int count = 0;
- if (down) {
- final int top = listPadding.top - incrementalDeltaY;
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getBottom() >= top) {
- break;
- } else {
- count++;
- int position = firstPosition + i;
- if (position >= headerViewsCount && position < footerViewsStart) {
- mRecycler.addScrapView(child);
-
- if (ViewDebug.TRACE_RECYCLER) {
- ViewDebug.trace(child,
- ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
- firstPosition + i, -1);
- }
+ if (down) {
+ final int top = listPadding.top - incrementalDeltaY;
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getBottom() >= top) {
+ break;
+ } else {
+ count++;
+ int position = firstPosition + i;
+ if (position >= headerViewsCount && position < footerViewsStart) {
+ mRecycler.addScrapView(child);
+
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(child,
+ ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+ firstPosition + i, -1);
}
}
}
- } else {
- final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
- for (int i = childCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- if (child.getTop() <= bottom) {
- break;
- } else {
- start = i;
- count++;
- int position = firstPosition + i;
- if (position >= headerViewsCount && position < footerViewsStart) {
- mRecycler.addScrapView(child);
-
- if (ViewDebug.TRACE_RECYCLER) {
- ViewDebug.trace(child,
- ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
- firstPosition + i, -1);
- }
+ }
+ } else {
+ final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
+ for (int i = childCount - 1; i >= 0; i--) {
+ final View child = getChildAt(i);
+ if (child.getTop() <= bottom) {
+ break;
+ } else {
+ start = i;
+ count++;
+ int position = firstPosition + i;
+ if (position >= headerViewsCount && position < footerViewsStart) {
+ mRecycler.addScrapView(child);
+
+ if (ViewDebug.TRACE_RECYCLER) {
+ ViewDebug.trace(child,
+ ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
+ firstPosition + i, -1);
}
}
}
}
+ }
- mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
+ mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
- mBlockLayoutRequests = true;
+ mBlockLayoutRequests = true;
+
+ if (count > 0) {
detachViewsFromParent(start, count);
- offsetChildrenTopAndBottom(incrementalDeltaY);
+ }
+ offsetChildrenTopAndBottom(incrementalDeltaY);
- if (down) {
- mFirstPosition += count;
- }
+ if (down) {
+ mFirstPosition += count;
+ }
+
+ invalidate();
- invalidate();
+ final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+ if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
- mBlockLayoutRequests = false;
+ }
- invokeOnItemScrollListener();
- awakenScrollBars();
+ if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
+ final int childIndex = mSelectedPosition - mFirstPosition;
+ if (childIndex >= 0 && childIndex < getChildCount()) {
+ positionSelector(getChildAt(childIndex));
+ }
}
+
+ mBlockLayoutRequests = false;
+
+ invokeOnItemScrollListener();
+ awakenScrollBars();
+
+ return false;
}
/**
@@ -2568,7 +2991,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
void hideSelector() {
if (mSelectedPosition != INVALID_POSITION) {
- mResurrectToPosition = mSelectedPosition;
+ if (mLayoutMode != LAYOUT_SPECIFIC) {
+ mResurrectToPosition = mSelectedPosition;
+ }
if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
mResurrectToPosition = mNextSelectedPosition;
}
@@ -2598,9 +3023,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Find the row closest to y. This row will be used as the motion row when scrolling
*
* @param y Where the user touched
- * @return The position of the first (or only) item in the row closest to y
+ * @return The position of the first (or only) item in the row containing y
*/
abstract int findMotionRow(int y);
+
+ /**
+ * Find the row closest to y. This row will be used as the motion row when scrolling.
+ *
+ * @param y Where the user touched
+ * @return The position of the first (or only) item in the row closest to y
+ */
+ int findClosestMotionRow(int y) {
+ final int childCount = getChildCount();
+ if (childCount == 0) {
+ return INVALID_POSITION;
+ }
+
+ final int motionRow = findMotionRow(y);
+ return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
+ }
/**
* Causes all the views to be rebuilt and redrawn.
@@ -2837,6 +3278,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
checkSelectionChanged();
}
+ @Override
+ protected void onDisplayHint(int hint) {
+ super.onDisplayHint(hint);
+ switch (hint) {
+ case INVISIBLE:
+ if (mPopup != null && mPopup.isShowing()) {
+ dismissPopup();
+ }
+ break;
+ case VISIBLE:
+ if (mFiltered && mPopup != null && !mPopup.isShowing()) {
+ showPopup();
+ }
+ break;
+ }
+ mPopupHidden = hint == INVISIBLE;
+ }
+
/**
* Removes the filter window
*/
@@ -2966,7 +3425,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
case KeyEvent.KEYCODE_SPACE:
// Only send spaces once we are filtered
- okToSend = mFiltered = true;
+ okToSend = mFiltered;
break;
}
@@ -3120,12 +3579,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public void onGlobalLayout() {
if (isShown()) {
// Show the popup if we are filtered
- if (mFiltered && mPopup != null && !mPopup.isShowing()) {
+ if (mFiltered && mPopup != null && !mPopup.isShowing() && !mPopupHidden) {
showPopup();
}
} else {
// Hide the popup when we are no longer visible
- if (mPopup.isShowing()) {
+ if (mPopup != null && mPopup.isShowing()) {
dismissPopup();
}
}
@@ -3235,7 +3694,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* @param color The background color
*/
public void setCacheColorHint(int color) {
- mCacheColorHint = color;
+ if (color != mCacheColorHint) {
+ mCacheColorHint = color;
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setDrawingCacheBackgroundColor(color);
+ }
+ mRecycler.setCacheColorHint(color);
+ }
}
/**
@@ -3292,7 +3758,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
for (int i = 0; i < count; i++) {
if (activeViews[i] != null) {
result = false;
- android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
+ Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"AbsListView " + this + " has a view in its active recycler: " +
activeViews[i]);
}
@@ -3320,12 +3786,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final View view = scrap.get(i);
if (view.getParent() != null) {
result = false;
- android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
+ Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
" has a view in its scrap heap still attached to a parent: " + view);
}
if (indexOfChild(view) >= 0) {
result = false;
- android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
+ Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
" has a view in its scrap heap that is also a direct child: " + view);
}
}
@@ -3356,6 +3822,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* View type for this view, as returned by
* {@link android.widget.Adapter#getItemViewType(int) }
*/
+ @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
+ @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
+ })
int viewType;
/**
@@ -3364,8 +3834,20 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* been added to the list view and whether they should be treated as
* recycled views or not.
*/
+ @ViewDebug.ExportedProperty
boolean recycledHeaderFooter;
+ /**
+ * When an AbsListView is measured with an AT_MOST measure spec, it needs
+ * to obtain children views to measure itself. When doing so, the children
+ * are not attached to the window, but put in the recycler which assumes
+ * they've been attached before. Setting this flag will force the reused
+ * view to be attached to the window rather than just attached to the
+ * parent.
+ */
+ @ViewDebug.ExportedProperty
+ boolean forceAdd;
+
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
@@ -3451,6 +3933,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
+
+ public void markChildrenDirty() {
+ if (mViewTypeCount == 1) {
+ final ArrayList<View> scrap = mCurrentScrap;
+ final int scrapCount = scrap.size();
+ for (int i = 0; i < scrapCount; i++) {
+ scrap.get(i).forceLayout();
+ }
+ } else {
+ final int typeCount = mViewTypeCount;
+ for (int i = 0; i < typeCount; i++) {
+ final ArrayList<View> scrap = mScrapViews[i];
+ final int scrapCount = scrap.size();
+ for (int j = 0; j < scrapCount; j++) {
+ scrap.get(j).forceLayout();
+ }
+ }
+ }
+ }
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
@@ -3494,9 +3995,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams();
+ AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// Don't put header or footer views into the scrap heap
- if (lp != null && lp.viewType != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
@@ -3563,12 +4064,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// into the scrap heap
int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
+ if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ removeDetachedView(scrap, false);
+ }
return;
}
if (mViewTypeCount == 1) {
+ scrap.dispatchStartTemporaryDetach();
mCurrentScrap.add(scrap);
} else {
+ scrap.dispatchStartTemporaryDetach();
mScrapViews[viewType].add(scrap);
}
@@ -3587,22 +4093,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ArrayList<View> scrapViews = mCurrentScrap;
final int count = activeViews.length;
- for (int i = 0; i < count; ++i) {
+ for (int i = count - 1; i >= 0; i--) {
final View victim = activeViews[i];
if (victim != null) {
- int whichScrap = ((AbsListView.LayoutParams)
- victim.getLayoutParams()).viewType;
+ int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
activeViews[i] = null;
- if (whichScrap == AdapterView.ITEM_VIEW_TYPE_IGNORE) {
+ if (!shouldRecycleViewType(whichScrap)) {
// Do not move views that should be ignored
+ if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
+ removeDetachedView(victim, false);
+ }
continue;
}
if (multipleScraps) {
scrapViews = mScrapViews[whichScrap];
}
+ victim.dispatchStartTemporaryDetach();
scrapViews.add(victim);
if (hasListener) {
@@ -3654,5 +4163,38 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
+
+ /**
+ * Updates the cache color hint of all known views.
+ *
+ * @param color The new cache color hint.
+ */
+ void setCacheColorHint(int color) {
+ if (mViewTypeCount == 1) {
+ final ArrayList<View> scrap = mCurrentScrap;
+ final int scrapCount = scrap.size();
+ for (int i = 0; i < scrapCount; i++) {
+ scrap.get(i).setDrawingCacheBackgroundColor(color);
+ }
+ } else {
+ final int typeCount = mViewTypeCount;
+ for (int i = 0; i < typeCount; i++) {
+ final ArrayList<View> scrap = mScrapViews[i];
+ final int scrapCount = scrap.size();
+ for (int j = 0; j < scrapCount; j++) {
+ scrap.get(i).setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
+ // Just in case this is called during a layout pass
+ final View[] activeViews = mActiveViews;
+ final int count = activeViews.length;
+ for (int i = 0; i < count; ++i) {
+ final View victim = activeViews[i];
+ if (victim != null) {
+ victim.setDrawingCacheBackgroundColor(color);
+ }
+ }
+ }
}
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 50e0680..d6dd872 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -26,7 +26,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
public abstract class AbsSeekBar extends ProgressBar {
-
private Drawable mThumb;
private int mThumbOffset;
@@ -66,9 +65,9 @@ public abstract class AbsSeekBar extends ProgressBar {
Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
setThumb(thumb); // will guess mThumbOffset if thumb != null...
// ...but allow layout to override this
- int thumbOffset =
- a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
- setThumbOffset(thumbOffset);
+ int thumbOffset = a.getDimensionPixelOffset(
+ com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
+ setThumbOffset(thumbOffset);
a.recycle();
a = context.obtainStyledAttributes(attrs,
@@ -92,7 +91,7 @@ public abstract class AbsSeekBar extends ProgressBar {
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
// progress bar.
- mThumbOffset = (int)thumb.getIntrinsicWidth() / 2;
+ mThumbOffset = thumb.getIntrinsicWidth() / 2;
}
mThumb = thumb;
invalidate();
@@ -369,20 +368,21 @@ public abstract class AbsSeekBar extends ProgressBar {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- int progress = getProgress();
-
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (progress <= 0) break;
- setProgress(progress - mKeyProgressIncrement, true);
- onKeyChange();
- return true;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (progress >= getMax()) break;
- setProgress(progress + mKeyProgressIncrement, true);
- onKeyChange();
- return true;
+ if (isEnabled()) {
+ int progress = getProgress();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (progress <= 0) break;
+ setProgress(progress - mKeyProgressIncrement, true);
+ onKeyChange();
+ return true;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (progress >= getMax()) break;
+ setProgress(progress + mKeyProgressIncrement, true);
+ onKeyChange();
+ return true;
+ }
}
return super.onKeyDown(keyCode, event);
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 424a936..2b3b98d 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -28,8 +28,6 @@ import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
-
/**
* An abstract base class for spinner widgets. SDK users will probably not
@@ -38,24 +36,21 @@ import android.view.animation.Interpolator;
* @attr ref android.R.styleable#AbsSpinner_entries
*/
public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
-
SpinnerAdapter mAdapter;
int mHeightMeasureSpec;
int mWidthMeasureSpec;
boolean mBlockLayoutRequests;
+
int mSelectionLeftPadding = 0;
int mSelectionTopPadding = 0;
int mSelectionRightPadding = 0;
int mSelectionBottomPadding = 0;
- Rect mSpinnerPadding = new Rect();
- View mSelectedView = null;
- Interpolator mInterpolator;
+ final Rect mSpinnerPadding = new Rect();
- RecycleBin mRecycler = new RecycleBin();
+ final RecycleBin mRecycler = new RecycleBin();
private DataSetObserver mDataSetObserver;
-
/** Temporary frame to hold a child View's frame rectangle */
private Rect mTouchFrame;
@@ -95,7 +90,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
setWillNotDraw(false);
}
-
/**
* The Adapter is used to provide the data which backs this Spinner.
* It also provides methods to transform spinner items based on their position
@@ -190,7 +184,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
boolean needsMeasuring = true;
int selectedPosition = getSelectedItemPosition();
- if (selectedPosition >= 0 && mAdapter != null) {
+ if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
// Try looking in the recycler. (Maybe we were measured once already)
View view = mRecycler.get(selectedPosition);
if (view == null) {
@@ -237,7 +231,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
mWidthMeasureSpec = widthMeasureSpec;
}
-
int getChildHeight(View child) {
return child.getMeasuredHeight();
}
@@ -249,31 +242,22 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
void recycleAllViews() {
- int childCount = getChildCount();
+ final int childCount = getChildCount();
final AbsSpinner.RecycleBin recycleBin = mRecycler;
+ final int position = mFirstPosition;
// All views go in recycler
- for (int i=0; i<childCount; i++) {
+ for (int i = 0; i < childCount; i++) {
View v = getChildAt(i);
- int index = mFirstPosition + i;
+ int index = position + i;
recycleBin.put(index, v);
}
}
-
- @Override
- void handleDataChanged() {
- // FIXME -- this is called from both measure and layout.
- // This is harmless right now, but we don't want to do redundant work if
- // this gets more complicated
- super.handleDataChanged();
- }
-
-
/**
* Jump directly to a specific item in the adapter data.
@@ -284,7 +268,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
position <= mFirstPosition + getChildCount() - 1;
setSelectionInt(position, shouldAnimate);
}
-
@Override
public void setSelection(int position) {
@@ -335,8 +318,6 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
}
-
-
@Override
public SpinnerAdapter getAdapter() {
return mAdapter;
@@ -452,7 +433,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
class RecycleBin {
- private SparseArray<View> mScrapHeap = new SparseArray<View>();
+ private final SparseArray<View> mScrapHeap = new SparseArray<View>();
public void put(int position, View v) {
mScrapHeap.put(position, v);
@@ -469,12 +450,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
return result;
}
-
- View peek(int position) {
- // System.out.print("Looking for " + position);
- return mScrapHeap.get(position);
- }
-
+
void clear() {
final SparseArray<View> scrapHeap = mScrapHeap;
final int count = scrapHeap.size();
diff --git a/core/java/android/widget/AbsoluteLayout.java b/core/java/android/widget/AbsoluteLayout.java
index c77f7ae..b829655 100644
--- a/core/java/android/widget/AbsoluteLayout.java
+++ b/core/java/android/widget/AbsoluteLayout.java
@@ -161,9 +161,9 @@ public class AbsoluteLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width,
* height and location.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
{@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
{@link #WRAP_CONTENT} or a fixed size in pixels
* @param x the X location of the child
* @param y the Y location of the child
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index a09f23c..aa14c81 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -146,6 +146,19 @@ public class AppSecurityPermissions implements View.OnClickListener {
}
}
+ /**
+ * Utility to retrieve a view displaying a single permission.
+ */
+ public static View getPermissionItemView(Context context,
+ CharSequence grpName, CharSequence description, boolean dangerous) {
+ LayoutInflater inflater = (LayoutInflater)context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ Drawable icon = context.getResources().getDrawable(dangerous
+ ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
+ return getPermissionItemView(context, inflater, grpName,
+ description, dangerous, icon);
+ }
+
private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -304,15 +317,20 @@ public class AppSecurityPermissions implements View.OnClickListener {
mNoPermsView.setVisibility(View.VISIBLE);
}
- private View getPermissionItemView(CharSequence grpName, String permList,
+ private View getPermissionItemView(CharSequence grpName, CharSequence permList,
boolean dangerous) {
- View permView = mInflater.inflate(R.layout.app_permission_item, null);
- Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
+ return getPermissionItemView(mContext, mInflater, grpName, permList,
+ dangerous, dangerous ? mDangerousIcon : mNormalIcon);
+ }
+
+ private static View getPermissionItemView(Context context, LayoutInflater inflater,
+ CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
+ View permView = inflater.inflate(R.layout.app_permission_item, null);
TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
if (dangerous) {
- final Resources resources = mContext.getResources();
+ final Resources resources = context.getResources();
permGrpView.setTextColor(resources.getColor(R.color.perms_dangerous_grp_color));
permDescView.setTextColor(resources.getColor(R.color.perms_dangerous_perm_color));
}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 75d0f31..e15a520 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -18,6 +18,7 @@ package android.widget;
import android.content.Context;
import android.content.res.TypedArray;
+import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
@@ -91,6 +92,13 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private static final int HINT_VIEW_ID = 0x17;
+ /**
+ * This value controls the length of time that the user
+ * must leave a pointer down without scrolling to expand
+ * the autocomplete dropdown list to cover the IME.
+ */
+ private static final int EXPAND_LIST_TIMEOUT = 250;
+
private CharSequence mHintText;
private int mHintResource;
@@ -129,10 +137,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private boolean mBlockCompletion;
- private AutoCompleteTextView.ListSelectorHider mHideSelector;
+ private ListSelectorHider mHideSelector;
private Runnable mShowDropDownRunnable;
+ private Runnable mResizePopupRunnable = new ResizePopupRunnable();
- private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
+ private PassThroughClickListener mPassThroughClickListener;
+ private PopupDataSetObserver mObserver;
public AutoCompleteTextView(Context context) {
this(context, null);
@@ -172,7 +182,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
View.NO_ID);
- // For dropdown width, the developer can specify a specific width, or FILL_PARENT
+ // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
// (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -210,22 +220,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* Private hook into the on click event, dispatched from {@link PassThroughClickListener}
*/
private void onClickImpl() {
- // If the dropdown is showing, bring it back in front of the soft
- // keyboard when the user touches the text field.
- if (mPopup.isShowing() && isInputMethodNotNeeded()) {
- ensureImeVisible();
+ // If the dropdown is showing, bring the keyboard to the front
+ // when the user touches the text field.
+ if (mPopup.isShowing()) {
+ ensureImeVisible(true);
}
}
/**
- * Sets this to be single line; a separate method so
- * MultiAutoCompleteTextView can skip this.
- */
- /* package */ void finishInit() {
- setSingleLine();
- }
-
- /**
* <p>Sets the optional hint text that is displayed at the bottom of the
* the matching list. This can be used as a cue to the user on how to
* best use the list, or to provide extra information.</p>
@@ -240,7 +242,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Returns the current width for the auto-complete drop down list. This can
- * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
*
* @return the width for the drop down list
@@ -253,7 +255,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Sets the current width for the auto-complete drop down list. This can
- * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
+ * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the screen, or
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
*
* @param width the width to use
@@ -266,7 +268,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Returns the current height for the auto-complete drop down list. This can
- * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
* the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
* of the drop down's content.</p>
*
@@ -280,7 +282,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* <p>Sets the current height for the auto-complete drop down list. This can
- * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill
* the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
* of the drop down's content.</p>
*
@@ -448,7 +450,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public boolean isDropDownDismissedOnCompletion() {
return mDropDownDismissedOnCompletion;
}
-
+
/**
* Sets whether the drop-down is dismissed when a suggestion is clicked. This is
* true by default.
@@ -590,10 +592,16 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @see android.widget.ListAdapter
*/
public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+ if (mObserver == null) {
+ mObserver = new PopupDataSetObserver();
+ } else if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mObserver);
+ }
mAdapter = adapter;
if (mAdapter != null) {
//noinspection unchecked
mFilter = ((Filterable) mAdapter).getFilter();
+ adapter.registerDataSetObserver(mObserver);
} else {
mFilter = null;
}
@@ -609,8 +617,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
&& !mDropDownAlwaysVisible) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
getKeyDispatcherState().startTracking(event, this);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
@@ -658,10 +665,25 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
&& keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
int curIndex = mDropDownList.getSelectedItemPosition();
boolean consumed;
+
final boolean below = !mPopup.isAboveAnchor();
- if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) ||
- (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >=
- mDropDownList.getAdapter().getCount() - 1)) {
+
+ final ListAdapter adapter = mAdapter;
+
+ boolean allEnabled;
+ int firstItem = Integer.MAX_VALUE;
+ int lastItem = Integer.MIN_VALUE;
+
+ if (adapter != null) {
+ allEnabled = adapter.areAllItemsEnabled();
+ firstItem = allEnabled ? 0 :
+ mDropDownList.lookForSelectablePosition(0, true);
+ lastItem = allEnabled ? adapter.getCount() - 1 :
+ mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
+ }
+
+ if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
+ (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
// When the selection is at the top, we block the key
// event to prevent focus from moving.
clearListSelection();
@@ -701,11 +723,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
// when the selection is at the bottom, we block the
// event to avoid going to the next focusable widget
- Adapter adapter = mDropDownList.getAdapter();
- if (adapter != null && curIndex == adapter.getCount() - 1) {
+ if (curIndex == lastItem) {
return true;
}
- } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == 0) {
+ } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+ curIndex == firstItem) {
return true;
}
}
@@ -858,16 +880,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return ListView.INVALID_POSITION;
}
-
- /**
- * @hide
- * @return {@link android.widget.ListView#getChildCount()} of the drop down if it is showing,
- * otherwise 0.
- */
- protected int getDropDownChildCount() {
- return mDropDownList == null ? 0 : mDropDownList.getChildCount();
- }
-
/**
* <p>Starts filtering the content of the drop down list. The filtering
* pattern is the content of the edit box. Subclasses should override this
@@ -968,25 +980,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mBlockCompletion = false;
}
}
-
- /**
- * Like {@link #setTextKeepState(CharSequence)}, except that it can disable filtering.
- *
- * @param filter If <code>false</code>, no filtering will be performed
- * as a result of this call.
- *
- * @hide Pending API council approval.
- */
- public void setTextKeepState(CharSequence text, boolean filter) {
- if (filter) {
- setTextKeepState(text);
- } else {
- mBlockCompletion = true;
- setTextKeepState(text);
- mBlockCompletion = false;
- }
- }
-
+
/**
* <p>Performs the text completion by replacing the current text by the
* selected item. Subclasses should override this method to avoid replacing
@@ -1005,6 +999,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/** {@inheritDoc} */
public void onFilterComplete(int count) {
+ updateDropDownForFilter(count);
+
+ }
+
+ private void updateDropDownForFilter(int count) {
// Not attached to window, don't update drop-down
if (getWindowVisibility() == View.GONE) return;
@@ -1027,16 +1026,30 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- performValidation();
if (!hasWindowFocus && !mDropDownAlwaysVisible) {
dismissDropDown();
}
}
@Override
+ protected void onDisplayHint(int hint) {
+ super.onDisplayHint(hint);
+ switch (hint) {
+ case INVISIBLE:
+ if (!mDropDownAlwaysVisible) {
+ dismissDropDown();
+ }
+ break;
+ }
+ }
+
+ @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
- performValidation();
+ // Perform validation if the view is losing focus.
+ if (!focused) {
+ performValidation();
+ }
if (!focused && !mDropDownAlwaysVisible) {
dismissDropDown();
}
@@ -1067,11 +1080,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
@Override
- protected boolean setFrame(int l, int t, int r, int b) {
+ protected boolean setFrame(final int l, int t, final int r, int b) {
boolean result = super.setFrame(l, t, r, b);
if (mPopup.isShowing()) {
- mPopup.update(this, r - l, -1);
+ showDropDown();
}
return result;
@@ -1100,11 +1113,13 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/**
* Ensures that the drop down is not obscuring the IME.
- *
+ * @param visible whether the ime should be in front. If false, the ime is pushed to
+ * the background.
* @hide internal used only here and SearchDialog
*/
- public void ensureImeVisible() {
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ public void ensureImeVisible(boolean visible) {
+ mPopup.setInputMethodMode(visible
+ ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED);
showDropDown();
}
@@ -1127,7 +1142,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
boolean noInputMethod = isInputMethodNotNeeded();
if (mPopup.isShowing()) {
- if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
+ if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
widthSpec = -1;
@@ -1137,19 +1152,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
widthSpec = mDropDownWidth;
}
- if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
+ if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
- heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.FILL_PARENT;
+ heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
if (noInputMethod) {
mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
- ViewGroup.LayoutParams.FILL_PARENT : 0, 0);
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
} else {
mPopup.setWindowLayoutMode(
- mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
- ViewGroup.LayoutParams.FILL_PARENT : 0,
- ViewGroup.LayoutParams.FILL_PARENT);
+ mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+ ViewGroup.LayoutParams.MATCH_PARENT : 0,
+ ViewGroup.LayoutParams.MATCH_PARENT);
}
} else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
heightSpec = height;
@@ -1162,8 +1177,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, widthSpec, heightSpec);
} else {
- if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
- widthSpec = ViewGroup.LayoutParams.FILL_PARENT;
+ if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+ widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setWidth(getDropDownAnchorView().getWidth());
@@ -1172,8 +1187,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
- if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
- heightSpec = ViewGroup.LayoutParams.FILL_PARENT;
+ if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+ heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setHeight(height);
@@ -1218,18 +1233,30 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
ViewGroup dropDownView;
int otherHeights = 0;
- if (mAdapter != null) {
+ final ListAdapter adapter = mAdapter;
+ if (adapter != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
- int N = mAdapter.getCount();
- if (N > 20) N = 20;
- CompletionInfo[] completions = new CompletionInfo[N];
- for (int i = 0; i < N; i++) {
- Object item = mAdapter.getItem(i);
- long id = mAdapter.getItemId(i);
- completions[i] = new CompletionInfo(id, i,
- convertSelectionToString(item));
+ final int count = Math.min(adapter.getCount(), 20);
+ CompletionInfo[] completions = new CompletionInfo[count];
+ int realCount = 0;
+
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ realCount++;
+ Object item = adapter.getItem(i);
+ long id = adapter.getItemId(i);
+ completions[i] = new CompletionInfo(id, i,
+ convertSelectionToString(item));
+ }
}
+
+ if (realCount != count) {
+ CompletionInfo[] tmp = new CompletionInfo[realCount];
+ System.arraycopy(completions, 0, tmp, 0, realCount);
+ completions = tmp;
+ }
+
imm.displayCompletions(this, completions);
}
}
@@ -1257,7 +1284,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownList = new DropDownListView(context);
mDropDownList.setSelector(mDropDownListHighlight);
- mDropDownList.setAdapter(mAdapter);
+ mDropDownList.setAdapter(adapter);
mDropDownList.setVerticalFadingEdgeEnabled(true);
mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
mDropDownList.setFocusable(true);
@@ -1278,6 +1305,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void onNothingSelected(AdapterView<?> parent) {
}
});
+ mDropDownList.setOnScrollListener(new PopupScrollListener());
if (mItemSelectedListener != null) {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
@@ -1293,7 +1321,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
hintContainer.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
);
hintContainer.addView(dropDownView, hintParams);
hintContainer.addView(hintView);
@@ -1329,20 +1357,26 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
final int maxHeight = mPopup.getMaxAvailableHeight(
getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
- if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
- // getMaxAvailableHeight() subtracts the padding, so we put it back,
- // to get the available height for the whole window
- int padding = 0;
- Drawable background = mPopup.getBackground();
- if (background != null) {
- background.getPadding(mTempRect);
- padding = mTempRect.top + mTempRect.bottom;
- }
+ // getMaxAvailableHeight() subtracts the padding, so we put it back,
+ // to get the available height for the whole window
+ int padding = 0;
+ Drawable background = mPopup.getBackground();
+ if (background != null) {
+ background.getPadding(mTempRect);
+ padding = mTempRect.top + mTempRect.bottom;
+ }
+
+ if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
return maxHeight + padding;
}
- return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
- 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
+ final int listContent = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+ 0, ListView.NO_POSITION, maxHeight - otherHeights, 2);
+ // add padding only if the list has items in it, that way we don't show
+ // the popup if it is not needed
+ if (listContent > 0) otherHeights += padding;
+
+ return listContent + otherHeights;
}
private View getHintView(Context context) {
@@ -1412,17 +1446,41 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
+ private class ResizePopupRunnable implements Runnable {
+ public void run() {
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ showDropDown();
+ }
+ }
+
private class PopupTouchInterceptor implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN &&
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN &&
mPopup != null && mPopup.isShowing()) {
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- showDropDown();
+ postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
+ } else if (action == MotionEvent.ACTION_UP) {
+ removeCallbacks(mResizePopupRunnable);
}
return false;
}
}
+ private class PopupScrollListener implements ListView.OnScrollListener {
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
+ !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
+ removeCallbacks(mResizePopupRunnable);
+ mResizePopupRunnable.run();
+ }
+ }
+ }
+
private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
public void onItemClick(AdapterView parent, View v, int position, long id) {
performCompletion(v, position, id);
@@ -1483,8 +1541,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @return the view for the specified item
*/
@Override
- protected View obtainView(int position) {
- View view = super.obtainView(position);
+ View obtainView(int position, boolean[] isScrap) {
+ View view = super.obtainView(position, isScrap);
if (view instanceof TextView) {
((TextView) view).setHorizontallyScrolling(true);
@@ -1493,24 +1551,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return view;
}
- /**
- * <p>Returns the top padding of the currently selected view.</p>
- *
- * @return the height of the top padding for the selection
- */
- public int getSelectionPaddingTop() {
- return mSelectionTopPadding;
- }
-
- /**
- * <p>Returns the bottom padding of the currently selected view.</p>
- *
- * @return the height of the bottom padding for the selection
- */
- public int getSelectionPaddingBottom() {
- return mSelectionBottomPadding;
- }
-
@Override
public boolean isInTouchMode() {
// WARNING: Please read the comment where mListSelectionHidden is declared
@@ -1608,5 +1648,36 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
if (mWrapped != null) mWrapped.onClick(v);
}
}
-
+
+ private class PopupDataSetObserver extends DataSetObserver {
+ @Override
+ public void onChanged() {
+ if (isPopupShowing()) {
+ // This will resize the popup to fit the new adapter's content
+ showDropDown();
+ } else if (mAdapter != null) {
+ // If the popup is not showing already, showing it will cause
+ // the list of data set observers attached to the adapter to
+ // change. We can't do it from here, because we are in the middle
+ // of iterating throught he list of observers.
+ post(new Runnable() {
+ public void run() {
+ final ListAdapter adapter = mAdapter;
+ if (adapter != null) {
+ updateDropDownForFilter(adapter.getCount());
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onInvalidated() {
+ if (!mDropDownAlwaysVisible) {
+ // There's no data to display so make sure we're not showing
+ // the drop down and its list
+ dismissDropDown();
+ }
+ }
+ }
}
diff --git a/core/java/android/widget/BaseExpandableListAdapter.java b/core/java/android/widget/BaseExpandableListAdapter.java
index 1bba7f0..396b7ae 100644
--- a/core/java/android/widget/BaseExpandableListAdapter.java
+++ b/core/java/android/widget/BaseExpandableListAdapter.java
@@ -18,7 +18,6 @@ package android.widget;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
-import android.view.KeyEvent;
/**
* Base class for a {@link ExpandableListAdapter} used to provide data and Views
@@ -31,7 +30,8 @@ import android.view.KeyEvent;
* @see SimpleExpandableListAdapter
* @see SimpleCursorTreeAdapter
*/
-public abstract class BaseExpandableListAdapter implements ExpandableListAdapter {
+public abstract class BaseExpandableListAdapter implements ExpandableListAdapter,
+ HeterogeneousExpandableList {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer) {
@@ -102,5 +102,37 @@ public abstract class BaseExpandableListAdapter implements ExpandableListAdapter
public boolean isEmpty() {
return getGroupCount() == 0;
}
-
+
+
+ /**
+ * {@inheritDoc}
+ * @return 0 for any group or child position, since only one child type count is declared.
+ */
+ public int getChildType(int groupPosition, int childPosition) {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return 1 as a default value in BaseExpandableListAdapter.
+ */
+ public int getChildTypeCount() {
+ return 1;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return 0 for any groupPosition, since only one group type count is declared.
+ */
+ public int getGroupType(int groupPosition) {
+ return 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return 1 as a default value in BaseExpandableListAdapter.
+ */
+ public int getGroupTypeCount() {
+ return 1;
+ }
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index f956df6..bf63607 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -119,11 +119,11 @@ public class CheckedTextView extends TextView implements Checkable {
* @param d The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(Drawable d) {
+ if (mCheckMarkDrawable != null) {
+ mCheckMarkDrawable.setCallback(null);
+ unscheduleDrawable(mCheckMarkDrawable);
+ }
if (d != null) {
- if (mCheckMarkDrawable != null) {
- mCheckMarkDrawable.setCallback(null);
- unscheduleDrawable(mCheckMarkDrawable);
- }
d.setCallback(this);
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
@@ -132,10 +132,10 @@ public class CheckedTextView extends TextView implements Checkable {
mCheckMarkWidth = d.getIntrinsicWidth();
mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
d.setState(getDrawableState());
- mCheckMarkDrawable = d;
} else {
mPaddingRight = mBasePaddingRight;
}
+ mCheckMarkDrawable = d;
requestLayout();
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index ebaf474..1fc23ab 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -25,10 +25,10 @@ import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnChangedListener;
import com.android.internal.R;
-import com.android.internal.widget.NumberPicker;
-import com.android.internal.widget.NumberPicker.OnChangedListener;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
@@ -101,6 +101,18 @@ public class DatePicker extends FrameLayout {
mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
DateFormatSymbols dfs = new DateFormatSymbols();
String[] months = dfs.getShortMonths();
+
+ /*
+ * If the user is in a locale where the month names are numeric,
+ * use just the number instead of the "month" character for
+ * consistency with the other fields.
+ */
+ if (months[0].startsWith("1")) {
+ for (int i = 0; i < months.length; i++) {
+ months[i] = String.valueOf(i + 1);
+ }
+ }
+
mMonthPicker.setRange(1, 12, months);
mMonthPicker.setSpeed(200);
mMonthPicker.setOnChangeListener(new OnChangedListener() {
diff --git a/core/java/android/widget/DateTimeView.java b/core/java/android/widget/DateTimeView.java
new file mode 100644
index 0000000..6b845b0
--- /dev/null
+++ b/core/java/android/widget/DateTimeView.java
@@ -0,0 +1,263 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.text.format.Time;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.widget.TextView;
+import android.widget.RemoteViews.RemoteView;
+
+import com.android.internal.R;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+//
+// TODO
+// - listen for the next threshold time to update the view.
+// - listen for date format pref changed
+// - put the AM/PM in a smaller font
+//
+
+/**
+ * Displays a given time in a convenient human-readable foramt.
+ *
+ * @hide
+ */
+@RemoteView
+public class DateTimeView extends TextView {
+ private static final String TAG = "DateTimeView";
+
+ private static final long TWELVE_HOURS_IN_MINUTES = 12 * 60;
+ private static final long TWENTY_FOUR_HOURS_IN_MILLIS = 24 * 60 * 60 * 1000;
+
+ private static final int SHOW_TIME = 0;
+ private static final int SHOW_MONTH_DAY_YEAR = 1;
+
+ Date mTime;
+ long mTimeMillis;
+
+ int mLastDisplay = -1;
+ DateFormat mLastFormat;
+
+ private boolean mAttachedToWindow;
+ private long mUpdateTimeMillis;
+
+ public DateTimeView(Context context) {
+ super(context);
+ }
+
+ public DateTimeView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onDetachedFromWindow();
+ registerReceivers();
+ mAttachedToWindow = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ unregisterReceivers();
+ mAttachedToWindow = false;
+ }
+
+ @android.view.RemotableViewMethod
+ public void setTime(long time) {
+ Time t = new Time();
+ t.set(time);
+ t.second = 0;
+ mTimeMillis = t.toMillis(false);
+ mTime = new Date(t.year-1900, t.month, t.monthDay, t.hour, t.minute, 0);
+ update();
+ }
+
+ void update() {
+ if (mTime == null) {
+ return;
+ }
+
+ long start = System.nanoTime();
+
+ int display;
+ Date time = mTime;
+
+ Time t = new Time();
+ t.set(mTimeMillis);
+ t.second = 0;
+
+ t.hour -= 12;
+ long twelveHoursBefore = t.toMillis(false);
+ t.hour += 12;
+ long twelveHoursAfter = t.toMillis(false);
+ t.hour = 0;
+ t.minute = 0;
+ long midnightBefore = t.toMillis(false);
+ t.monthDay++;
+ long midnightAfter = t.toMillis(false);
+
+ long nowMillis = System.currentTimeMillis();
+ t.set(nowMillis);
+ t.second = 0;
+ nowMillis = t.normalize(false);
+
+ // Choose the display mode
+ choose_display: {
+ if ((nowMillis >= midnightBefore && nowMillis < midnightAfter)
+ || (nowMillis >= twelveHoursBefore && nowMillis < twelveHoursAfter)) {
+ display = SHOW_TIME;
+ break choose_display;
+ }
+ // Else, show month day and year.
+ display = SHOW_MONTH_DAY_YEAR;
+ break choose_display;
+ }
+
+ // Choose the format
+ DateFormat format;
+ if (display == mLastDisplay && mLastFormat != null) {
+ // use cached format
+ format = mLastFormat;
+ } else {
+ switch (display) {
+ case SHOW_TIME:
+ format = getTimeFormat();
+ break;
+ case SHOW_MONTH_DAY_YEAR:
+ format = getDateFormat();
+ break;
+ default:
+ throw new RuntimeException("unknown display value: " + display);
+ }
+ mLastFormat = format;
+ }
+
+ // Set the text
+ String text = format.format(mTime);
+ setText(text);
+
+ // Schedule the next update
+ if (display == SHOW_TIME) {
+ // Currently showing the time, update at the later of twelve hours after or midnight.
+ mUpdateTimeMillis = twelveHoursAfter > midnightAfter ? twelveHoursAfter : midnightAfter;
+ } else {
+ // Currently showing the date
+ if (mTimeMillis < nowMillis) {
+ // If the time is in the past, don't schedule an update
+ mUpdateTimeMillis = 0;
+ } else {
+ // If hte time is in the future, schedule one at the earlier of twelve hours
+ // before or midnight before.
+ mUpdateTimeMillis = twelveHoursBefore < midnightBefore
+ ? twelveHoursBefore : midnightBefore;
+ }
+ }
+ if (false) {
+ Log.d(TAG, "update needed for '" + time + "' at '" + new Date(mUpdateTimeMillis)
+ + "' - text=" + text);
+ }
+
+ long finish = System.nanoTime();
+ }
+
+ private DateFormat getTimeFormat() {
+ int res;
+ Context context = getContext();
+ if (android.text.format.DateFormat.is24HourFormat(context)) {
+ res = R.string.twenty_four_hour_time_format;
+ } else {
+ res = R.string.twelve_hour_time_format;
+ }
+ String format = context.getString(res);
+ return new SimpleDateFormat(format);
+ }
+
+ private DateFormat getDateFormat() {
+ String format = Settings.System.getString(getContext().getContentResolver(),
+ Settings.System.DATE_FORMAT);
+ if (format == null || "".equals(format)) {
+ return DateFormat.getDateInstance(DateFormat.SHORT);
+ } else {
+ try {
+ return new SimpleDateFormat(format);
+ } catch (IllegalArgumentException e) {
+ // If we tried to use a bad format string, fall back to a default.
+ return DateFormat.getDateInstance(DateFormat.SHORT);
+ }
+ }
+ }
+
+ private void registerReceivers() {
+ Context context = getContext();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+
+ Uri uri = Settings.System.getUriFor(Settings.System.DATE_FORMAT);
+ context.getContentResolver().registerContentObserver(uri, true, mContentObserver);
+ }
+
+ private void unregisterReceivers() {
+ Context context = getContext();
+ context.unregisterReceiver(mBroadcastReceiver);
+ context.getContentResolver().unregisterContentObserver(mContentObserver);
+ }
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)) {
+ if (System.currentTimeMillis() < mUpdateTimeMillis) {
+ // The update() function takes a few milliseconds to run because of
+ // all of the time conversions it needs to do, so we can't do that
+ // every minute.
+ return;
+ }
+ }
+ // ACTION_TIME_CHANGED can also signal a change of 12/24 hr. format.
+ mLastFormat = null;
+ update();
+ }
+ };
+
+ private ContentObserver mContentObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mLastFormat = null;
+ update();
+ }
+ };
+}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 57aca24..1532db1 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -16,9 +16,13 @@
package android.widget;
-import android.text.*;
-import android.text.method.*;
import android.content.Context;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.ArrowKeyMovementMethod;
+import android.text.method.MovementMethod;
import android.util.AttributeSet;
diff --git a/core/java/android/widget/ExpandableListAdapter.java b/core/java/android/widget/ExpandableListAdapter.java
index b75983c..7f6781b 100644
--- a/core/java/android/widget/ExpandableListAdapter.java
+++ b/core/java/android/widget/ExpandableListAdapter.java
@@ -17,7 +17,6 @@
package android.widget;
import android.database.DataSetObserver;
-import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -108,7 +107,7 @@ public interface ExpandableListAdapter {
/**
* Gets a View that displays the given group. This View is only for the
* group--the Views for the group's children will be fetched using
- * getChildrenView.
+ * {@link #getChildView(int, int, boolean, View, ViewGroup)}.
*
* @param groupPosition the position of the group for which the View is
* returned
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
index ccce7c1..2ff6b70 100644
--- a/core/java/android/widget/ExpandableListConnector.java
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -25,7 +25,6 @@ import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
/*
* Implementation notes:
@@ -68,7 +67,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
private int mMaxExpGroupCount = Integer.MAX_VALUE;
/** Change observer used to have ExpandableListAdapter changes pushed to us */
- private DataSetObserver mDataSetObserver = new MyDataSetObserver();
+ private final DataSetObserver mDataSetObserver = new MyDataSetObserver();
/**
* Constructs the connector
@@ -443,8 +442,8 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
View retValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
- retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
- .isExpanded(), convertView, parent);
+ retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos,
+ posMetadata.isExpanded(), convertView, parent);
} else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
@@ -465,10 +464,21 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
int retValue;
- if (pos.type == ExpandableListPosition.GROUP) {
- retValue = 0;
+ if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+ HeterogeneousExpandableList adapter =
+ (HeterogeneousExpandableList) mExpandableListAdapter;
+ if (pos.type == ExpandableListPosition.GROUP) {
+ retValue = adapter.getGroupType(pos.groupPos);
+ } else {
+ final int childType = adapter.getChildType(pos.groupPos, pos.childPos);
+ retValue = adapter.getGroupTypeCount() + childType;
+ }
} else {
- retValue = 1;
+ if (pos.type == ExpandableListPosition.GROUP) {
+ retValue = 0;
+ } else {
+ retValue = 1;
+ }
}
pos.recycle();
@@ -478,7 +488,13 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
@Override
public int getViewTypeCount() {
- return 2;
+ if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+ HeterogeneousExpandableList adapter =
+ (HeterogeneousExpandableList) mExpandableListAdapter;
+ return adapter.getGroupTypeCount() + adapter.getChildTypeCount();
+ } else {
+ return 2;
+ }
}
@Override
@@ -637,7 +653,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
// Check to see if it's already expanded
if (posMetadata.groupMetadata != null) return false;
- /* Restrict number of exp groups to mMaxExpGroupCount */
+ /* Restrict number of expanded groups to mMaxExpGroupCount */
if (mExpGroupMetadataList.size() >= mMaxExpGroupCount) {
/* Collapse a group */
// TODO: Collapse something not on the screen instead of the first one?
@@ -850,7 +866,7 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
* position to either a) group position for groups, or b) child position for
* children
*/
- static class GroupMetadata implements Parcelable, Comparable {
+ static class GroupMetadata implements Parcelable, Comparable<GroupMetadata> {
final static int REFRESH = -1;
/** This group's flat list position */
@@ -886,12 +902,12 @@ class ExpandableListConnector extends BaseAdapter implements Filterable {
return gm;
}
- public int compareTo(Object another) {
- if (another == null || !(another instanceof GroupMetadata)) {
- throw new ClassCastException();
+ public int compareTo(GroupMetadata another) {
+ if (another == null) {
+ throw new IllegalArgumentException();
}
- return gPos - ((GroupMetadata) another).gPos;
+ return gPos - another.gPos;
}
public int describeContents() {
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 6abb2ae..8bd797b 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -18,14 +18,12 @@ package android.widget;
import com.android.internal.R;
-import java.util.ArrayList;
-
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -33,9 +31,10 @@ import android.view.ContextMenu;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ExpandableListConnector.PositionMetadata;
+import java.util.ArrayList;
+
/**
* A view that shows items in a vertically scrolling two-level list. This
* differs from the {@link ListView} by allowing two levels: groups which can
@@ -390,7 +389,8 @@ public class ExpandableListView extends ListView {
// Only proceed as possible child if the divider isn't above all items (if it is above
// all items, then the item below it has to be a group)
if (flatListPosition >= 0) {
- PositionMetadata pos = mConnector.getUnflattenedPos(flatListPosition);
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+ PositionMetadata pos = mConnector.getUnflattenedPos(adjustedPosition);
// If this item is a child, or it is a non-empty group that is expanded
if ((pos.position.type == ExpandableListPosition.CHILD) || (pos.isExpanded() &&
pos.groupMetadata.lastChildFlPos != pos.groupMetadata.flPos)) {
@@ -483,19 +483,48 @@ public class ExpandableListView extends ListView {
return mAdapter;
}
+ /**
+ * @param position An absolute (including header and footer) flat list position.
+ * @return true if the position corresponds to a header or a footer item.
+ */
+ private boolean isHeaderOrFooterPosition(int position) {
+ final int footerViewsStart = mItemCount - getFooterViewsCount();
+ return (position < getHeaderViewsCount() || position >= footerViewsStart);
+ }
+
+ /**
+ * Converts an absolute item flat position into a group/child flat position, shifting according
+ * to the number of header items.
+ *
+ * @param flatListPosition The absolute flat position
+ * @return A group/child flat position as expected by the connector.
+ */
+ private int getFlatPositionForConnector(int flatListPosition) {
+ return flatListPosition - getHeaderViewsCount();
+ }
+
+ /**
+ * Converts a group/child flat position into an absolute flat position, that takes into account
+ * the possible headers.
+ *
+ * @param flatListPosition The child/group flat position
+ * @return An absolute flat position.
+ */
+ private int getAbsoluteFlatPosition(int flatListPosition) {
+ return flatListPosition + getHeaderViewsCount();
+ }
+
@Override
public boolean performItemClick(View v, int position, long id) {
// Ignore clicks in header/footers
- final int headerViewsCount = getHeaderViewsCount();
- final int footerViewsStart = mItemCount - getFooterViewsCount();
-
- if (position < headerViewsCount || position >= footerViewsStart) {
+ if (isHeaderOrFooterPosition(position)) {
// Clicked on a header/footer, so ignore pass it on to super
return super.performItemClick(v, position, id);
}
// Internally handle the item click
- return handleItemClick(v, position - headerViewsCount, id);
+ final int adjustedPosition = getFlatPositionForConnector(position);
+ return handleItemClick(v, adjustedPosition, id);
}
/**
@@ -514,37 +543,43 @@ public class ExpandableListView extends ListView {
boolean returnValue;
if (posMetadata.position.type == ExpandableListPosition.GROUP) {
/* It's a group, so handle collapsing/expanding */
-
+
+ /* It's a group click, so pass on event */
+ if (mOnGroupClickListener != null) {
+ if (mOnGroupClickListener.onGroupClick(this, v,
+ posMetadata.position.groupPos, id)) {
+ posMetadata.recycle();
+ return true;
+ }
+ }
+
if (posMetadata.isExpanded()) {
/* Collapse it */
mConnector.collapseGroup(posMetadata);
playSoundEffect(SoundEffectConstants.CLICK);
-
+
if (mOnGroupCollapseListener != null) {
mOnGroupCollapseListener.onGroupCollapse(posMetadata.position.groupPos);
}
-
} else {
- /* It's a group click, so pass on event */
- if (mOnGroupClickListener != null) {
- if (mOnGroupClickListener.onGroupClick(this, v,
- posMetadata.position.groupPos, id)) {
- posMetadata.recycle();
- return true;
- }
- }
-
/* Expand it */
mConnector.expandGroup(posMetadata);
playSoundEffect(SoundEffectConstants.CLICK);
-
+
if (mOnGroupExpandListener != null) {
mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos);
}
+
+ final int groupPos = posMetadata.position.groupPos;
+ final int groupFlatPos = posMetadata.position.flatListPos;
+
+ final int shiftedGroupPosition = groupFlatPos + getHeaderViewsCount();
+ smoothScrollToPosition(shiftedGroupPosition + mAdapter.getChildrenCount(groupPos),
+ shiftedGroupPosition);
}
-
+
returnValue = true;
} else {
/* It's a child, so pass on event */
@@ -553,12 +588,12 @@ public class ExpandableListView extends ListView {
return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
posMetadata.position.childPos, id);
}
-
+
returnValue = false;
}
-
+
posMetadata.recycle();
-
+
return returnValue;
}
@@ -685,8 +720,8 @@ public class ExpandableListView extends ListView {
}
/**
- * Converts a flat list position (the raw position of an item (child or
- * group) in the list) to an group and/or child position (represented in a
+ * Converts a flat list position (the raw position of an item (child or group)
+ * in the list) to an group and/or child position (represented in a
* packed position). This is useful in situations where the caller needs to
* use the underlying {@link ListView}'s methods. Use
* {@link ExpandableListView#getPackedPositionType} ,
@@ -695,10 +730,16 @@ public class ExpandableListView extends ListView {
*
* @param flatListPosition The flat list position to be converted.
* @return The group and/or child position for the given flat list position
- * in packed position representation.
+ * in packed position representation. #PACKED_POSITION_VALUE_NULL if
+ * the position corresponds to a header or a footer item.
*/
public long getExpandableListPosition(int flatListPosition) {
- PositionMetadata pm = mConnector.getUnflattenedPos(flatListPosition);
+ if (isHeaderOrFooterPosition(flatListPosition)) {
+ return PACKED_POSITION_VALUE_NULL;
+ }
+
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
+ PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
long packedPos = pm.position.getPackedPosition();
pm.recycle();
return packedPos;
@@ -718,9 +759,9 @@ public class ExpandableListView extends ListView {
public int getFlatListPosition(long packedPosition) {
PositionMetadata pm = mConnector.getFlattenedPos(ExpandableListPosition
.obtainPosition(packedPosition));
- int retValue = pm.position.flatListPos;
+ final int flatListPosition = pm.position.flatListPos;
pm.recycle();
- return retValue;
+ return getAbsoluteFlatPosition(flatListPosition);
}
/**
@@ -728,12 +769,13 @@ public class ExpandableListView extends ListView {
* its type). Can return {@link #PACKED_POSITION_VALUE_NULL} if no selection.
*
* @return A packed position containing the currently selected group or
- * child's position and type. #PACKED_POSITION_VALUE_NULL if no selection.
+ * child's position and type. #PACKED_POSITION_VALUE_NULL if no selection
+ * or if selection is on a header or a footer item.
*/
public long getSelectedPosition() {
final int selectedPos = getSelectedItemPosition();
- if (selectedPos == -1) return PACKED_POSITION_VALUE_NULL;
-
+
+ // The case where there is no selection (selectedPos == -1) is also handled here.
return getExpandableListPosition(selectedPos);
}
@@ -768,7 +810,8 @@ public class ExpandableListView extends ListView {
.obtainGroupPosition(groupPosition);
PositionMetadata pm = mConnector.getFlattenedPos(elGroupPos);
elGroupPos.recycle();
- super.setSelection(pm.position.flatListPos);
+ final int absoluteFlatPosition = getAbsoluteFlatPosition(pm.position.flatListPos);
+ super.setSelection(absoluteFlatPosition);
pm.recycle();
}
@@ -804,7 +847,8 @@ public class ExpandableListView extends ListView {
}
}
- super.setSelection(flatChildPos.position.flatListPos);
+ int absoluteFlatPosition = getAbsoluteFlatPosition(flatChildPos.position.flatListPos);
+ super.setSelection(absoluteFlatPosition);
elChildPos.recycle();
flatChildPos.recycle();
@@ -917,13 +961,12 @@ public class ExpandableListView extends ListView {
@Override
ContextMenuInfo createContextMenuInfo(View view, int flatListPosition, long id) {
- // Adjust for and handle for header views
- final int adjustedPosition = flatListPosition - getHeaderViewsCount();
- if (adjustedPosition < 0) {
- // Return normal info for header view context menus
+ if (isHeaderOrFooterPosition(flatListPosition)) {
+ // Return normal info for header/footer view context menus
return new AdapterContextMenuInfo(view, flatListPosition, id);
}
+ final int adjustedPosition = getFlatPositionForConnector(flatListPosition);
PositionMetadata pm = mConnector.getUnflattenedPos(adjustedPosition);
ExpandableListPosition pos = pm.position;
pm.recycle();
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 67c0def..54c4b36 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.SystemClock;
import android.util.TypedValue;
import android.view.MotionEvent;
+import android.widget.AbsListView.OnScrollListener;
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
@@ -428,12 +429,23 @@ class FastScroller {
if (mListAdapter == null && mList != null) {
getSectionsFromIndexer();
}
+ if (mList != null) {
+ mList.requestDisallowInterceptTouchEvent(true);
+ mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ }
cancelFling();
return true;
}
- } else if (action == MotionEvent.ACTION_UP) {
+ } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here
if (mState == STATE_DRAGGING) {
+ if (mList != null) {
+ // ViewGroup does the right thing already, but there might
+ // be other classes that don't properly reset on touch-up,
+ // so do this explicitly just in case.
+ mList.requestDisallowInterceptTouchEvent(false);
+ mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+ }
setState(STATE_VISIBLE);
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 3afd5d4..e27bb4f 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -164,12 +164,12 @@ public class FrameLayout extends ViewGroup {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
- * and a height of {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}.
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
+ * and a height of {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}.
*/
@Override
protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
/**
@@ -304,8 +304,8 @@ public class FrameLayout extends ViewGroup {
childLeft = parentLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
- childLeft = parentLeft + (parentRight - parentLeft + lp.leftMargin +
- lp.rightMargin - width) / 2;
+ childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
@@ -319,8 +319,8 @@ public class FrameLayout extends ViewGroup {
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
- childTop = parentTop + (parentBottom - parentTop + lp.topMargin +
- lp.bottomMargin - height) / 2;
+ childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+ lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
@@ -467,9 +467,9 @@ public class FrameLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width, height
* and weight.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param gravity the gravity
*
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index ffe9908..d2829db 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -431,8 +431,6 @@ public class GridView extends AbsListView {
}
}
}
-
- return mFirstPosition + childCount - 1;
}
return INVALID_POSITION;
}
@@ -931,15 +929,16 @@ public class GridView extends AbsListView {
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
final int count = mItemCount;
if (count > 0) {
- final View child = obtainView(0);
+ final View child = obtainView(0, mIsScrap);
AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(0);
+ p.forceAdd = true;
int childHeightSpec = getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
@@ -1129,11 +1128,13 @@ public class GridView extends AbsListView {
default:
if (childCount == 0) {
if (!mStackFromBottom) {
- setSelectedPositionInt(0);
+ setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
+ INVALID_POSITION : 0);
sel = fillFromTop(childrenTop);
} else {
final int last = mItemCount - 1;
- setSelectedPositionInt(last);
+ setSelectedPositionInt(mAdapter == null || isInTouchMode() ?
+ INVALID_POSITION : last);
sel = fillFromBottom(last, childrenBottom);
}
} else {
@@ -1203,7 +1204,7 @@ public class GridView extends AbsListView {
View child;
if (!mDataChanged) {
- // Try to use an exsiting view for this position
+ // Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
@@ -1215,10 +1216,10 @@ public class GridView extends AbsListView {
// Make a new view for this position, or convert an unused view if
// possible
- child = obtainView(position);
+ child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
- setupChild(child, position, y, flow, childrenLeft, selected, false, where);
+ setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0], where);
return child;
}
@@ -1254,14 +1255,15 @@ public class GridView extends AbsListView {
// some up...
AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
- if (recycled) {
+ if (recycled && !p.forceAdd) {
attachViewToParent(child, where, p);
} else {
+ p.forceAdd = false;
addViewInLayout(child, where, p, true);
}
@@ -1856,8 +1858,11 @@ public class GridView extends AbsListView {
final int top = view.getTop();
int height = view.getHeight();
if (height > 0) {
- final int whichRow = mFirstPosition / mNumColumns;
- return Math.max(whichRow * 100 - (top * 100) / height, 0);
+ final int numColumns = mNumColumns;
+ final int whichRow = mFirstPosition / numColumns;
+ final int rowCount = (mItemCount + numColumns - 1) / numColumns;
+ return Math.max(whichRow * 100 - (top * 100) / height +
+ (int) ((float) mScrollY / getHeight() * rowCount * 100), 0);
}
}
return 0;
diff --git a/core/java/android/widget/HeaderViewListAdapter.java b/core/java/android/widget/HeaderViewListAdapter.java
index b0e5f7e..e2a269e 100644
--- a/core/java/android/widget/HeaderViewListAdapter.java
+++ b/core/java/android/widget/HeaderViewListAdapter.java
@@ -28,17 +28,24 @@ import java.util.ArrayList;
* associated data objects.
*<p>This is intended as a base class; you will probably not need to
* use this class directly in your own code.
- *
*/
public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
- private ListAdapter mAdapter;
+ private final ListAdapter mAdapter;
+ // These two ArrayList are assumed to NOT be null.
+ // They are indeed created when declared in ListView and then shared.
ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
+
+ // Used as a placeholder in case the provided info views are indeed null.
+ // Currently only used by some CTS tests, which may be removed.
+ static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
+ new ArrayList<ListView.FixedViewInfo>();
+
boolean mAreAllFixedViewsSelectable;
- private boolean mIsFilterable;
+ private final boolean mIsFilterable;
public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
@@ -46,8 +53,17 @@ public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
mAdapter = adapter;
mIsFilterable = adapter instanceof Filterable;
- mHeaderViewInfos = headerViewInfos;
- mFooterViewInfos = footerViewInfos;
+ if (headerViewInfos == null) {
+ mHeaderViewInfos = EMPTY_INFO_LIST;
+ } else {
+ mHeaderViewInfos = headerViewInfos;
+ }
+
+ if (footerViewInfos == null) {
+ mFooterViewInfos = EMPTY_INFO_LIST;
+ } else {
+ mFooterViewInfos = footerViewInfos;
+ }
mAreAllFixedViewsSelectable =
areAllListInfosSelectable(mHeaderViewInfos)
@@ -55,11 +71,11 @@ public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
}
public int getHeadersCount() {
- return mHeaderViewInfos == null ? 0 : mHeaderViewInfos.size();
+ return mHeaderViewInfos.size();
}
public int getFootersCount() {
- return mFooterViewInfos == null ? 0 : mFooterViewInfos.size();
+ return mFooterViewInfos.size();
}
public boolean isEmpty() {
@@ -128,43 +144,53 @@ public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
}
public boolean isEnabled(int position) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
- if (mAdapter != null && position >= numHeaders) {
- int adjPosition = position - numHeaders;
- int adapterCount = mAdapter.getCount();
- if (adjPosition >= adapterCount && mFooterViewInfos != null) {
- return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
- } else {
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).isSelectable;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
return mAdapter.isEnabled(adjPosition);
}
- } else if (position < numHeaders && mHeaderViewInfos != null) {
- return mHeaderViewInfos.get(position).isSelectable;
}
- return true;
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
}
public Object getItem(int position) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
- if (mAdapter != null && position >= numHeaders) {
- int adjPosition = position - numHeaders;
- int adapterCount = mAdapter.getCount();
- if (adjPosition >= adapterCount && mFooterViewInfos != null) {
- return mFooterViewInfos.get(adjPosition - adapterCount).data;
- } else {
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).data;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
return mAdapter.getItem(adjPosition);
}
- } else if (position < numHeaders && mHeaderViewInfos != null) {
- return mHeaderViewInfos.get(position).data;
}
- return null;
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).data;
}
public long getItemId(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
- int adapterCnt = mAdapter.getCount();
- if (adjPosition < adapterCnt) {
+ int adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
return mAdapter.getItemId(adjPosition);
}
}
@@ -179,21 +205,24 @@ public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
}
public View getView(int position, View convertView, ViewGroup parent) {
+ // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
int numHeaders = getHeadersCount();
- if (mAdapter != null && position >= numHeaders) {
- int adjPosition = position - numHeaders;
- int adapterCount = mAdapter.getCount();
- if (adjPosition >= adapterCount) {
- if (mFooterViewInfos != null) {
- return mFooterViewInfos.get(adjPosition - adapterCount).view;
- }
- } else {
+ if (position < numHeaders) {
+ return mHeaderViewInfos.get(position).view;
+ }
+
+ // Adapter
+ final int adjPosition = position - numHeaders;
+ int adapterCount = 0;
+ if (mAdapter != null) {
+ adapterCount = mAdapter.getCount();
+ if (adjPosition < adapterCount) {
return mAdapter.getView(adjPosition, convertView, parent);
}
- } else if (position < numHeaders) {
- return mHeaderViewInfos.get(position).view;
}
- return null;
+
+ // Footer (off-limits positions will throw an ArrayIndexOutOfBoundsException)
+ return mFooterViewInfos.get(adjPosition - adapterCount).view;
}
public int getItemViewType(int position) {
diff --git a/core/java/android/widget/HeterogeneousExpandableList.java b/core/java/android/widget/HeterogeneousExpandableList.java
new file mode 100644
index 0000000..1292733
--- /dev/null
+++ b/core/java/android/widget/HeterogeneousExpandableList.java
@@ -0,0 +1,109 @@
+/*
+ * 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.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Additional methods that when implemented make an
+ * {@link ExpandableListAdapter} take advantage of the {@link Adapter} view type
+ * mechanism.
+ *
+ * An {@link ExpandableListAdapter} declares one view type for its group items
+ * and one view type for its child items. Although adapted for most {@link ExpandableListView}s,
+ * these values should be tuned heterogeneous {@link ExpandableListView}s. Lists that contain
+ * different types of group and/or child item views, should use an adapter that implements this
+ * interface. This way, the recycled views that will be provided to
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * and
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * will be of the appropriate group or child type, resulting in a more efficient reuse of the
+ * previously created views.
+ */
+public interface HeterogeneousExpandableList {
+ /**
+ * Get the type of group View that will be created by
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . for the specified group item.
+ *
+ * @param groupPosition the position of the group for which the type should be returned.
+ * @return An integer representing the type of group View. Two group views should share the same
+ * type if one can be converted to the other in
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . Note: Integers must be in the range 0 to {@link #getGroupTypeCount} - 1.
+ * {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+ * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+ * @see getGroupTypeCount()
+ */
+ int getGroupType(int groupPosition);
+
+ /**
+ * Get the type of child View that will be created by
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * for the specified child item.
+ *
+ * @param groupPosition the position of the group that the child resides in
+ * @param childPosition the position of the child with respect to other children in the group
+ * @return An integer representing the type of child View. Two child views should share the same
+ * type if one can be converted to the other in
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * Note: Integers must be in the range 0 to {@link #getChildTypeCount} - 1.
+ * {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+ * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+ * @see getChildTypeCount()
+ */
+ int getChildType(int groupPosition, int childPosition);
+
+ /**
+ * <p>
+ * Returns the number of types of group Views that will be created by
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . Each type represents a set of views that can be converted in
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * . If the adapter always returns the same type of View for all group items, this method should
+ * return 1.
+ * </p>
+ * <p>
+ * This method will only be called when the adapter is set on the {@link AdapterView}.
+ * </p>
+ *
+ * @return The number of types of group Views that will be created by this adapter.
+ * @see getChildTypeCount()
+ * @see getGroupType()
+ */
+ int getGroupTypeCount();
+
+ /**
+ * <p>
+ * Returns the number of types of child Views that will be created by
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * . Each type represents a set of views that can be converted in
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * , for any group. If the adapter always returns the same type of View for
+ * all child items, this method should return 1.
+ * </p>
+ * <p>
+ * This method will only be called when the adapter is set on the {@link AdapterView}.
+ * </p>
+ *
+ * @return The total number of types of child Views that will be created by this adapter.
+ * @see getGroupTypeCount()
+ * @see getChildType()
+ */
+ int getChildTypeCount();
+}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 52f56a7..32a9146 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,19 +16,19 @@
package android.widget;
-import android.util.AttributeSet;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Rect;
-import android.view.View;
+import android.util.AttributeSet;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.VelocityTracker;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.KeyEvent;
-import android.view.FocusFinder;
-import android.view.MotionEvent;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
-import android.content.Context;
-import android.content.res.TypedArray;
import java.util.List;
@@ -116,7 +116,19 @@ public class HorizontalScrollView extends FrameLayout {
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
-
+
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
+
public HorizontalScrollView(Context context) {
this(context, null);
}
@@ -306,11 +318,7 @@ public class HorizontalScrollView extends FrameLayout {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Let the focused view and/or our descendants get the key first
- boolean handled = super.dispatchKeyEvent(event);
- if (handled) {
- return true;
- }
- return executeKeyEvent(event);
+ return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
/**
@@ -362,6 +370,18 @@ public class HorizontalScrollView extends FrameLayout {
return handled;
}
+ private boolean inChild(int x, int y) {
+ if (getChildCount() > 0) {
+ final int scrollX = mScrollX;
+ final View child = getChildAt(0);
+ return !(y < child.getTop()
+ || y >= child.getBottom()
+ || x < child.getLeft() - scrollX
+ || x >= child.getRight() - scrollX);
+ }
+ return false;
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
@@ -380,15 +400,8 @@ public class HorizontalScrollView extends FrameLayout {
return true;
}
- if (!canScroll()) {
- mIsBeingDragged = false;
- return false;
- }
-
- final float x = ev.getX();
-
- switch (action) {
- case MotionEvent.ACTION_MOVE:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
@@ -398,16 +411,36 @@ public class HorizontalScrollView extends FrameLayout {
* Locally do absolute value. mLastMotionX is set to the x value
* of the down event.
*/
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ // If we don't have a valid id, the touch down wasn't on content.
+ break;
+ }
+
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float x = ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
+ mLastMotionX = x;
if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
}
break;
+ }
- case MotionEvent.ACTION_DOWN:
- /* Remember location of down touch */
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ if (!inChild((int) x, (int) ev.getY())) {
+ mIsBeingDragged = false;
+ break;
+ }
+
+ /*
+ * Remember location of down touch.
+ * ACTION_DOWN always refers to pointer index 0.
+ */
mLastMotionX = x;
+ mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag;
@@ -416,11 +449,16 @@ public class HorizontalScrollView extends FrameLayout {
*/
mIsBeingDragged = !mScroller.isFinished();
break;
+ }
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
+ mActivePointerId = INVALID_POINTER;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
break;
}
@@ -440,64 +478,106 @@ public class HorizontalScrollView extends FrameLayout {
return false;
}
- if (!canScroll()) {
- return false;
- }
-
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
- final float x = ev.getX();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = ev.getX();
+ if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
+ return false;
+ }
+
/*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
+ mActivePointerId = ev.getPointerId(0);
break;
+ }
case MotionEvent.ACTION_MOVE:
- // Scroll to follow the motion event
- final int deltaX = (int) (mLastMotionX - x);
- mLastMotionX = x;
-
- if (deltaX < 0) {
- if (mScrollX > 0) {
- scrollBy(deltaX, 0);
- }
- } else if (deltaX > 0) {
- final int rightEdge = getWidth() - mPaddingRight;
- final int availableToScroll = getChildAt(0).getRight() - mScrollX - rightEdge;
- if (availableToScroll > 0) {
- scrollBy(Math.min(availableToScroll, deltaX), 0);
- }
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float x = ev.getX(activePointerIndex);
+ final int deltaX = (int) (mLastMotionX - x);
+ mLastMotionX = x;
+
+ scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) velocityTracker.getXVelocity();
+ if (mIsBeingDragged) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
- if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
- fling(-initialVelocity);
- }
+ if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
+ fling(-initialVelocity);
+ }
+
+ mActivePointerId = INVALID_POINTER;
+ mIsBeingDragged = false;
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
}
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsBeingDragged && getChildCount() > 0) {
+ mActivePointerId = INVALID_POINTER;
+ mIsBeingDragged = false;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
}
return true;
}
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = ev.getX(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private int getScrollRange() {
+ int scrollRange = 0;
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ scrollRange = Math.max(0,
+ child.getWidth() - getWidth() - mPaddingLeft - mPaddingRight);
+ }
+ return scrollRange;
+ }
/**
* <p>
@@ -826,10 +906,19 @@ public class HorizontalScrollView extends FrameLayout {
* @param dy the number of pixels to scroll by on the Y axis
*/
public final void smoothScrollBy(int dx, int dy) {
+ if (getChildCount() == 0) {
+ // Nothing to do.
+ return;
+ }
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
- mScroller.startScroll(mScrollX, mScrollY, dx, dy);
- awakenScrollBars(mScroller.getDuration());
+ final int width = getWidth() - mPaddingRight - mPaddingLeft;
+ final int right = getChildAt(0).getWidth();
+ final int maxX = Math.max(0, right - width);
+ final int scrollX = mScrollX;
+ dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;
+
+ mScroller.startScroll(scrollX, mScrollY, dx, 0);
invalidate();
} else {
if (!mScroller.isFinished()) {
@@ -856,10 +945,19 @@ public class HorizontalScrollView extends FrameLayout {
*/
@Override
protected int computeHorizontalScrollRange() {
- int count = getChildCount();
- return count == 0 ? getWidth() : getChildAt(0).getRight();
+ final int count = getChildCount();
+ final int contentWidth = getWidth() - mPaddingLeft - mPaddingRight;
+ if (count == 0) {
+ return contentWidth;
+ }
+
+ return getChildAt(0).getRight();
+ }
+
+ @Override
+ protected int computeHorizontalScrollOffset() {
+ return Math.max(0, super.computeHorizontalScrollOffset());
}
-
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
@@ -913,17 +1011,18 @@ public class HorizontalScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
+
if (getChildCount() > 0) {
View child = getChildAt(0);
- mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
- mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
- } else {
- mScrollX = x;
- mScrollY = y;
- }
- if (oldX != mScrollX || oldY != mScrollY) {
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+ if (x != oldX || y != oldY) {
+ mScrollX = x;
+ mScrollY = y;
+ onScrollChanged(x, y, oldX, oldY);
+ }
}
+ awakenScrollBars();
// Keep on drawing until the animation has finished.
postInvalidate();
@@ -1156,7 +1255,8 @@ public class HorizontalScrollView extends FrameLayout {
int width = getWidth() - mPaddingRight - mPaddingLeft;
int right = getChildAt(0).getWidth();
- mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0);
+ mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
+ Math.max(0, right - width), 0, 0);
final boolean movingRight = velocityX > 0;
@@ -1173,7 +1273,6 @@ public class HorizontalScrollView extends FrameLayout {
mScrollViewMovedFocus = false;
}
- awakenScrollBars(mScroller.getDuration());
invalidate();
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b8f0a7e..c77416b 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -31,6 +32,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.RemotableViewMethod;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -68,6 +70,7 @@ public class ImageView extends View {
private ColorFilter mColorFilter;
private int mAlpha = 255;
private int mViewAlphaScale = 256;
+ private boolean mColorMod = false;
private Drawable mDrawable = null;
private int[] mState = null;
@@ -137,7 +140,7 @@ public class ImageView extends View {
int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
if (tint != 0) {
- setColorFilter(tint, PorterDuff.Mode.SRC_ATOP);
+ setColorFilter(tint);
}
mCropToPadding = a.getBoolean(
@@ -180,6 +183,7 @@ public class ImageView extends View {
int scale = alpha + (alpha >> 7);
if (mViewAlphaScale != scale) {
mViewAlphaScale = scale;
+ mColorMod = true;
applyColorMod();
}
return true;
@@ -489,7 +493,18 @@ public class ImageView extends View {
mUri = null;
}
} else if (mUri != null) {
- if ("content".equals(mUri.getScheme())) {
+ String scheme = mUri.getScheme();
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+ try {
+ // Load drawable through Resources, to get the source density information
+ ContentResolver.OpenResourceIdResult r =
+ mContext.getContentResolver().getResourceId(mUri);
+ d = r.r.getDrawable(r.id);
+ } catch (Exception e) {
+ Log.w("ImageView", "Unable to open content: " + mUri, e);
+ }
+ } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
try {
d = Drawable.createFromStream(
mContext.getContentResolver().openInputStream(mUri),
@@ -754,8 +769,8 @@ public class ImageView extends View {
} else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
mDrawMatrix = mMatrix;
- mDrawMatrix.setTranslate((vwidth - dwidth) * 0.5f,
- (vheight - dheight) * 0.5f);
+ mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
+ (int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
@@ -771,7 +786,7 @@ public class ImageView extends View {
}
mDrawMatrix.setScale(scale, scale);
- mDrawMatrix.postTranslate(dx, dy);
+ mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
@@ -785,8 +800,8 @@ public class ImageView extends View {
(float) vheight / (float) dheight);
}
- dx = (vwidth - dwidth * scale) * 0.5f;
- dy = (vheight - dheight * scale) * 0.5f;
+ dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
+ dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
@@ -865,6 +880,18 @@ public class ImageView extends View {
setColorFilter(new PorterDuffColorFilter(color, mode));
}
+ /**
+ * Set a tinting option for the image. Assumes
+ * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
+ *
+ * @param color Color tint to apply.
+ * @attr ref android.R.styleable#ImageView_tint
+ */
+ @RemotableViewMethod
+ public final void setColorFilter(int color) {
+ setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ }
+
public final void clearColorFilter() {
setColorFilter(null);
}
@@ -877,22 +904,29 @@ public class ImageView extends View {
public void setColorFilter(ColorFilter cf) {
if (mColorFilter != cf) {
mColorFilter = cf;
+ mColorMod = true;
applyColorMod();
invalidate();
}
}
+ @RemotableViewMethod
public void setAlpha(int alpha) {
alpha &= 0xFF; // keep it legal
if (mAlpha != alpha) {
mAlpha = alpha;
+ mColorMod = true;
applyColorMod();
invalidate();
}
}
private void applyColorMod() {
- if (mDrawable != null) {
+ // Only mutate and apply when modifications have occurred. This should
+ // not reset the mColorMod flag, since these filters need to be
+ // re-applied if the Drawable is changed.
+ if (mDrawable != null && mColorMod) {
+ mDrawable = mDrawable.mutate();
mDrawable.setColorFilter(mColorFilter);
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6cc794b..bd07e1f 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -25,8 +27,6 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
/**
* A Layout that arranges its children in a single column or a single row. The direction of
@@ -50,6 +50,7 @@ public class LinearLayout extends ViewGroup {
* Whether the children of this layout are baseline aligned. Only applicable
* if {@link #mOrientation} is horizontal.
*/
+ @ViewDebug.ExportedProperty
private boolean mBaselineAligned = true;
/**
@@ -59,6 +60,7 @@ public class LinearLayout extends ViewGroup {
* Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
* with whether the children of this layout are baseline aligned.
*/
+ @ViewDebug.ExportedProperty
private int mBaselineAlignedChildIndex = -1;
/**
@@ -66,14 +68,35 @@ public class LinearLayout extends ViewGroup {
* We'll calculate the baseline of this layout as we measure vertically; for
* horizontal linear layouts, the offset of 0 is appropriate.
*/
+ @ViewDebug.ExportedProperty
private int mBaselineChildTop = 0;
+ @ViewDebug.ExportedProperty
private int mOrientation;
+ @ViewDebug.ExportedProperty(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")
+ })
private int mGravity = Gravity.LEFT | Gravity.TOP;
+ @ViewDebug.ExportedProperty
private int mTotalLength;
+ @ViewDebug.ExportedProperty
private float mWeightSum;
+ @ViewDebug.ExportedProperty
+ private boolean mUseLargestChild;
+
private int[] mMaxAscent;
private int[] mMaxDescent;
@@ -82,7 +105,7 @@ public class LinearLayout extends ViewGroup {
private static final int INDEX_CENTER_VERTICAL = 0;
private static final int INDEX_TOP = 1;
private static final int INDEX_BOTTOM = 2;
- private static final int INDEX_FILL = 3;
+ private static final int INDEX_FILL = 3;
public LinearLayout(Context context) {
super(context);
@@ -114,6 +137,9 @@ public class LinearLayout extends ViewGroup {
mBaselineAlignedChildIndex =
a.getInt(com.android.internal.R.styleable.LinearLayout_baselineAlignedChildIndex, -1);
+ // TODO: Better name, add Java APIs, make it public
+ mUseLargestChild = a.getBoolean(R.styleable.LinearLayout_useLargestChild, false);
+
a.recycle();
}
@@ -308,6 +334,9 @@ public class LinearLayout extends ViewGroup {
boolean matchWidth = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
+ final boolean useLargestChild = mUseLargestChild;
+
+ int largestChildHeight = Integer.MIN_VALUE;
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
@@ -331,32 +360,40 @@ public class LinearLayout extends ViewGroup {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
- mTotalLength += lp.topMargin + lp.bottomMargin;
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
- int oldHeight = Integer.MIN_VALUE;
-
- if (lp.height == 0 && lp.weight > 0) {
- // heightMode is either UNSPECIFIED OR AT_MOST, and this child
- // wanted to stretch to fill available space. Translate that to
- // WRAP_CONTENT so that it does not end up with a height of 0
- oldHeight = 0;
- lp.height = LayoutParams.WRAP_CONTENT;
- }
+ int oldHeight = Integer.MIN_VALUE;
+
+ if (lp.height == 0 && lp.weight > 0) {
+ // heightMode is either UNSPECIFIED or AT_MOST, and this
+ // child wanted to stretch to fill available space.
+ // Translate that to WRAP_CONTENT so that it does not end up
+ // with a height of 0
+ oldHeight = 0;
+ lp.height = LayoutParams.WRAP_CONTENT;
+ }
- // Determine how big this child would like to. If this or
- // previous children have given a weight, then we allow it to
- // use all available space (and we will shrink things later
- // if needed).
- measureChildBeforeLayout(
+ // Determine how big this child would like to be. If this or
+ // previous children have given a weight, then we allow it to
+ // use all available space (and we will shrink things later
+ // if needed).
+ measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);
- if (oldHeight != Integer.MIN_VALUE) {
+ if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
- }
+ }
- mTotalLength += child.getMeasuredHeight() + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child);
+ final int childHeight = child.getMeasuredHeight();
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+ lp.bottomMargin + getNextLocationOffset(child));
+
+ if (useLargestChild) {
+ largestChildHeight = Math.max(childHeight, largestChildHeight);
+ }
}
/**
@@ -378,7 +415,7 @@ public class LinearLayout extends ViewGroup {
}
boolean matchWidthLocally = false;
- if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.FILL_PARENT) {
+ if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
@@ -391,7 +428,7 @@ public class LinearLayout extends ViewGroup {
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
- allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
@@ -406,7 +443,32 @@ public class LinearLayout extends ViewGroup {
i += getChildrenSkipCount(child, i);
}
-
+
+ if (useLargestChild && heightMode == MeasureSpec.AT_MOST) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
+ // Account for negative margins
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
+ }
+ }
+
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
@@ -472,19 +534,21 @@ public class LinearLayout extends ViewGroup {
maxWidth = Math.max(maxWidth, measuredWidth);
boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
- lp.width == LayoutParams.FILL_PARENT;
+ lp.width == LayoutParams.MATCH_PARENT;
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
- allFillParent = allFillParent && lp.width == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
- mTotalLength += child.getMeasuredHeight() + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child);
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
+ lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// Add in our padding
- mTotalLength += mPaddingTop + mPaddingBottom;
+ mTotalLength += mPaddingTop + mPaddingBottom;
+ // TODO: Should we recompute the heightSpec based on the new total length?
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
weightedMaxWidth);
@@ -515,7 +579,7 @@ public class LinearLayout extends ViewGroup {
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = ((LinearLayout.LayoutParams)child.getLayoutParams());
- if (lp.width == LayoutParams.FILL_PARENT) {
+ if (lp.width == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured height
// FIXME: this may not be right for something like wrapping text?
int oldHeight = lp.height;
@@ -567,6 +631,11 @@ public class LinearLayout extends ViewGroup {
maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
final boolean baselineAligned = mBaselineAligned;
+ final boolean useLargestChild = mUseLargestChild;
+
+ final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
+
+ int largestChildWidth = Integer.MIN_VALUE;
// See how wide everyone is. Also remember max height.
for (int i = 0; i < count; ++i) {
@@ -582,7 +651,8 @@ public class LinearLayout extends ViewGroup {
continue;
}
- final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
totalWeight += lp.weight;
@@ -590,13 +660,19 @@ public class LinearLayout extends ViewGroup {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
- mTotalLength += lp.leftMargin + lp.rightMargin;
+ if (isExactly) {
+ mTotalLength += lp.leftMargin + lp.rightMargin;
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength +
+ lp.leftMargin + lp.rightMargin);
+ }
// Baseline alignment requires to measure widgets to obtain the
- // baseline offset (in particular for TextViews).
- // The following defeats the optimization mentioned above.
- // Allow the child to use as much space as it wants because we
- // can shrink things later (and re-measure).
+ // baseline offset (in particular for TextViews). The following
+ // defeats the optimization mentioned above. Allow the child to
+ // use as much space as it wants because we can shrink things
+ // later (and re-measure).
if (baselineAligned) {
final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(freeSpec, freeSpec);
@@ -605,7 +681,8 @@ public class LinearLayout extends ViewGroup {
int oldWidth = Integer.MIN_VALUE;
if (lp.width == 0 && lp.weight > 0) {
- // widthMode is either UNSPECIFIED OR AT_MOST, and this child
+ // widthMode is either UNSPECIFIED or AT_MOST, and this
+ // child
// wanted to stretch to fill available space. Translate that to
// WRAP_CONTENT so that it does not end up with a width of 0
oldWidth = 0;
@@ -623,13 +700,24 @@ public class LinearLayout extends ViewGroup {
if (oldWidth != Integer.MIN_VALUE) {
lp.width = oldWidth;
}
-
- mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child);
+
+ final int childWidth = child.getMeasuredWidth();
+ if (isExactly) {
+ mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+ lp.rightMargin + getNextLocationOffset(child));
+ }
+
+ if (useLargestChild) {
+ largestChildWidth = Math.max(childWidth, largestChildWidth);
+ }
}
boolean matchHeightLocally = false;
- if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.FILL_PARENT) {
+ if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
// The height of the linear layout will scale, and at least one
// child said it wanted to match our height. Set a flag indicating that
// we need to remeasure at least that view when we know our height.
@@ -657,7 +745,7 @@ public class LinearLayout extends ViewGroup {
maxHeight = Math.max(maxHeight, childHeight);
- allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Heights of weighted Views are bogus if we end up
@@ -688,6 +776,35 @@ public class LinearLayout extends ViewGroup {
maxHeight = Math.max(maxHeight, ascent + descent);
}
+ if (useLargestChild && widthMode == MeasureSpec.AT_MOST) {
+ mTotalLength = 0;
+
+ for (int i = 0; i < count; ++i) {
+ final View child = getVirtualChildAt(i);
+
+ if (child == null) {
+ mTotalLength += measureNullChild(i);
+ continue;
+ }
+
+ if (child.getVisibility() == GONE) {
+ i += getChildrenSkipCount(child, i);
+ continue;
+ }
+
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
+ child.getLayoutParams();
+ if (isExactly) {
+ mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ }
+ }
+ }
+
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
@@ -754,11 +871,17 @@ public class LinearLayout extends ViewGroup {
}
}
- mTotalLength += child.getMeasuredWidth() + lp.leftMargin +
- lp.rightMargin + getNextLocationOffset(child);
+ if (isExactly) {
+ mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
+ getNextLocationOffset(child);
+ } else {
+ final int totalLength = mTotalLength;
+ mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
+ lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+ }
boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
- lp.height == LayoutParams.FILL_PARENT;
+ lp.height == LayoutParams.MATCH_PARENT;
final int margin = lp.topMargin + lp .bottomMargin;
int childHeight = child.getMeasuredHeight() + margin;
@@ -766,7 +889,7 @@ public class LinearLayout extends ViewGroup {
alternativeMaxHeight = Math.max(alternativeMaxHeight,
matchHeightLocally ? margin : childHeight);
- allFillParent = allFillParent && lp.height == LayoutParams.FILL_PARENT;
+ allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
if (baselineAligned) {
final int childBaseline = child.getBaseline();
@@ -786,6 +909,7 @@ public class LinearLayout extends ViewGroup {
// Add in our padding
mTotalLength += mPaddingLeft + mPaddingRight;
+ // TODO: Should we update widthSize with the new total length?
// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
// the most common case
@@ -832,7 +956,7 @@ public class LinearLayout extends ViewGroup {
if (child.getVisibility() != GONE) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
- if (lp.height == LayoutParams.FILL_PARENT) {
+ if (lp.height == LayoutParams.MATCH_PARENT) {
// Temporarily force children to reuse their old measured width
// FIXME: this may not be right for something like wrapping text?
int oldWidth = lp.width;
@@ -933,7 +1057,7 @@ public class LinearLayout extends ViewGroup {
final int paddingLeft = mPaddingLeft;
int childTop = mPaddingTop;
- int childLeft = paddingLeft;
+ int childLeft;
// Where right end of child should go
final int width = mRight - mLeft;
@@ -991,6 +1115,9 @@ public class LinearLayout extends ViewGroup {
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
+ default:
+ childLeft = paddingLeft;
+ break;
}
@@ -1015,7 +1142,7 @@ public class LinearLayout extends ViewGroup {
void layoutHorizontal() {
final int paddingTop = mPaddingTop;
- int childTop = paddingTop;
+ int childTop;
int childLeft = mPaddingLeft;
// Where bottom of child should go
@@ -1062,7 +1189,7 @@ public class LinearLayout extends ViewGroup {
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
- if (baselineAligned && lp.height != LayoutParams.FILL_PARENT) {
+ if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
@@ -1080,7 +1207,7 @@ public class LinearLayout extends ViewGroup {
break;
case Gravity.CENTER_VERTICAL:
- // Removed support for baselign alignment when layout_gravity or
+ // Removed support for baseline alignment when layout_gravity or
// gravity == center_vertical. See bug #1038483.
// Keep the code around if we need to re-enable this feature
// if (childBaseline != -1) {
@@ -1102,6 +1229,9 @@ public class LinearLayout extends ViewGroup {
childTop -= (maxDescent[INDEX_BOTTOM] - descent);
}
break;
+ default:
+ childTop = paddingTop;
+ break;
}
childLeft += lp.leftMargin;
@@ -1193,7 +1323,7 @@ public class LinearLayout extends ViewGroup {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
* when the layout's orientation is {@link #VERTICAL}. When the orientation is
* {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
@@ -1204,7 +1334,7 @@ public class LinearLayout extends ViewGroup {
if (mOrientation == HORIZONTAL) {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
} else if (mOrientation == VERTICAL) {
- return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
return null;
}
@@ -1284,9 +1414,9 @@ public class LinearLayout extends ViewGroup {
* Creates a new set of layout parameters with the specified width, height
* and weight.
*
- * @param width the width, either {@link #FILL_PARENT},
+ * @param width the width, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
- * @param height the height, either {@link #FILL_PARENT},
+ * @param height the height, either {@link #MATCH_PARENT},
* {@link #WRAP_CONTENT} or a fixed size in pixels
* @param weight the weight
*/
diff --git a/core/java/android/widget/ListAdapter.java b/core/java/android/widget/ListAdapter.java
index a035145..0fd2e70 100644
--- a/core/java/android/widget/ListAdapter.java
+++ b/core/java/android/widget/ListAdapter.java
@@ -36,6 +36,9 @@ public interface ListAdapter extends Adapter {
/**
* Returns true if the item at the specified position is not a separator.
* (A separator is a non-selectable, non-clickable item).
+ *
+ * The result is unspecified if position is invalid. An {@link ArrayIndexOutOfBoundsException}
+ * should be thrown in that case for fast failure.
*
* @param position Index of the item
* @return True if the item is not a separator
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 7c8151e..892c44a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -16,31 +16,32 @@
package android.widget;
+import com.android.internal.R;
+import com.google.android.collect.Lists;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.PixelFormat;
import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
+import android.util.LongSparseArray;
import android.util.SparseBooleanArray;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
-import android.view.SoundEffectConstants;
import android.view.accessibility.AccessibilityEvent;
-import com.google.android.collect.Lists;
-import com.android.internal.R;
-
import java.util.ArrayList;
/*
@@ -116,7 +117,7 @@ public class ListView extends AbsListView {
Drawable mDivider;
int mDividerHeight;
-
+
private boolean mIsCacheColorOpaque;
private boolean mDividerIsOpaque;
private boolean mClipDivider;
@@ -131,6 +132,7 @@ public class ListView extends AbsListView {
private int mChoiceMode = CHOICE_MODE_NONE;
private SparseBooleanArray mCheckStates;
+ private LongSparseArray<Boolean> mCheckedIdStates;
// used for temporary calculations.
private final Rect mTempRect = new Rect();
@@ -138,8 +140,11 @@ public class ListView extends AbsListView {
// the single allocated result per list view; kinda cheesey but avoids
// allocating these thingies too often.
- private ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
+ private final ArrowScrollFocusResult mArrowScrollFocusResult = new ArrowScrollFocusResult();
+ // Keeps focused children visible through resizes
+ private FocusSelector mFocusSelector;
+
public ListView(Context context) {
this(context, null);
}
@@ -166,7 +171,7 @@ public class ListView extends AbsListView {
// If a divider is specified use its intrinsic height for divider height
setDivider(d);
}
-
+
// Use the height specified, zero being the default
final int dividerHeight = a.getDimensionPixelSize(
com.android.internal.R.styleable.ListView_dividerHeight, 0);
@@ -243,7 +248,7 @@ public class ListView extends AbsListView {
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so ListView can wrap
- * the supplied cursor with one that that will also account for header
+ * the supplied cursor with one that will also account for header and footer
* views.
*
* @param v The view to add.
@@ -270,7 +275,7 @@ public class ListView extends AbsListView {
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so ListView can wrap
- * the supplied cursor with one that that will also account for header
+ * the supplied cursor with one that will also account for header and footer
* views.
*
* @param v The view to add.
@@ -321,7 +326,7 @@ public class ListView extends AbsListView {
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so ListView can wrap
- * the supplied cursor with one that that will also account for header
+ * the supplied cursor with one that will also account for header and footer
* views.
*
* @param v The view to add.
@@ -347,7 +352,7 @@ public class ListView extends AbsListView {
* than once, the views will appear in the order they were added. Views added using
* this call can take focus if they want.
* <p>NOTE: Call this before calling setAdapter. This is so ListView can wrap the supplied
- * cursor with one that that will also account for header views.
+ * cursor with one that will also account for header and footer views.
*
*
* @param v The view to add.
@@ -450,6 +455,12 @@ public class ListView extends AbsListView {
checkSelectionChanged();
}
+ if (mChoiceMode != CHOICE_MODE_NONE &&
+ mAdapter.hasStableIds() &&
+ mCheckedIdStates == null) {
+ mCheckedIdStates = new LongSparseArray<Boolean>();
+ }
+
} else {
mAreAllItemsSelectable = true;
checkFocus();
@@ -460,6 +471,10 @@ public class ListView extends AbsListView {
if (mCheckStates != null) {
mCheckStates.clear();
}
+
+ if (mCheckedIdStates != null) {
+ mCheckedIdStates.clear();
+ }
requestLayout();
}
@@ -1017,6 +1032,39 @@ public class ListView extends AbsListView {
return sel;
}
+ private class FocusSelector implements Runnable {
+ private int mPosition;
+ private int mPositionTop;
+
+ public FocusSelector setup(int position, int top) {
+ mPosition = position;
+ mPositionTop = top;
+ return this;
+ }
+
+ public void run() {
+ setSelectionFromTop(mPosition, mPositionTop);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (getChildCount() > 0) {
+ View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ final int childPosition = mFirstPosition + indexOfChild(focusedChild);
+ final int childBottom = focusedChild.getBottom();
+ final int offset = Math.max(0, childBottom - (h - mPaddingTop));
+ final int top = focusedChild.getTop() - offset;
+ if (mFocusSelector == null) {
+ mFocusSelector = new FocusSelector();
+ }
+ post(mFocusSelector.setup(childPosition, top));
+ }
+ }
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Sets up mListPadding
@@ -1033,14 +1081,15 @@ public class ListView extends AbsListView {
mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
- final View child = obtainView(0);
+ final View child = obtainView(0, mIsScrap);
measureScrapChild(child, 0, widthMeasureSpec);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
- if (recycleOnMeasure()) {
+ if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
+ ((LayoutParams) child.getLayoutParams()).viewType)) {
mRecycler.addScrapView(child);
}
}
@@ -1067,11 +1116,12 @@ public class ListView extends AbsListView {
private void measureScrapChild(View child, int position, int widthMeasureSpec) {
LayoutParams p = (LayoutParams) child.getLayoutParams();
if (p == null) {
- p = new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(position);
+ p.forceAdd = true;
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
mListPadding.left + mListPadding.right, p.width);
@@ -1142,9 +1192,10 @@ public class ListView extends AbsListView {
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
+ final boolean[] isScrap = mIsScrap;
for (i = startPosition; i <= endPosition; ++i) {
- child = obtainView(i);
+ child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec);
@@ -1154,7 +1205,8 @@ public class ListView extends AbsListView {
}
// Recycle the view before we possibly return from the method
- if (recyle) {
+ if (recyle && recycleBin.shouldRecycleViewType(
+ ((LayoutParams) child.getLayoutParams()).viewType)) {
recycleBin.addScrapView(child);
}
@@ -1185,13 +1237,21 @@ public class ListView extends AbsListView {
int findMotionRow(int y) {
int childCount = getChildCount();
if (childCount > 0) {
- for (int i = 0; i < childCount; i++) {
- View v = getChildAt(i);
- if (y <= v.getBottom()) {
- return mFirstPosition + i;
+ if (!mStackFromBottom) {
+ for (int i = 0; i < childCount; i++) {
+ View v = getChildAt(i);
+ if (y <= v.getBottom()) {
+ return mFirstPosition + i;
+ }
+ }
+ } else {
+ for (int i = childCount - 1; i >= 0; i--) {
+ View v = getChildAt(i);
+ if (y >= v.getTop()) {
+ return mFirstPosition + i;
+ }
}
}
- return mFirstPosition + childCount - 1;
}
return INVALID_POSITION;
}
@@ -1374,7 +1434,7 @@ public class ListView extends AbsListView {
int childrenBottom = mBottom - mTop - mListPadding.bottom;
int childCount = getChildCount();
- int index;
+ int index = 0;
int delta = 0;
View sel;
@@ -1484,7 +1544,6 @@ public class ListView extends AbsListView {
}
// Clear out old views
- //removeAllViewsInLayout();
detachAllViewsFromParent();
switch (mLayoutMode) {
@@ -1665,10 +1724,10 @@ public class ListView extends AbsListView {
}
// Make a new view for this position, or convert an unused view if possible
- child = obtainView(position);
+ child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
- setupChild(child, position, y, flow, childrenLeft, selected, false);
+ setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
@@ -1701,15 +1760,16 @@ public class ListView extends AbsListView {
// noinspection unchecked
AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
- p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, 0);
}
p.viewType = mAdapter.getItemViewType(position);
- if (recycled || (p.recycledHeaderFooter &&
+ if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
} else {
+ p.forceAdd = false;
if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
p.recycledHeaderFooter = true;
}
@@ -2387,7 +2447,7 @@ public class ListView extends AbsListView {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
@@ -2721,7 +2781,8 @@ public class ListView extends AbsListView {
/**
* Determine the distance to the nearest edge of a view in a particular
- * direciton.
+ * direction.
+ *
* @param descendant A descendant of this list.
* @return The distance, or 0 if the nearest edge is already on screen.
*/
@@ -2780,10 +2841,10 @@ public class ListView extends AbsListView {
while (first.getBottom() < listTop) {
AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
- removeViewInLayout(first);
+ detachViewFromParent(first);
recycleBin.addScrapView(first);
} else {
- detachViewFromParent(first);
+ removeViewInLayout(first);
}
first = getChildAt(0);
mFirstPosition++;
@@ -2811,10 +2872,10 @@ public class ListView extends AbsListView {
while (last.getTop() > listBottom) {
AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
- removeViewInLayout(last);
+ detachViewFromParent(last);
recycleBin.addScrapView(last);
} else {
- detachViewFromParent(last);
+ removeViewInLayout(last);
}
last = getChildAt(--lastIndex);
}
@@ -2823,17 +2884,19 @@ public class ListView extends AbsListView {
private View addViewAbove(View theView, int position) {
int abovePosition = position - 1;
- View view = obtainView(abovePosition);
+ View view = obtainView(abovePosition, mIsScrap);
int edgeOfNewChild = theView.getTop() - mDividerHeight;
- setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, false, false);
+ setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
+ false, mIsScrap[0]);
return view;
}
private View addViewBelow(View theView, int position) {
int belowPosition = position + 1;
- View view = obtainView(belowPosition);
+ View view = obtainView(belowPosition, mIsScrap);
int edgeOfNewChild = theView.getBottom() + mDividerHeight;
- setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, false, false);
+ setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
+ false, mIsScrap[0]);
return view;
}
@@ -2879,13 +2942,14 @@ public class ListView extends AbsListView {
}
super.setCacheColorHint(color);
}
-
+
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the dividers
final int dividerHeight = mDividerHeight;
+ final boolean drawDividers = dividerHeight > 0 && mDivider != null;
- if (dividerHeight > 0 && mDivider != null) {
+ if (drawDividers) {
// Only modify the top and bottom in the loop, we set the left and right here
final Rect bounds = mTempRect;
bounds.left = mPaddingLeft;
@@ -2893,7 +2957,8 @@ public class ListView extends AbsListView {
final int count = getChildCount();
final int headerCount = mHeaderViewInfos.size();
- final int footerLimit = mItemCount - mFooterViewInfos.size() - 1;
+ final int itemCount = mItemCount;
+ final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
final boolean headerDividers = mHeaderDividersEnabled;
final boolean footerDividers = mFooterDividersEnabled;
final int first = mFirstPosition;
@@ -2903,7 +2968,7 @@ public class ListView extends AbsListView {
// fill a rect where the dividers would be for non-selectable items
// If the list is opaque and the background is also opaque, we don't
// need to draw anything since the background will do it for us
- final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
+ final boolean fillForMissingDividers = drawDividers && isOpaque() && !super.isOpaque();
if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
mDividerPaint = new Paint();
@@ -2911,17 +2976,18 @@ public class ListView extends AbsListView {
}
final Paint paint = mDividerPaint;
+ final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
if (!mStackFromBottom) {
- int bottom;
- int listBottom = mBottom - mTop - mListPadding.bottom;
-
+ int bottom = 0;
+
+ final int scrollY = mScrollY;
for (int i = 0; i < count; i++) {
if ((headerDividers || first + i >= headerCount) &&
(footerDividers || first + i < footerLimit)) {
View child = getChildAt(i);
bottom = child.getBottom();
// Don't draw dividers next to items that are not enabled
- if (bottom < listBottom) {
+ if (drawDividers) {
if ((areAllItemsSelectable ||
(adapter.isEnabled(first + i) && (i == count - 1 ||
adapter.isEnabled(first + i + 1))))) {
@@ -2940,13 +3006,15 @@ public class ListView extends AbsListView {
int top;
int listTop = mListPadding.top;
+ final int scrollY = mScrollY;
+
for (int i = 0; i < count; i++) {
if ((headerDividers || first + i >= headerCount) &&
(footerDividers || first + i < footerLimit)) {
View child = getChildAt(i);
top = child.getTop();
// Don't draw dividers next to items that are not enabled
- if (top > listTop) {
+ if (drawDividers && top > listTop) {
if ((areAllItemsSelectable ||
(adapter.isEnabled(first + i) && (i == count - 1 ||
adapter.isEnabled(first + i + 1))))) {
@@ -2965,6 +3033,12 @@ public class ListView extends AbsListView {
}
}
}
+
+ if (count > 0 && scrollY > 0 && drawDividers) {
+ bounds.top = listBottom;
+ bounds.bottom = listBottom + dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
}
}
@@ -3071,7 +3145,7 @@ public class ListView extends AbsListView {
mFooterDividersEnabled = footerDividersEnabled;
invalidate();
}
-
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
@@ -3080,13 +3154,20 @@ public class ListView extends AbsListView {
if (gainFocus && previouslyFocusedRect != null) {
previouslyFocusedRect.offset(mScrollX, mScrollY);
+ final ListAdapter adapter = mAdapter;
+ // Don't cache the result of getChildCount or mFirstPosition here,
+ // it could change in layoutChildren.
+ if (adapter.getCount() < getChildCount() + mFirstPosition) {
+ mLayoutMode = LAYOUT_NORMAL;
+ layoutChildren();
+ }
+
// figure out which item should be selected based on previously
// focused rect
Rect otherRect = mTempRect;
int minDistance = Integer.MAX_VALUE;
final int childCount = getChildCount();
final int firstPosition = mFirstPosition;
- final ListAdapter adapter = mAdapter;
for (int i = 0; i < childCount; i++) {
// only consider selectable views
@@ -3254,8 +3335,13 @@ public class ListView extends AbsListView {
*/
public void setChoiceMode(int choiceMode) {
mChoiceMode = choiceMode;
- if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates == null) {
- mCheckStates = new SparseBooleanArray();
+ if (mChoiceMode != CHOICE_MODE_NONE) {
+ if (mCheckStates == null) {
+ mCheckStates = new SparseBooleanArray();
+ }
+ if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
+ mCheckedIdStates = new LongSparseArray<Boolean>();
+ }
}
}
@@ -3267,14 +3353,25 @@ public class ListView extends AbsListView {
handled = true;
if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
- boolean oldValue = mCheckStates.get(position, false);
- mCheckStates.put(position, !oldValue);
+ boolean newValue = !mCheckStates.get(position, false);
+ mCheckStates.put(position, newValue);
+ if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
+ if (newValue) {
+ mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ } else {
+ mCheckedIdStates.delete(mAdapter.getItemId(position));
+ }
+ }
} else {
- boolean oldValue = mCheckStates.get(position, false);
- if (!oldValue) {
+ boolean newValue = !mCheckStates.get(position, false);
+ if (newValue) {
mCheckStates.clear();
mCheckStates.put(position, true);
- }
+ if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
+ mCheckedIdStates.clear();
+ mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ }
+ }
}
mDataChanged = true;
@@ -3291,9 +3388,9 @@ public class ListView extends AbsListView {
* Sets the checked state of the specified position. The is only valid if
* the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or
* {@link #CHOICE_MODE_MULTIPLE}.
- *
+ *
* @param position The item whose checked state is to be checked
- * @param value The new checked sate for the item
+ * @param value The new checked state for the item
*/
public void setItemChecked(int position, boolean value) {
if (mChoiceMode == CHOICE_MODE_NONE) {
@@ -3302,16 +3399,30 @@ public class ListView extends AbsListView {
if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
mCheckStates.put(position, value);
+ if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
+ if (value) {
+ mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ } else {
+ mCheckedIdStates.delete(mAdapter.getItemId(position));
+ }
+ }
} else {
+ boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
// Clear all values if we're checking something, or unchecking the currently
// selected item
if (value || isItemChecked(position)) {
mCheckStates.clear();
+ if (updateIds) {
+ mCheckedIdStates.clear();
+ }
}
// this may end up selecting the value we just cleared but this way
// we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
if (value) {
mCheckStates.put(position, true);
+ if (updateIds) {
+ mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ }
}
}
@@ -3376,27 +3487,72 @@ public class ListView extends AbsListView {
}
/**
- * Returns the set of checked items ids. The result is only valid if
- * the choice mode has not been set to {@link #CHOICE_MODE_SINGLE}.
- *
- * @return A new array which contains the id of each checked item in the list.
+ * Returns the set of checked items ids. The result is only valid if the
+ * choice mode has not been set to {@link #CHOICE_MODE_NONE}.
+ *
+ * @return A new array which contains the id of each checked item in the
+ * list.
+ *
+ * @deprecated Use {@link #getCheckedItemIds()} instead.
*/
public long[] getCheckItemIds() {
+ // Use new behavior that correctly handles stable ID mapping.
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ return getCheckedItemIds();
+ }
+
+ // Old behavior was buggy, but would sort of work for adapters without stable IDs.
+ // Fall back to it to support legacy apps.
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null && mAdapter != null) {
final SparseBooleanArray states = mCheckStates;
final int count = states.size();
final long[] ids = new long[count];
final ListAdapter adapter = mAdapter;
+ int checkedCount = 0;
for (int i = 0; i < count; i++) {
- ids[i]= adapter.getItemId(states.keyAt(i));
+ if (states.valueAt(i)) {
+ ids[checkedCount++] = adapter.getItemId(states.keyAt(i));
+ }
}
- return ids;
- }
+ // Trim array if needed. mCheckStates may contain false values
+ // resulting in checkedCount being smaller than count.
+ if (checkedCount == count) {
+ return ids;
+ } else {
+ final long[] result = new long[checkedCount];
+ System.arraycopy(ids, 0, result, 0, checkedCount);
+ return result;
+ }
+ }
return new long[0];
}
+
+ /**
+ * Returns the set of checked items ids. The result is only valid if the
+ * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
+ * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
+ *
+ * @return A new array which contains the id of each checked item in the
+ * list.
+ */
+ public long[] getCheckedItemIds() {
+ if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
+ return new long[0];
+ }
+
+ final LongSparseArray<Boolean> idStates = mCheckedIdStates;
+ final int count = idStates.size();
+ final long[] ids = new long[count];
+
+ for (int i = 0; i < count; i++) {
+ ids[i] = idStates.keyAt(i);
+ }
+
+ return ids;
+ }
/**
* Clear any choices previously set
@@ -3405,17 +3561,23 @@ public class ListView extends AbsListView {
if (mCheckStates != null) {
mCheckStates.clear();
}
+ if (mCheckedIdStates != null) {
+ mCheckedIdStates.clear();
+ }
}
static class SavedState extends BaseSavedState {
SparseBooleanArray checkState;
+ LongSparseArray<Boolean> checkIdState;
/**
* Constructor called from {@link ListView#onSaveInstanceState()}
*/
- SavedState(Parcelable superState, SparseBooleanArray checkState) {
+ SavedState(Parcelable superState, SparseBooleanArray checkState,
+ LongSparseArray<Boolean> checkIdState) {
super(superState);
this.checkState = checkState;
+ this.checkIdState = checkIdState;
}
/**
@@ -3424,12 +3586,19 @@ public class ListView extends AbsListView {
private SavedState(Parcel in) {
super(in);
checkState = in.readSparseBooleanArray();
+ long[] idState = in.createLongArray();
+
+ if (idState.length > 0) {
+ checkIdState = new LongSparseArray<Boolean>();
+ checkIdState.setValues(idState, Boolean.TRUE);
+ }
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseBooleanArray(checkState);
+ out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]);
}
@Override
@@ -3454,7 +3623,7 @@ public class ListView extends AbsListView {
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
- return new SavedState(superState, mCheckStates);
+ return new SavedState(superState, mCheckStates, mCheckedIdStates);
}
@Override
@@ -3467,5 +3636,8 @@ public class ListView extends AbsListView {
mCheckStates = ss.checkState;
}
+ if (ss.checkIdState != null) {
+ mCheckedIdStates = ss.checkIdState;
+ }
}
}
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index 446a992..c246c247 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -167,8 +167,8 @@ public class MediaController extends FrameLayout {
mAnchor = view;
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
);
removeAllViews();
diff --git a/core/java/com/android/internal/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index ae08eca..582d9e4 100644
--- a/core/java/com/android/internal/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.widget;
+import android.annotation.Widget;
import android.content.Context;
import android.os.Handler;
import android.text.InputFilter;
@@ -34,13 +35,30 @@ import android.widget.EditText;
import com.android.internal.R;
-public class NumberPicker extends LinearLayout implements OnClickListener,
- OnFocusChangeListener, OnLongClickListener {
+/**
+ * A view for selecting a number
+ *
+ * For a dialog using this view, see {@link android.app.TimePickerDialog}.
+ * @hide
+ */
+@Widget
+public class NumberPicker extends LinearLayout {
+ /**
+ * The callback interface used to indicate the number value has been adjusted.
+ */
public interface OnChangedListener {
+ /**
+ * @param picker The NumberPicker associated with this listener.
+ * @param oldVal The previous value.
+ * @param newVal The new value.
+ */
void onChanged(NumberPicker picker, int oldVal, int newVal);
}
+ /**
+ * Interface used to format the number into a string for presentation
+ */
public interface Formatter {
String toString(int value);
}
@@ -81,10 +99,26 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private final InputFilter mNumberInputFilter;
private String[] mDisplayedValues;
- protected int mStart;
- protected int mEnd;
- protected int mCurrent;
- protected int mPrevious;
+
+ /**
+ * Lower value of the range of numbers allowed for the NumberPicker
+ */
+ private int mStart;
+
+ /**
+ * Upper value of the range of numbers allowed for the NumberPicker
+ */
+ private int mEnd;
+
+ /**
+ * Current value of this NumberPicker
+ */
+ private int mCurrent;
+
+ /**
+ * Previous value of this NumberPicker.
+ */
+ private int mPrevious;
private OnChangedListener mListener;
private Formatter mFormatter;
private long mSpeed = 300;
@@ -92,34 +126,89 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private boolean mIncrement;
private boolean mDecrement;
+ /**
+ * Create a new number picker
+ * @param context the application environment
+ */
public NumberPicker(Context context) {
this(context, null);
}
+ /**
+ * Create a new number picker
+ * @param context the application environment
+ * @param attrs a collection of attributes
+ */
public NumberPicker(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- @SuppressWarnings({"UnusedDeclaration"})
- public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setOrientation(VERTICAL);
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LayoutInflater inflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
mHandler = new Handler();
+
+ OnClickListener clickListener = new OnClickListener() {
+ public void onClick(View v) {
+ validateInput(mText);
+ if (!mText.hasFocus()) mText.requestFocus();
+
+ // now perform the increment/decrement
+ if (R.id.increment == v.getId()) {
+ changeCurrent(mCurrent + 1);
+ } else if (R.id.decrement == v.getId()) {
+ changeCurrent(mCurrent - 1);
+ }
+ }
+ };
+
+ OnFocusChangeListener focusListener = new OnFocusChangeListener() {
+ public void onFocusChange(View v, boolean hasFocus) {
+
+ /* When focus is lost check that the text field
+ * has valid values.
+ */
+ if (!hasFocus) {
+ validateInput(v);
+ }
+ }
+ };
+
+ OnLongClickListener longClickListener = new OnLongClickListener() {
+ /**
+ * We start the long click here but rely on the {@link NumberPickerButton}
+ * to inform us when the long click has ended.
+ */
+ public boolean onLongClick(View v) {
+ /* The text view may still have focus so clear it's focus which will
+ * trigger the on focus changed and any typed values to be pulled.
+ */
+ mText.clearFocus();
+
+ if (R.id.increment == v.getId()) {
+ mIncrement = true;
+ mHandler.post(mRunnable);
+ } else if (R.id.decrement == v.getId()) {
+ mDecrement = true;
+ mHandler.post(mRunnable);
+ }
+ return true;
+ }
+ };
+
InputFilter inputFilter = new NumberPickerInputFilter();
mNumberInputFilter = new NumberRangeKeyListener();
mIncrementButton = (NumberPickerButton) findViewById(R.id.increment);
- mIncrementButton.setOnClickListener(this);
- mIncrementButton.setOnLongClickListener(this);
+ mIncrementButton.setOnClickListener(clickListener);
+ mIncrementButton.setOnLongClickListener(longClickListener);
mIncrementButton.setNumberPicker(this);
+
mDecrementButton = (NumberPickerButton) findViewById(R.id.decrement);
- mDecrementButton.setOnClickListener(this);
- mDecrementButton.setOnLongClickListener(this);
+ mDecrementButton.setOnClickListener(clickListener);
+ mDecrementButton.setOnLongClickListener(longClickListener);
mDecrementButton.setNumberPicker(this);
mText = (EditText) findViewById(R.id.timepicker_input);
- mText.setOnFocusChangeListener(this);
+ mText.setOnFocusChangeListener(focusListener);
mText.setFilters(new InputFilter[] {inputFilter});
mText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
@@ -128,6 +217,12 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
}
}
+ /**
+ * Set the enabled state of this view. The interpretation of the enabled
+ * state varies by subclass.
+ *
+ * @param enabled True if this view is enabled, false otherwise.
+ */
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
@@ -136,10 +231,19 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
mText.setEnabled(enabled);
}
+ /**
+ * Set the callback that indicates the number has been adjusted by the user.
+ * @param listener the callback, should not be null.
+ */
public void setOnChangeListener(OnChangedListener listener) {
mListener = listener;
}
+ /**
+ * Set the formatter that will be used to format the number for presentation
+ * @param formatter the formatter object. If formatter is null, String.valueOf()
+ * will be used
+ */
public void setFormatter(Formatter formatter) {
mFormatter = formatter;
}
@@ -152,10 +256,7 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
* @param end the end of the range (inclusive)
*/
public void setRange(int start, int end) {
- mStart = start;
- mEnd = end;
- mCurrent = start;
- updateView();
+ setRange(start, end, null/*displayedValues*/);
}
/**
@@ -175,39 +276,49 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
updateView();
}
+ /**
+ * Set the current value for the number picker.
+ *
+ * @param current the current value the start of the range (inclusive)
+ * @throws IllegalArgumentException when current is not within the range
+ * of of the number picker
+ */
public void setCurrent(int current) {
+ if (current < mStart || current > mEnd) {
+ throw new IllegalArgumentException(
+ "current should be >= start and <= end");
+ }
mCurrent = current;
updateView();
}
/**
- * The speed (in milliseconds) at which the numbers will scroll
- * when the the +/- buttons are longpressed. Default is 300ms.
+ * Sets the speed at which the numbers will scroll when the +/-
+ * buttons are longpressed
+ *
+ * @param speed The speed (in milliseconds) at which the numbers will scroll
+ * default 300ms
*/
public void setSpeed(long speed) {
mSpeed = speed;
}
- public void onClick(View v) {
- validateInput(mText);
- if (!mText.hasFocus()) mText.requestFocus();
-
- // now perform the increment/decrement
- if (R.id.increment == v.getId()) {
- changeCurrent(mCurrent + 1);
- } else if (R.id.decrement == v.getId()) {
- changeCurrent(mCurrent - 1);
- }
- }
-
private String formatNumber(int value) {
return (mFormatter != null)
? mFormatter.toString(value)
: String.valueOf(value);
}
+ /**
+ * Sets the current value of this NumberPicker, and sets mPrevious to the previous
+ * value. If current is greater than mEnd less than mStart, the value of mCurrent
+ * is wrapped around.
+ *
+ * Subclasses can override this to change the wrapping behavior
+ *
+ * @param current the new value of the NumberPicker
+ */
protected void changeCurrent(int current) {
-
// Wrap around the values if we go past the start or end
if (current > mEnd) {
current = mStart;
@@ -220,14 +331,23 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
updateView();
}
- protected void notifyChange() {
+ /**
+ * Notifies the listener, if registered, of a change of the value of this
+ * NumberPicker.
+ */
+ private void notifyChange() {
if (mListener != null) {
mListener.onChanged(this, mPrevious, mCurrent);
}
}
- protected void updateView() {
-
+ /**
+ * Updates the view of this NumberPicker. If displayValues were specified
+ * in {@link #setRange}, the string corresponding to the index specified by
+ * the current value will be returned. Otherwise, the formatter specified
+ * in {@link setFormatter} will be used to format the number.
+ */
+ private void updateView() {
/* If we don't have displayed values then use the
* current number else find the correct value in the
* displayed values for the current number.
@@ -252,16 +372,6 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
updateView();
}
- public void onFocusChange(View v, boolean hasFocus) {
-
- /* When focus is lost check that the text field
- * has valid values.
- */
- if (!hasFocus) {
- validateInput(v);
- }
- }
-
private void validateInput(View v) {
String str = String.valueOf(((TextView) v).getText());
if ("".equals(str)) {
@@ -276,30 +386,15 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
}
/**
- * We start the long click here but rely on the {@link NumberPickerButton}
- * to inform us when the long click has ended.
+ * @hide
*/
- public boolean onLongClick(View v) {
-
- /* The text view may still have focus so clear it's focus which will
- * trigger the on focus changed and any typed values to be pulled.
- */
- mText.clearFocus();
-
- if (R.id.increment == v.getId()) {
- mIncrement = true;
- mHandler.post(mRunnable);
- } else if (R.id.decrement == v.getId()) {
- mDecrement = true;
- mHandler.post(mRunnable);
- }
- return true;
- }
-
public void cancelIncrement() {
mIncrement = false;
}
+ /**
+ * @hide
+ */
public void cancelDecrement() {
mDecrement = false;
}
@@ -378,10 +473,13 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
private int getSelectedPos(String str) {
if (mDisplayedValues == null) {
- return Integer.parseInt(str);
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException e) {
+ /* Ignore as if it's not a number we don't care */
+ }
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
-
/* Don't force the user to type in jan when ja will do */
str = str.toLowerCase();
if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
@@ -403,9 +501,26 @@ public class NumberPicker extends LinearLayout implements OnClickListener,
}
/**
+ * Returns the current value of the NumberPicker
* @return the current value.
*/
public int getCurrent() {
return mCurrent;
}
-} \ No newline at end of file
+
+ /**
+ * Returns the upper value of the range of the NumberPicker
+ * @return the uppper number of the range.
+ */
+ protected int getEndRange() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the lower value of the range of the NumberPicker
+ * @return the lower number of the range.
+ */
+ protected int getBeginRange() {
+ return mStart;
+ }
+}
diff --git a/core/java/com/android/internal/widget/NumberPickerButton.java b/core/java/android/widget/NumberPickerButton.java
index 39f1e2c..1c8579c 100644
--- a/core/java/com/android/internal/widget/NumberPickerButton.java
+++ b/core/java/android/widget/NumberPickerButton.java
@@ -14,23 +14,25 @@
* limitations under the License.
*/
-package com.android.internal.widget;
+package android.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.ImageButton;
+import android.widget.NumberPicker;
import com.android.internal.R;
/**
- * This class exists purely to cancel long click events.
+ * This class exists purely to cancel long click events, that got
+ * started in NumberPicker
*/
-public class NumberPickerButton extends ImageButton {
+class NumberPickerButton extends ImageButton {
private NumberPicker mNumberPicker;
-
+
public NumberPickerButton(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
@@ -43,23 +45,23 @@ public class NumberPickerButton extends ImageButton {
public NumberPickerButton(Context context) {
super(context);
}
-
+
public void setNumberPicker(NumberPicker picker) {
mNumberPicker = picker;
}
-
+
@Override
public boolean onTouchEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTouchEvent(event);
}
-
+
@Override
public boolean onTrackballEvent(MotionEvent event) {
cancelLongpressIfRequired(event);
return super.onTrackballEvent(event);
}
-
+
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
@@ -68,7 +70,7 @@ public class NumberPickerButton extends ImageButton {
}
return super.onKeyUp(keyCode, event);
}
-
+
private void cancelLongpressIfRequired(MotionEvent event) {
if ((event.getAction() == MotionEvent.ACTION_CANCEL)
|| (event.getAction() == MotionEvent.ACTION_UP)) {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index e4cc609..0378328 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -126,7 +126,7 @@ public class PopupWindow {
WindowManager.LayoutParams p = (WindowManager.LayoutParams)
mPopupView.getLayoutParams();
- mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff);
+ updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
update(p.x, p.y, -1, -1, true);
}
}
@@ -569,7 +569,7 @@ public class PopupWindow {
* the current width or height is requested as an explicit size from
* the window manager. You can supply
* {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
- * {@link ViewGroup.LayoutParams#FILL_PARENT} to have that measure
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
* spec supplied instead, replacing the absolute width and height that
* has been set in the popup.</p>
*
@@ -578,11 +578,11 @@ public class PopupWindow {
*
* @param widthSpec an explicit width measure spec mode, either
* {@link ViewGroup.LayoutParams#WRAP_CONTENT},
- * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
* width.
* @param heightSpec an explicit height measure spec mode, either
* {@link ViewGroup.LayoutParams#WRAP_CONTENT},
- * {@link ViewGroup.LayoutParams#FILL_PARENT}, or 0 to use the absolute
+ * {@link ViewGroup.LayoutParams#MATCH_PARENT}, or 0 to use the absolute
* height.
*/
public void setWindowLayoutMode(int widthSpec, int heightSpec) {
@@ -729,22 +729,8 @@ public class PopupWindow {
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
preparePopup(p);
- mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
- if (mBackground != null) {
- // If the background drawable provided was a StateListDrawable with above-anchor
- // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
- // do the job.
- if (mAboveAnchorBackgroundDrawable != null) {
- if (mAboveAnchor) {
- mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
- } else {
- mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
- }
- } else {
- mPopupView.refreshDrawableState();
- }
- }
+ updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
@@ -754,6 +740,27 @@ public class PopupWindow {
invokePopup(p);
}
+ private void updateAboveAnchor(boolean aboveAnchor) {
+ if (aboveAnchor != mAboveAnchor) {
+ mAboveAnchor = aboveAnchor;
+
+ if (mBackground != null) {
+ // If the background drawable provided was a StateListDrawable with above-anchor
+ // and below-anchor states, use those. Otherwise rely on refreshDrawableState to
+ // do the job.
+ if (mAboveAnchorBackgroundDrawable != null) {
+ if (mAboveAnchor) {
+ mPopupView.setBackgroundDrawable(mAboveAnchorBackgroundDrawable);
+ } else {
+ mPopupView.setBackgroundDrawable(mBelowAnchorBackgroundDrawable);
+ }
+ } else {
+ mPopupView.refreshDrawableState();
+ }
+ }
+ }
+ }
+
/**
* Indicates whether the popup is showing above (the y coordinate of the popup's bottom
* is less than the y coordinate of the anchor) or below the anchor view (the y coordinate
@@ -785,7 +792,7 @@ public class PopupWindow {
if (mBackground != null) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
- int height = ViewGroup.LayoutParams.FILL_PARENT;
+ int height = ViewGroup.LayoutParams.MATCH_PARENT;
if (layoutParams != null &&
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -795,7 +802,7 @@ public class PopupWindow {
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT, height
+ ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackgroundDrawable(mBackground);
popupViewContainer.addView(mContentView, listParams);
@@ -915,7 +922,7 @@ public class PopupWindow {
anchor.getLocationInWindow(mDrawingLocation);
p.x = mDrawingLocation[0] + xoff;
- p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+ p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
boolean onTop = false;
@@ -932,26 +939,26 @@ public class PopupWindow {
// the edit box
int scrollX = anchor.getScrollX();
int scrollY = anchor.getScrollY();
- Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth,
- scrollY + mPopupHeight + anchor.getMeasuredHeight());
+ Rect r = new Rect(scrollX, scrollY, scrollX + mPopupWidth + xoff,
+ scrollY + mPopupHeight + anchor.getHeight() + yoff);
anchor.requestRectangleOnScreen(r, true);
-
+
// now we re-evaluate the space available, and decide from that
// whether the pop-up will go above or below the anchor.
anchor.getLocationInWindow(mDrawingLocation);
p.x = mDrawingLocation[0] + xoff;
- p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+ p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
// determine whether there is more space above or below the anchor
anchor.getLocationOnScreen(mScreenLocation);
- onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getMeasuredHeight() - yoff) <
+ onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) <
(mScreenLocation[1] - yoff - displayFrame.top);
if (onTop) {
p.gravity = Gravity.LEFT | Gravity.BOTTOM;
p.y = root.getHeight() - mDrawingLocation[1] + yoff;
} else {
- p.y = mDrawingLocation[1] + anchor.getMeasuredHeight() + yoff;
+ p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
}
}
@@ -1041,16 +1048,18 @@ public class PopupWindow {
if (isShowing() && mPopupView != null) {
unregisterForScrollChanged();
- mWindowManager.removeView(mPopupView);
-
- if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
- ((ViewGroup) mPopupView).removeView(mContentView);
- }
- mPopupView = null;
- mIsShowing = false;
-
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
+ try {
+ mWindowManager.removeView(mPopupView);
+ } finally {
+ if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
+ ((ViewGroup) mPopupView).removeView(mContentView);
+ }
+ mPopupView = null;
+ mIsShowing = false;
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
}
}
}
@@ -1257,13 +1266,16 @@ public class PopupWindow {
}
}
+ int x = p.x;
+ int y = p.y;
+
if (updateLocation) {
- mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff);
+ updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff));
} else {
- mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff);
+ updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff));
}
-
- update(p.x, p.y, width, height);
+
+ update(p.x, p.y, width, height, x != p.x || y != p.y);
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 6dc9f78..202e658 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -696,8 +696,7 @@ public class ProgressBar extends View {
* <p>Start the indeterminate progress animation.</p>
*/
void startAnimation() {
- int visibility = getVisibility();
- if (visibility != VISIBLE) {
+ if (getVisibility() != VISIBLE) {
return;
}
@@ -771,7 +770,7 @@ public class ProgressBar extends View {
// let's be nice with the UI thread
if (v == GONE || v == INVISIBLE) {
stopAnimation();
- } else if (v == VISIBLE) {
+ } else {
startAnimation();
}
}
@@ -779,6 +778,20 @@ public class ProgressBar extends View {
}
@Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (mIndeterminate) {
+ // let's be nice with the UI thread
+ if (visibility == GONE || visibility == INVISIBLE) {
+ stopAnimation();
+ } else {
+ startAnimation();
+ }
+ }
+ }
+
+ @Override
public void invalidateDrawable(Drawable dr) {
if (!mInDrawing) {
if (verifyDrawable(dr)) {
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 8019f14..07c3e4b 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -25,9 +25,9 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.PhoneLookup;
+import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.util.AttributeSet;
@@ -55,21 +55,28 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
static final private int TOKEN_PHONE_LOOKUP = 1;
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private int TOKEN_CONTACT_LOOKUP_AND_TRIGGER = 4;
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
};
- static int EMAIL_ID_COLUMN_INDEX = 0;
- static int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int EMAIL_ID_COLUMN_INDEX = 0;
+ static final int EMAIL_LOOKUP_STRING_COLUMN_INDEX = 1;
static final String[] PHONE_LOOKUP_PROJECTION = new String[] {
PhoneLookup._ID,
PhoneLookup.LOOKUP_KEY,
};
- static int PHONE_ID_COLUMN_INDEX = 0;
- static int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final int PHONE_ID_COLUMN_INDEX = 0;
+ static final int PHONE_LOOKUP_STRING_COLUMN_INDEX = 1;
+ static final String[] CONTACT_LOOKUP_PROJECTION = new String[] {
+ Contacts._ID,
+ Contacts.LOOKUP_KEY,
+ };
+ static final int CONTACT_ID_COLUMN_INDEX = 0;
+ static final int CONTACT_LOOKUPKEY_COLUMN_INDEX = 1;
public QuickContactBadge(Context context) {
@@ -181,9 +188,9 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
public void onClick(View v) {
if (mContactUri != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri lookupUri = Contacts.getLookupUri(resolver, mContactUri);
- trigger(lookupUri);
+ mQueryHandler.startQuery(TOKEN_CONTACT_LOOKUP_AND_TRIGGER, null,
+ mContactUri,
+ CONTACT_LOOKUP_PROJECTION, null, null, null);
} else if (mContactEmail != null) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -249,6 +256,17 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
lookupUri = Contacts.getLookupUri(contactId, lookupKey);
}
}
+
+ case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
+ if (cursor != null && cursor.moveToFirst()) {
+ long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = cursor.getString(CONTACT_LOOKUPKEY_COLUMN_INDEX);
+ lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+ trigger = true;
+ }
+
+ break;
+ }
}
} finally {
if (cursor != null) {
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index e19a93d..1aa1df3 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -45,8 +45,7 @@ import java.util.ArrayList;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
- * parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if
- * view Y is dependent on the position of view X, make sure the view X comes first in the layout.
+ * parent.
*
* <p>
* Note that you cannot have a circular dependency between the size of the RelativeLayout and the
@@ -292,6 +291,8 @@ public class RelativeLayout extends ViewGroup {
}
}
+ // TODO: we need to find another way to implement RelativeLayout
+ // This implementation cannot handle every case
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mDirtyHierarchy) {
@@ -439,6 +440,10 @@ public class RelativeLayout extends ViewGroup {
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
+ } else if (rules[ALIGN_PARENT_RIGHT] != 0) {
+ final int childWidth = child.getMeasuredWidth();
+ params.mLeft = width - mPaddingRight - childWidth;
+ params.mRight = params.mLeft + childWidth;
}
}
}
@@ -465,6 +470,10 @@ public class RelativeLayout extends ViewGroup {
final int[] rules = params.getRules();
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
+ } else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
+ final int childHeight = child.getMeasuredHeight();
+ params.mTop = height - mPaddingBottom - childHeight;
+ params.mBottom = params.mTop + childHeight;
}
}
}
@@ -561,7 +570,7 @@ public class RelativeLayout extends ViewGroup {
mPaddingLeft, mPaddingRight,
myWidth);
int childHeightMeasureSpec;
- if (params.width == LayoutParams.FILL_PARENT) {
+ if (params.width == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
@@ -623,7 +632,7 @@ public class RelativeLayout extends ViewGroup {
// We can grow in this dimension.
childSpecSize = childSize;
}
- } else if (childSize == LayoutParams.FILL_PARENT) {
+ } else if (childSize == LayoutParams.MATCH_PARENT) {
// Child wanted to be as big as possible. Give all availble
// space
childSpecMode = MeasureSpec.EXACTLY;
@@ -674,7 +683,7 @@ public class RelativeLayout extends ViewGroup {
params.mRight = params.mLeft + child.getMeasuredWidth();
}
}
- return false;
+ return rules[ALIGN_PARENT_RIGHT] != 0;
}
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
@@ -703,7 +712,7 @@ public class RelativeLayout extends ViewGroup {
params.mBottom = params.mTop + child.getMeasuredHeight();
}
}
- return false;
+ return rules[ALIGN_PARENT_BOTTOM] != 0;
}
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b847e57..3003580 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -26,6 +26,7 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -37,7 +38,6 @@ import android.view.ViewGroup;
import android.view.LayoutInflater.Filter;
import android.view.View.OnClickListener;
-import java.lang.Class;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -137,11 +137,21 @@ public class RemoteViews implements Parcelable, Filter {
if (target != null && pendingIntent != null) {
OnClickListener listener = new OnClickListener() {
public void onClick(View v) {
- int[] pos = new int[2];
+ // Find target view location in screen coordinates and
+ // fill into PendingIntent before sending.
+ final float appScale = v.getContext().getResources()
+ .getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
v.getLocationOnScreen(pos);
- Intent intent = new Intent();
- intent.setSourceBounds(new Rect(pos[0], pos[1],
- pos[0]+v.getWidth(), pos[1]+v.getHeight()));
+
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
+
+ final Intent intent = new Intent();
+ intent.setSourceBounds(rect);
try {
// TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
v.getContext().startIntentSender(
@@ -273,6 +283,7 @@ public class RemoteViews implements Parcelable, Filter {
static final int CHAR_SEQUENCE = 10;
static final int URI = 11;
static final int BITMAP = 12;
+ static final int BUNDLE = 13;
int viewId;
String methodName;
@@ -332,6 +343,9 @@ public class RemoteViews implements Parcelable, Filter {
case BITMAP:
this.value = Bitmap.CREATOR.createFromParcel(in);
break;
+ case BUNDLE:
+ this.value = in.readBundle();
+ break;
default:
break;
}
@@ -384,6 +398,9 @@ public class RemoteViews implements Parcelable, Filter {
case BITMAP:
((Bitmap)this.value).writeToParcel(out, flags);
break;
+ case BUNDLE:
+ out.writeBundle((Bundle) this.value);
+ break;
default:
break;
}
@@ -415,6 +432,8 @@ public class RemoteViews implements Parcelable, Filter {
return Uri.class;
case BITMAP:
return Bitmap.class;
+ case BUNDLE:
+ return Bundle.class;
default:
return null;
}
@@ -876,6 +895,17 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Call a method taking one Bundle on a view in the layout for this RemoteViews.
+ *
+ * @param viewId The id of the view whose text should change
+ * @param methodName The name of the method to call.
+ * @param value The value to pass to the method.
+ */
+ public void setBundle(int viewId, String methodName, Bundle value) {
+ addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
+ }
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 24d97a5..959e982 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.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.Rect;
@@ -30,8 +32,6 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
-import com.android.internal.R;
-
import java.util.List;
/**
@@ -51,8 +51,6 @@ import java.util.List;
* <p>ScrollView only supports vertical scrolling.
*/
public class ScrollView extends FrameLayout {
- static final String TAG = "ScrollView";
-
static final int ANIMATED_SCROLL_GAP = 250;
static final float MAX_SCROLL_FACTOR = 0.5f;
@@ -114,6 +112,18 @@ public class ScrollView extends FrameLayout {
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
+
+ /**
+ * ID of the active pointer. This is used to retain consistency during
+ * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+
+ /**
+ * Sentinel value for no current active pointer.
+ * Used by {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
public ScrollView(Context context) {
this(context, null);
@@ -305,11 +315,7 @@ public class ScrollView extends FrameLayout {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Let the focused view and/or our descendants get the key first
- boolean handled = super.dispatchKeyEvent(event);
- if (handled) {
- return true;
- }
- return executeKeyEvent(event);
+ return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
/**
@@ -324,7 +330,7 @@ public class ScrollView extends FrameLayout {
mTempRect.setEmpty();
if (!canScroll()) {
- if (isFocused()) {
+ if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
View currentFocused = findFocus();
if (currentFocused == this) currentFocused = null;
View nextFocused = FocusFinder.getInstance().findNextFocus(this,
@@ -362,6 +368,18 @@ public class ScrollView extends FrameLayout {
return handled;
}
+ private boolean inChild(int x, int y) {
+ if (getChildCount() > 0) {
+ final int scrollY = mScrollY;
+ final View child = getChildAt(0);
+ return !(y < child.getTop() - scrollY
+ || y >= child.getBottom() - scrollY
+ || x < child.getLeft()
+ || x >= child.getRight());
+ }
+ return false;
+ }
+
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
@@ -380,15 +398,8 @@ public class ScrollView extends FrameLayout {
return true;
}
- if (!canScroll()) {
- mIsBeingDragged = false;
- return false;
- }
-
- final float y = ev.getY();
-
- switch (action) {
- case MotionEvent.ACTION_MOVE:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
@@ -398,15 +409,35 @@ public class ScrollView extends FrameLayout {
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ // If we don't have a valid id, the touch down wasn't on content.
+ break;
+ }
+
+ final int pointerIndex = ev.findPointerIndex(activePointerId);
+ final float y = ev.getY(pointerIndex);
final int yDiff = (int) Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
+ mLastMotionY = y;
}
break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ final float y = ev.getY();
+ if (!inChild((int) ev.getX(), (int) y)) {
+ mIsBeingDragged = false;
+ break;
+ }
- case MotionEvent.ACTION_DOWN:
- /* Remember location of down touch */
+ /*
+ * Remember location of down touch.
+ * ACTION_DOWN always refers to pointer index 0.
+ */
mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag;
@@ -415,11 +446,16 @@ public class ScrollView extends FrameLayout {
*/
mIsBeingDragged = !mScroller.isFinished();
break;
+ }
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
+ mActivePointerId = INVALID_POINTER;
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
break;
}
@@ -438,10 +474,6 @@ public class ScrollView extends FrameLayout {
// descendants.
return false;
}
-
- if (!canScroll()) {
- return false;
- }
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
@@ -449,54 +481,100 @@ public class ScrollView extends FrameLayout {
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
- final float y = ev.getY();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ final float y = ev.getY();
+ if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) {
+ return false;
+ }
+
/*
- * If being flinged and user touches, stop the fling. isFinished
- * will be false if being flinged.
- */
+ * If being flinged and user touches, stop the fling. isFinished
+ * will be false if being flinged.
+ */
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionY = y;
+ mActivePointerId = ev.getPointerId(0);
break;
+ }
case MotionEvent.ACTION_MOVE:
- // Scroll to follow the motion event
- final int deltaY = (int) (mLastMotionY - y);
- mLastMotionY = y;
-
- if (deltaY < 0) {
- if (mScrollY > 0) {
- scrollBy(0, deltaY);
- }
- } else if (deltaY > 0) {
- final int bottomEdge = getHeight() - mPaddingBottom;
- final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge;
- if (availableToScroll > 0) {
- scrollBy(0, Math.min(availableToScroll, deltaY));
- }
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ final float y = ev.getY(activePointerIndex);
+ final int deltaY = (int) (mLastMotionY - y);
+ mLastMotionY = y;
+
+ scrollBy(0, deltaY);
}
break;
- case MotionEvent.ACTION_UP:
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) velocityTracker.getYVelocity();
+ case MotionEvent.ACTION_UP:
+ if (mIsBeingDragged) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+
+ if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
+ fling(-initialVelocity);
+ }
- if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
- fling(-initialVelocity);
- }
+ mActivePointerId = INVALID_POINTER;
+ mIsBeingDragged = false;
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsBeingDragged && getChildCount() > 0) {
+ mActivePointerId = INVALID_POINTER;
+ mIsBeingDragged = false;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
}
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
}
return true;
}
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
+ MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ // TODO: Make this decision more intelligent.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionY = ev.getY(newPointerIndex);
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private int getScrollRange() {
+ int scrollRange = 0;
+ if (getChildCount() > 0) {
+ View child = getChildAt(0);
+ scrollRange = Math.max(0,
+ child.getHeight() - getHeight() - mPaddingBottom - mPaddingTop);
+ }
+ return scrollRange;
+ }
/**
* <p>
@@ -829,10 +907,19 @@ public class ScrollView extends FrameLayout {
* @param dy the number of pixels to scroll by on the Y axis
*/
public final void smoothScrollBy(int dx, int dy) {
+ if (getChildCount() == 0) {
+ // Nothing to do.
+ return;
+ }
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
- mScroller.startScroll(mScrollX, mScrollY, dx, dy);
- awakenScrollBars(mScroller.getDuration());
+ final int height = getHeight() - mPaddingBottom - mPaddingTop;
+ final int bottom = getChildAt(0).getHeight();
+ final int maxY = Math.max(0, bottom - height);
+ final int scrollY = mScrollY;
+ dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
+
+ mScroller.startScroll(mScrollX, scrollY, 0, dy);
invalidate();
} else {
if (!mScroller.isFinished()) {
@@ -859,10 +946,19 @@ public class ScrollView extends FrameLayout {
*/
@Override
protected int computeVerticalScrollRange() {
- int count = getChildCount();
- return count == 0 ? getHeight() : (getChildAt(0)).getBottom();
+ final int count = getChildCount();
+ final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop;
+ if (count == 0) {
+ return contentHeight;
+ }
+
+ return getChildAt(0).getBottom();
}
+ @Override
+ protected int computeVerticalScrollOffset() {
+ return Math.max(0, super.computeVerticalScrollOffset());
+ }
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
@@ -916,18 +1012,19 @@ public class ScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
+
if (getChildCount() > 0) {
View child = getChildAt(0);
- mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
- mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
- } else {
- mScrollX = x;
- mScrollY = y;
- }
- if (oldX != mScrollX || oldY != mScrollY) {
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
+ y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
+ if (x != oldX || y != oldY) {
+ mScrollX = x;
+ mScrollY = y;
+ onScrollChanged(x, y, oldX, oldY);
+ }
}
-
+ awakenScrollBars();
+
// Keep on drawing until the animation has finished.
postInvalidate();
}
@@ -1152,7 +1249,7 @@ public class ScrollView extends FrameLayout {
* Fling the scroll view
*
* @param velocityY The initial velocity in the Y direction. Positive
- * numbers mean that the finger/curor is moving down the screen,
+ * numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
public void fling(int velocityY) {
@@ -1160,7 +1257,8 @@ public class ScrollView extends FrameLayout {
int height = getHeight() - mPaddingBottom - mPaddingTop;
int bottom = getChildAt(0).getHeight();
- mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height);
+ mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
+ Math.max(0, bottom - height));
final boolean movingDown = velocityY > 0;
@@ -1176,7 +1274,6 @@ public class ScrollView extends FrameLayout {
mScrollViewMovedFocus = false;
}
- awakenScrollBars(mScroller.getDuration());
invalidate();
}
}
@@ -1186,6 +1283,7 @@ public class ScrollView extends FrameLayout {
*
* <p>This version also clamps the scrolling to the bounds of our child.
*/
+ @Override
public void scrollTo(int x, int y) {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 11dab02..784a75f 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -203,9 +203,6 @@ public class Scroller {
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
- if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) {
- mFinished = true;
- }
break;
case FLING_MODE:
float timePassedSeconds = timePassed / 1000.0f;
@@ -221,10 +218,6 @@ public class Scroller {
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
-
- if (mCurrX == mFinalX && mCurrY == mFinalY) {
- mFinished = true;
- }
break;
}
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 9dd4d15..4b17a92 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -25,7 +25,6 @@ import android.net.Uri;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.WeakHashMap;
/**
* An easy adapter to map static data to views defined in an XML file. You can specify the data
@@ -58,7 +57,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
private int mResource;
private int mDropDownResource;
private LayoutInflater mInflater;
- private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>();
private SimpleFilter mFilter;
private ArrayList<Map<String, ?>> mUnfilteredData;
@@ -121,16 +119,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
View v;
if (convertView == null) {
v = mInflater.inflate(resource, parent, false);
-
- final int[] to = mTo;
- final int count = to.length;
- final View[] holder = new View[count];
-
- for (int i = 0; i < count; i++) {
- holder[i] = v.findViewById(to[i]);
- }
-
- mHolders.put(v, holder);
} else {
v = convertView;
}
@@ -162,13 +150,12 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
}
final ViewBinder binder = mViewBinder;
- final View[] holder = mHolders.get(view);
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
- final View v = holder[i];
+ final View v = view.findViewById(to[i]);
if (v != null) {
final Object data = dataSet.get(from[i]);
String text = data == null ? "" : data.toString();
@@ -185,9 +172,14 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
+ } else if (v instanceof TextView) {
+ // Note: keep the instanceof TextView check at the bottom of these
+ // ifs since a lot of views are TextViews (e.g. CheckBoxes).
+ setViewText((TextView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() +
- " should be bound to a Boolean, not a " + data.getClass());
+ " should be bound to a Boolean, not a " +
+ (data == null ? "<unknown type>" : data.getClass()));
}
} else if (v instanceof TextView) {
// Note: keep the instanceof TextView check at the bottom of these
diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java
index 436b79b..7d3459e 100644
--- a/core/java/android/widget/SimpleCursorAdapter.java
+++ b/core/java/android/widget/SimpleCursorAdapter.java
@@ -20,9 +20,6 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.WeakHashMap;
/**
* An easy adapter to map columns from a cursor to TextViews or ImageViews
@@ -66,7 +63,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
private CursorToStringConverter mCursorToStringConverter;
private ViewBinder mViewBinder;
private String[] mOriginalFrom;
- private final WeakHashMap<View, View[]> mHolders = new WeakHashMap<View, View[]>();
/**
* Constructor.
@@ -91,29 +87,6 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
findColumns(from);
}
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return generateViewHolder(super.newView(context, cursor, parent));
- }
-
- @Override
- public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
- return generateViewHolder(super.newDropDownView(context, cursor, parent));
- }
-
- private View generateViewHolder(View v) {
- final int[] to = mTo;
- final int count = to.length;
- final View[] holder = new View[count];
-
- for (int i = 0; i < count; i++) {
- holder[i] = v.findViewById(to[i]);
- }
- mHolders.put(v, holder);
-
- return v;
- }
-
/**
* Binds all of the field names passed into the "to" parameter of the
* constructor with their corresponding cursor columns as specified in the
@@ -140,13 +113,13 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter {
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
- final View[] holder = mHolders.get(view);
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
+ final int[] to = mTo;
for (int i = 0; i < count; i++) {
- final View v = holder[i];
+ final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index f706744..11d72de 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -43,7 +43,7 @@ import android.view.accessibility.AccessibilityEvent;
* SlidingDrawer should be used as an overlay inside layouts. This means SlidingDrawer
* should only be used inside of a FrameLayout or a RelativeLayout for instance. The
* size of the SlidingDrawer defines how much space the content will occupy once slid
- * out so SlidingDrawer should usually use fill_parent for both its dimensions.
+ * out so SlidingDrawer should usually use match_parent for both its dimensions.
*
* Inside an XML layout, SlidingDrawer must define the id of the handle and of the
* content:
@@ -51,8 +51,8 @@ import android.view.accessibility.AccessibilityEvent;
* <pre class="prettyprint">
* &lt;SlidingDrawer
* android:id="@+id/drawer"
- * android:layout_width="fill_parent"
- * android:layout_height="fill_parent"
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent"
*
* android:handle="@+id/handle"
* android:content="@+id/content"&gt;
@@ -64,8 +64,8 @@ import android.view.accessibility.AccessibilityEvent;
*
* &lt;GridView
* android:id="@id/content"
- * android:layout_width="fill_parent"
- * android:layout_height="fill_parent" /&gt;
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent" /&gt;
*
* &lt;/SlidingDrawer&gt;
* </pre>
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index bcddca1..2f6dd1e 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -295,14 +295,18 @@ public class Spinner extends AbsSpinner implements OnClickListener {
*/
private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
private SpinnerAdapter mAdapter;
+ private ListAdapter mListAdapter;
/**
- * <p>Creates a new ListAddapter wrapper for the specified adapter.</p>
+ * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
*
* @param adapter the Adapter to transform into a ListAdapter
*/
public DropDownAdapter(SpinnerAdapter adapter) {
this.mAdapter = adapter;
+ if (adapter instanceof ListAdapter) {
+ this.mListAdapter = (ListAdapter) adapter;
+ }
}
public int getCount() {
@@ -343,21 +347,29 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
/**
- * <p>Always returns false.</p>
- *
- * @return false
+ * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+ * Otherwise, return true.
*/
public boolean areAllItemsEnabled() {
- return true;
+ final ListAdapter adapter = mListAdapter;
+ if (adapter != null) {
+ return adapter.areAllItemsEnabled();
+ } else {
+ return true;
+ }
}
/**
- * <p>Always returns false.</p>
- *
- * @return false
+ * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
+ * Otherwise, return true.
*/
public boolean isEnabled(int position) {
- return true;
+ final ListAdapter adapter = mListAdapter;
+ if (adapter != null) {
+ return adapter.isEnabled(position);
+ } else {
+ return true;
+ }
}
public int getItemViewType(int position) {
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 31920e7..02cd6a8 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -16,6 +16,8 @@
package android.widget;
+import com.android.internal.R;
+
import android.app.LocalActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -33,8 +35,6 @@ import android.view.Window;
import java.util.ArrayList;
import java.util.List;
-import com.android.internal.R;
-
/**
* Container for a tabbed window view. This object holds two children: a set of tab labels that the
* user clicks to select a specific tab, and a FrameLayout object that displays the contents of that
@@ -204,7 +204,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
// If this is a custom view, then do not draw the bottom strips for
// the tab indicators.
if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
- mTabWidget.setDrawBottomStrips(false);
+ mTabWidget.setStripEnabled(false);
}
mTabWidget.addView(tabIndicator);
mTabSpecs.add(tabSpec);
@@ -279,6 +279,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
if (!handled
&& (event.getAction() == KeyEvent.ACTION_DOWN)
&& (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP)
+ && (mCurrentView != null)
&& (mCurrentView.isRootNamespace())
&& (mCurrentView.hasFocus())
&& (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
@@ -292,7 +293,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
- mCurrentView.dispatchWindowFocusChanged(hasFocus);
+ if (mCurrentView != null){
+ mCurrentView.dispatchWindowFocusChanged(hasFocus);
+ }
}
public void setCurrentTab(int index) {
@@ -324,8 +327,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
.addView(
mCurrentView,
new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
}
if (!mTabWidget.hasFocus()) {
@@ -621,7 +624,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
public void tabClosed() {
- mTabContent.setVisibility(View.INVISIBLE);
+ mTabContent.setVisibility(View.GONE);
}
}
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 2ba6268..4e1b585 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -16,8 +16,7 @@
package android.widget;
-import com.android.internal.R;
-
+import android.R;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -26,13 +25,10 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnFocusChangeListener;
-
-
/**
*
* Displays a list of tab labels representing each page in the parent's tab
@@ -44,17 +40,26 @@ import android.view.View.OnFocusChangeListener;
* handler, and manage callbacks. You might call this object to iterate the list
* of tabs, or to tweak the layout of the tab list, but most methods should be
* called on the containing TabHost object.
+ *
+ * @attr ref android.R.styleable#TabWidget_divider
+ * @attr ref android.R.styleable#TabWidget_tabStripEnabled
+ * @attr ref android.R.styleable#TabWidget_tabStripLeft
+ * @attr ref android.R.styleable#TabWidget_tabStripRight
*/
public class TabWidget extends LinearLayout implements OnFocusChangeListener {
-
-
private OnTabSelectionChanged mSelectionChangedListener;
+
private int mSelectedTab = 0;
- private Drawable mBottomLeftStrip;
- private Drawable mBottomRightStrip;
+
+ private Drawable mLeftStrip;
+ private Drawable mRightStrip;
+
+ private boolean mDrawBottomStrips = true;
private boolean mStripMoved;
+
private Drawable mDividerDrawable;
- private boolean mDrawBottomStrips = true;
+
+ private final Rect mBounds = new Rect();
public TabWidget(Context context) {
this(context, null);
@@ -66,13 +71,19 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
public TabWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
- initTabWidget();
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget,
defStyle, 0);
+ mDrawBottomStrips = a.getBoolean(R.styleable.TabWidget_tabStripEnabled, true);
+ mDividerDrawable = a.getDrawable(R.styleable.TabWidget_divider);
+ mLeftStrip = a.getDrawable(R.styleable.TabWidget_tabStripLeft);
+ mRightStrip = a.getDrawable(R.styleable.TabWidget_tabStripRight);
+
a.recycle();
+
+ initTabWidget();
}
@Override
@@ -103,19 +114,26 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) {
// Donut apps get old color scheme
- mBottomLeftStrip = resources.getDrawable(
- com.android.internal.R.drawable.tab_bottom_left_v4);
- mBottomRightStrip = resources.getDrawable(
- com.android.internal.R.drawable.tab_bottom_right_v4);
+ if (mLeftStrip == null) {
+ mLeftStrip = resources.getDrawable(
+ com.android.internal.R.drawable.tab_bottom_left_v4);
+ }
+ if (mRightStrip == null) {
+ mRightStrip = resources.getDrawable(
+ com.android.internal.R.drawable.tab_bottom_right_v4);
+ }
} else {
// Use modern color scheme for Eclair and beyond
- mBottomLeftStrip = resources.getDrawable(
- com.android.internal.R.drawable.tab_bottom_left);
- mBottomRightStrip = resources.getDrawable(
- com.android.internal.R.drawable.tab_bottom_right);
+ if (mLeftStrip == null) {
+ mLeftStrip = resources.getDrawable(
+ com.android.internal.R.drawable.tab_bottom_left);
+ }
+ if (mRightStrip == null) {
+ mRightStrip = resources.getDrawable(
+ com.android.internal.R.drawable.tab_bottom_right);
+ }
}
-
// Deal with focus, as we don't want the focus to go by default
// to a tab other than the current tab
setFocusable(true);
@@ -159,6 +177,8 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
*/
public void setDividerDrawable(Drawable drawable) {
mDividerDrawable = drawable;
+ requestLayout();
+ invalidate();
}
/**
@@ -168,22 +188,78 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
*/
public void setDividerDrawable(int resId) {
mDividerDrawable = mContext.getResources().getDrawable(resId);
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Sets the drawable to use as the left part of the strip below the
+ * tab indicators.
+ * @param drawable the left strip drawable
+ */
+ public void setLeftStripDrawable(Drawable drawable) {
+ mLeftStrip = drawable;
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Sets the drawable to use as the left part of the strip below the
+ * tab indicators.
+ * @param resId the resource identifier of the drawable to use as the
+ * left strip drawable
+ */
+ public void setLeftStripDrawable(int resId) {
+ mLeftStrip = mContext.getResources().getDrawable(resId);
+ requestLayout();
+ invalidate();
}
/**
+ * Sets the drawable to use as the right part of the strip below the
+ * tab indicators.
+ * @param drawable the right strip drawable
+ */
+ public void setRightStripDrawable(Drawable drawable) {
+ mRightStrip = drawable;
+ requestLayout();
+ invalidate(); }
+
+ /**
+ * Sets the drawable to use as the right part of the strip below the
+ * tab indicators.
+ * @param resId the resource identifier of the drawable to use as the
+ * right strip drawable
+ */
+ public void setRightStripDrawable(int resId) {
+ mRightStrip = mContext.getResources().getDrawable(resId);
+ requestLayout();
+ invalidate();
+ }
+
+ /**
* Controls whether the bottom strips on the tab indicators are drawn or
* not. The default is to draw them. If the user specifies a custom
* view for the tab indicators, then the TabHost class calls this method
* to disable drawing of the bottom strips.
- * @param drawBottomStrips true if the bottom strips should be drawn.
+ * @param stripEnabled true if the bottom strips should be drawn.
+ */
+ public void setStripEnabled(boolean stripEnabled) {
+ mDrawBottomStrips = stripEnabled;
+ invalidate();
+ }
+
+ /**
+ * Indicates whether the bottom strips on the tab indicators are drawn
+ * or not.
*/
- void setDrawBottomStrips(boolean drawBottomStrips) {
- mDrawBottomStrips = drawBottomStrips;
+ public boolean isStripEnabled() {
+ return mDrawBottomStrips;
}
@Override
public void childDrawableStateChanged(View child) {
- if (child == getChildTabViewAt(mSelectedTab)) {
+ if (getTabCount() > 0 && child == getChildTabViewAt(mSelectedTab)) {
// To make sure that the bottom strip is redrawn
invalidate();
}
@@ -194,6 +270,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
+ // Do nothing if there are no tabs.
+ if (getTabCount() == 0) return;
+
// If the user specified a custom view for the tab indicators, then
// do not draw the bottom strips.
if (!mDrawBottomStrips) {
@@ -201,33 +280,28 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
return;
}
- View selectedChild = getChildTabViewAt(mSelectedTab);
+ final View selectedChild = getChildTabViewAt(mSelectedTab);
+
+ final Drawable leftStrip = mLeftStrip;
+ final Drawable rightStrip = mRightStrip;
- mBottomLeftStrip.setState(selectedChild.getDrawableState());
- mBottomRightStrip.setState(selectedChild.getDrawableState());
+ leftStrip.setState(selectedChild.getDrawableState());
+ rightStrip.setState(selectedChild.getDrawableState());
if (mStripMoved) {
- Rect selBounds = new Rect(); // Bounds of the selected tab indicator
- selBounds.left = selectedChild.getLeft();
- selBounds.right = selectedChild.getRight();
+ final Rect bounds = mBounds;
+ bounds.left = selectedChild.getLeft();
+ bounds.right = selectedChild.getRight();
final int myHeight = getHeight();
- mBottomLeftStrip.setBounds(
- Math.min(0, selBounds.left
- - mBottomLeftStrip.getIntrinsicWidth()),
- myHeight - mBottomLeftStrip.getIntrinsicHeight(),
- selBounds.left,
- getHeight());
- mBottomRightStrip.setBounds(
- selBounds.right,
- myHeight - mBottomRightStrip.getIntrinsicHeight(),
- Math.max(getWidth(),
- selBounds.right + mBottomRightStrip.getIntrinsicWidth()),
- myHeight);
+ leftStrip.setBounds(Math.min(0, bounds.left - leftStrip.getIntrinsicWidth()),
+ myHeight - leftStrip.getIntrinsicHeight(), bounds.left, myHeight);
+ rightStrip.setBounds(bounds.right, myHeight - rightStrip.getIntrinsicHeight(),
+ Math.max(getWidth(), bounds.right + rightStrip.getIntrinsicWidth()), myHeight);
mStripMoved = false;
}
- mBottomLeftStrip.draw(canvas);
- mBottomRightStrip.draw(canvas);
+ leftStrip.draw(canvas);
+ rightStrip.draw(canvas);
}
/**
@@ -310,7 +384,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
if (child.getLayoutParams() == null) {
final LinearLayout.LayoutParams lp = new LayoutParams(
0,
- ViewGroup.LayoutParams.FILL_PARENT, 1.0f);
+ ViewGroup.LayoutParams.MATCH_PARENT, 1.0f);
lp.setMargins(0, 0, 0, 0);
child.setLayoutParams(lp);
}
@@ -325,7 +399,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
ImageView divider = new ImageView(mContext);
final LinearLayout.LayoutParams lp = new LayoutParams(
mDividerDrawable.getIntrinsicWidth(),
- LayoutParams.FILL_PARENT);
+ LayoutParams.MATCH_PARENT);
lp.setMargins(0, 0, 0, 0);
divider.setLayoutParams(lp);
divider.setBackgroundDrawable(mDividerDrawable);
@@ -347,7 +421,7 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
public void onFocusChange(View v, boolean hasFocus) {
- if (v == this && hasFocus) {
+ if (v == this && hasFocus && getTabCount() > 0) {
getChildTabViewAt(mSelectedTab).requestFocus();
return;
}
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index afa2f3b..73760ac 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -52,7 +52,7 @@ import java.util.regex.Pattern;
* {@link #setColumnCollapsed(int,boolean) setColumnCollapsed()}.</p>
*
* <p>The children of a TableLayout cannot specify the <code>layout_width</code>
- * attribute. Width is always <code>FILL_PARENT</code>. However, the
+ * attribute. Width is always <code>MATCH_PARENT</code>. However, the
* <code>layout_height</code> attribute can be defined by a child; default value
* is {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}. If the child
* is a {@link android.widget.TableRow}, then the height is always
@@ -575,6 +575,16 @@ public class TableLayout extends LinearLayout {
final int totalExtraSpace = size - totalWidth;
int extraSpace = totalExtraSpace / count;
+ // Column's widths are changed: force child table rows to re-measure.
+ // (done by super.measureVertical after shrinkAndStretchColumns.)
+ final int nbChildren = getChildCount();
+ for (int i = 0; i < nbChildren; i++) {
+ View child = getChildAt(i);
+ if (child instanceof TableRow) {
+ child.forceLayout();
+ }
+ }
+
if (!allColumns) {
for (int i = 0; i < count; i++) {
int column = columns.keyAt(i);
@@ -621,7 +631,7 @@ public class TableLayout extends LinearLayout {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
* and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
*/
@Override
@@ -647,7 +657,7 @@ public class TableLayout extends LinearLayout {
/**
* <p>This set of layout parameters enforces the width of each child to be
- * {@link #FILL_PARENT} and the height of each child to be
+ * {@link #MATCH_PARENT} and the height of each child to be
* {@link #WRAP_CONTENT}, but only if the height is not specified.</p>
*/
@SuppressWarnings({"UnusedDeclaration"})
@@ -663,14 +673,14 @@ public class TableLayout extends LinearLayout {
* {@inheritDoc}
*/
public LayoutParams(int w, int h) {
- super(FILL_PARENT, h);
+ super(MATCH_PARENT, h);
}
/**
* {@inheritDoc}
*/
public LayoutParams(int w, int h, float initWeight) {
- super(FILL_PARENT, h, initWeight);
+ super(MATCH_PARENT, h, initWeight);
}
/**
@@ -679,7 +689,7 @@ public class TableLayout extends LinearLayout {
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*/
public LayoutParams() {
- super(FILL_PARENT, WRAP_CONTENT);
+ super(MATCH_PARENT, WRAP_CONTENT);
}
/**
@@ -698,7 +708,7 @@ public class TableLayout extends LinearLayout {
/**
* <p>Fixes the row's width to
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}; the row's
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}; the row's
* height is fixed to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} if no layout
* height is specified.</p>
@@ -710,7 +720,7 @@ public class TableLayout extends LinearLayout {
@Override
protected void setBaseAttributes(TypedArray a,
int widthAttr, int heightAttr) {
- this.width = FILL_PARENT;
+ this.width = MATCH_PARENT;
if (a.hasValue(heightAttr)) {
this.height = a.getLayoutDimension(heightAttr, "layout_height");
} else {
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 5628cab..48d12df 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -22,8 +22,8 @@ import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewDebug;
+import android.view.ViewGroup;
/**
@@ -35,7 +35,7 @@ import android.view.ViewDebug;
* <p>The children of a TableRow do not need to specify the
* <code>layout_width</code> and <code>layout_height</code> attributes in the
* XML file. TableRow always enforces those values to be respectively
- * {@link android.widget.TableLayout.LayoutParams#FILL_PARENT} and
+ * {@link android.widget.TableLayout.LayoutParams#MATCH_PARENT} and
* {@link android.widget.TableLayout.LayoutParams#WRAP_CONTENT}.</p>
*
* <p>
@@ -299,7 +299,7 @@ public class TableRow extends LinearLayout {
case LayoutParams.WRAP_CONTENT:
spec = getChildMeasureSpec(widthMeasureSpec, 0, LayoutParams.WRAP_CONTENT);
break;
- case LayoutParams.FILL_PARENT:
+ case LayoutParams.MATCH_PARENT:
spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
break;
default:
@@ -351,7 +351,7 @@ public class TableRow extends LinearLayout {
/**
* Returns a set of layout parameters with a width of
- * {@link android.view.ViewGroup.LayoutParams#FILL_PARENT},
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT},
* a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
*/
@Override
@@ -451,7 +451,7 @@ public class TableRow extends LinearLayout {
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*/
public LayoutParams() {
- super(FILL_PARENT, WRAP_CONTENT);
+ super(MATCH_PARENT, WRAP_CONTENT);
column = -1;
span = 1;
}
@@ -459,7 +459,7 @@ public class TableRow extends LinearLayout {
/**
* <p>Puts the view in the specified column.</p>
*
- * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#FILL_PARENT}
+ * <p>Sets the child width to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
* and the child height to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.</p>
*
@@ -490,7 +490,7 @@ public class TableRow extends LinearLayout {
if (a.hasValue(widthAttr)) {
width = a.getLayoutDimension(widthAttr, "layout_width");
} else {
- width = FILL_PARENT;
+ width = MATCH_PARENT;
}
// We don't want to force users to specify a layout_height
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6418dad..64c9c99 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -198,6 +198,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mFreezesText;
private boolean mFrozenWithFocus;
private boolean mTemporaryDetach;
+ private boolean mDispatchTemporaryDetach;
private boolean mEatTouchRelease = false;
private boolean mScrolled = false;
@@ -1375,15 +1376,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
dr.mDrawableLeft.setCallback(null);
}
dr.mDrawableLeft = left;
- if (dr.mDrawableTop != left && dr.mDrawableTop != null) {
+
+ if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
dr.mDrawableTop.setCallback(null);
}
dr.mDrawableTop = top;
- if (dr.mDrawableRight != left && dr.mDrawableRight != null) {
+
+ if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
dr.mDrawableRight.setCallback(null);
}
dr.mDrawableRight = right;
- if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) {
+
+ if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
dr.mDrawableBottom.setCallback(null);
}
dr.mDrawableBottom = bottom;
@@ -2822,9 +2826,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Null means to use the normal empty text. The hint does not currently
* participate in determining the size of the view.
*
- * This method is deprecated. Use {link #setHint(int, String)} or
- * {link #setHint(CharSequence, String)} instead.
- *
* @attr ref android.R.styleable#TextView_hint
*/
@android.view.RemotableViewMethod
@@ -2844,9 +2845,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Sets the text to be displayed when the text of the TextView is empty,
* from a resource.
*
- * This method is deprecated. Use {link #setHint(int, String)} or
- * {link #setHint(CharSequence, String)} instead.
- *
* @attr ref android.R.styleable#TextView_hint
*/
@android.view.RemotableViewMethod
@@ -4992,7 +4990,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int height = mLayoutParams.height;
// If the size of the view does not depend on the size of the text, try to
// start the marquee immediately
- if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) {
+ if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
startMarquee();
} else {
// Defer the start of the marquee until we know our width (see setFrame())
@@ -5307,7 +5305,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (desiredHeight != this.getHeight()) {
sizeChanged = true;
}
- } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) {
+ } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
if (mDesiredHeightAtMeasure >= 0) {
int desiredHeight = getDesiredHeight();
@@ -5354,7 +5352,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
- mLayoutParams.height != LayoutParams.FILL_PARENT) {
+ mLayoutParams.height != LayoutParams.MATCH_PARENT) {
invalidate();
return;
}
@@ -6371,14 +6369,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mBlink.postAtTime(mBlink, mShowCursor + BLINK);
}
+ /**
+ * @hide
+ */
+ @Override
+ public void dispatchFinishTemporaryDetach() {
+ mDispatchTemporaryDetach = true;
+ super.dispatchFinishTemporaryDetach();
+ mDispatchTemporaryDetach = false;
+ }
+
@Override
public void onStartTemporaryDetach() {
- mTemporaryDetach = true;
+ super.onStartTemporaryDetach();
+ // Only track when onStartTemporaryDetach() is called directly,
+ // usually because this instance is an editable field in a list
+ if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
}
@Override
public void onFinishTemporaryDetach() {
- mTemporaryDetach = false;
+ super.onFinishTemporaryDetach();
+ // Only track when onStartTemporaryDetach() is called directly,
+ // usually because this instance is an editable field in a list
+ if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
}
@Override
@@ -6975,6 +6989,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (!isShown()) {
+ return false;
+ }
+
final boolean isPassword = isPasswordInputType(mInputType);
if (!isPassword) {
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index ab4edc5..caed308 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -23,9 +23,9 @@ import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.NumberPicker;
import com.android.internal.R;
-import com.android.internal.widget.NumberPicker;
import java.text.DateFormatSymbols;
import java.util.Calendar;
@@ -357,4 +357,3 @@ public class TimePicker extends FrameLayout {
mOnTimeChangedListener.onTimeChanged(this, getCurrentHour(), getCurrentMinute());
}
}
-
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 8142a82..531d9fe 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -38,6 +38,7 @@ import android.view.View;
import android.widget.MediaController.*;
import java.io.IOException;
+import java.util.Map;
/**
* Displays a video file. The VideoView class
@@ -50,6 +51,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private String TAG = "VideoView";
// settable by the client
private Uri mUri;
+ private Map<String, String> mHeaders;
private int mDuration;
// all possible internal states
@@ -60,6 +62,9 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private static final int STATE_PLAYING = 3;
private static final int STATE_PAUSED = 4;
private static final int STATE_PLAYBACK_COMPLETED = 5;
+ private static final int STATE_SUSPEND = 6;
+ private static final int STATE_RESUME = 7;
+ private static final int STATE_SUSPEND_UNSUPPORTED = 8;
// mCurrentState is a VideoView object's current state.
// mTargetState is the state that a method caller intends to reach.
@@ -85,17 +90,18 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private boolean mCanPause;
private boolean mCanSeekBack;
private boolean mCanSeekForward;
+ private int mStateWhenSuspended; //state before calling suspend()
public VideoView(Context context) {
super(context);
initVideoView();
}
-
+
public VideoView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
initVideoView();
}
-
+
public VideoView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initVideoView();
@@ -122,7 +128,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
//Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
-
+
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
@@ -137,13 +143,13 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
break;
case MeasureSpec.AT_MOST:
- /* Parent says we can be as big as we want, up to specSize.
- * Don't be larger than specSize, and don't be larger than
+ /* Parent says we can be as big as we want, up to specSize.
+ * Don't be larger than specSize, and don't be larger than
* the max size imposed on ourselves.
*/
result = Math.min(desiredSize, specSize);
break;
-
+
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
@@ -151,7 +157,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
return result;
}
-
+
private void initVideoView() {
mVideoWidth = 0;
mVideoHeight = 0;
@@ -169,13 +175,21 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
public void setVideoURI(Uri uri) {
+ setVideoURI(uri, null);
+ }
+
+ /**
+ * @hide
+ */
+ public void setVideoURI(Uri uri, Map<String, String> headers) {
mUri = uri;
+ mHeaders = headers;
mSeekWhenPrepared = 0;
openVideo();
requestLayout();
invalidate();
}
-
+
public void stopPlayback() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
@@ -191,7 +205,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
// not ready for playback just yet, will try again later
return;
}
- // Tell the music playback service to pause
+ // Tell the music playback service to pause
// TODO: these constants need to be published somewhere in the framework.
Intent i = new Intent("com.android.music.musicservicecommand");
i.putExtra("command", "pause");
@@ -209,7 +223,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mCurrentBufferPercentage = 0;
- mMediaPlayer.setDataSource(mContext, mUri);
+ mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
mMediaPlayer.setDisplay(mSurfaceHolder);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setScreenOnWhilePlaying(true);
@@ -232,7 +246,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
return;
}
}
-
+
public void setMediaController(MediaController controller) {
if (mMediaController != null) {
mMediaController.hide();
@@ -250,7 +264,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaController.setEnabled(isInPlaybackState());
}
}
-
+
MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
new MediaPlayer.OnVideoSizeChangedListener() {
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
@@ -261,7 +275,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
}
};
-
+
MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mp) {
mCurrentState = STATE_PREPARED;
@@ -278,7 +292,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
|| data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
} else {
- mCanPause = mCanSeekForward = mCanSeekForward = true;
+ mCanPause = mCanSeekBack = mCanSeekForward = true;
}
if (mOnPreparedListener != null) {
@@ -456,7 +470,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
public void surfaceCreated(SurfaceHolder holder)
{
mSurfaceHolder = holder;
- openVideo();
+ //resume() was called before surfaceCreated()
+ if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND
+ && mTargetState == STATE_RESUME) {
+ mMediaPlayer.setDisplay(mSurfaceHolder);
+ resume();
+ } else {
+ openVideo();
+ }
}
public void surfaceDestroyed(SurfaceHolder holder)
@@ -464,7 +485,9 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
// after we return from this we can't use the surface any more
mSurfaceHolder = null;
if (mMediaController != null) mMediaController.hide();
- release(true);
+ if (mCurrentState != STATE_SUSPEND) {
+ release(true);
+ }
}
};
@@ -490,7 +513,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
return false;
}
-
+
@Override
public boolean onTrackballEvent(MotionEvent ev) {
if (isInPlaybackState() && mMediaController != null) {
@@ -498,7 +521,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
return false;
}
-
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
@@ -519,7 +542,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaController.hide();
}
return true;
- } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
+ } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
&& mMediaPlayer.isPlaying()) {
pause();
mMediaController.show();
@@ -532,13 +555,13 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
private void toggleMediaControlsVisiblity() {
- if (mMediaController.isShowing()) {
+ if (mMediaController.isShowing()) {
mMediaController.hide();
} else {
mMediaController.show();
}
}
-
+
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start();
@@ -546,7 +569,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
mTargetState = STATE_PLAYING;
}
-
+
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
@@ -556,8 +579,41 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
mTargetState = STATE_PAUSED;
}
-
- // cache duration as mDuration for faster access
+
+ public void suspend() {
+ if (isInPlaybackState()) {
+ if (mMediaPlayer.suspend()) {
+ mStateWhenSuspended = mCurrentState;
+ mCurrentState = STATE_SUSPEND;
+ mTargetState = STATE_SUSPEND;
+ } else {
+ release(false);
+ mCurrentState = STATE_SUSPEND_UNSUPPORTED;
+ Log.w(TAG, "Unable to suspend video. Release MediaPlayer.");
+ }
+ }
+ }
+
+ public void resume() {
+ if (mSurfaceHolder == null && mCurrentState == STATE_SUSPEND){
+ mTargetState = STATE_RESUME;
+ return;
+ }
+ if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND) {
+ if (mMediaPlayer.resume()) {
+ mCurrentState = mStateWhenSuspended;
+ mTargetState = mStateWhenSuspended;
+ } else {
+ Log.w(TAG, "Unable to resume video");
+ }
+ return;
+ }
+ if (mCurrentState == STATE_SUSPEND_UNSUPPORTED) {
+ openVideo();
+ }
+ }
+
+ // cache duration as mDuration for faster access
public int getDuration() {
if (isInPlaybackState()) {
if (mDuration > 0) {
@@ -569,14 +625,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mDuration = -1;
return mDuration;
}
-
+
public int getCurrentPosition() {
if (isInPlaybackState()) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
-
+
public void seekTo(int msec) {
if (isInPlaybackState()) {
mMediaPlayer.seekTo(msec);
@@ -584,12 +640,12 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
} else {
mSeekWhenPrepared = msec;
}
- }
-
+ }
+
public boolean isPlaying() {
return isInPlaybackState() && mMediaPlayer.isPlaying();
}
-
+
public int getBufferPercentage() {
if (mMediaPlayer != null) {
return mCurrentBufferPercentage;
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index aee25b0..8034961 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -38,7 +38,7 @@ import android.widget.RemoteViews.RemoteView;
@RemoteView
public class ViewFlipper extends ViewAnimator {
private static final String TAG = "ViewFlipper";
- private static final boolean LOGD = true;
+ private static final boolean LOGD = false;
private static final int DEFAULT_INTERVAL = 3000;
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index 0dcaf95..71ae624 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -80,7 +80,7 @@ public class ViewSwitcher extends ViewAnimator {
View child = mFactory.makeView();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp == null) {
- lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
+ lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
addView(child, lp);
return child;
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index c683e71..3df419a 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -247,7 +247,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
LayoutParams.FLAG_LAYOUT_NO_LIMITS |
LayoutParams.FLAG_ALT_FOCUSABLE_IM;
lp.height = LayoutParams.WRAP_CONTENT;
- lp.width = LayoutParams.FILL_PARENT;
+ lp.width = LayoutParams.MATCH_PARENT;
lp.type = LayoutParams.TYPE_APPLICATION_PANEL;
lp.format = PixelFormat.TRANSLUCENT;
lp.windowAnimations = com.android.internal.R.style.Animation_ZoomButtons;
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 57dbb44..107b145 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -16,7 +16,7 @@
package com.android.internal.app;
-import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -48,7 +49,6 @@ import android.widget.ScrollView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
-import android.util.AttributeSet;
import com.android.internal.R;
@@ -322,24 +322,24 @@ public class AlertController {
public Button getButton(int whichButton) {
switch (whichButton) {
case DialogInterface.BUTTON_POSITIVE:
- return mButtonPositiveMessage != null ? mButtonPositive : null;
+ return mButtonPositive;
case DialogInterface.BUTTON_NEGATIVE:
- return mButtonNegativeMessage != null ? mButtonNegative : null;
+ return mButtonNegative;
case DialogInterface.BUTTON_NEUTRAL:
- return mButtonNeutralMessage != null ? mButtonNeutral : null;
+ return mButtonNeutral;
default:
return null;
}
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
+ @SuppressWarnings({"UnusedDeclaration"})
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (mScrollView != null && mScrollView.executeKeyEvent(event)) return true;
- return false;
+ return mScrollView != null && mScrollView.executeKeyEvent(event);
}
private void setupView() {
@@ -361,7 +361,7 @@ public class AlertController {
if (mView != null) {
customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel);
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
- custom.addView(mView, new LayoutParams(FILL_PARENT, FILL_PARENT));
+ custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mViewSpacingSpecified) {
custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight,
mViewSpacingBottom);
@@ -391,7 +391,7 @@ public class AlertController {
if (mCustomTitleView != null) {
// Add the custom title view directly to the topPanel layout
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
topPanel.addView(mCustomTitleView, lp);
@@ -460,8 +460,8 @@ public class AlertController {
if (mListView != null) {
contentPanel.removeView(mWindow.findViewById(R.id.scrollView));
contentPanel.addView(mListView,
- new LinearLayout.LayoutParams(FILL_PARENT, FILL_PARENT));
- contentPanel.setLayoutParams(new LinearLayout.LayoutParams(FILL_PARENT, 0, 1.0f));
+ new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f));
} else {
contentPanel.setVisibility(View.GONE);
}
@@ -469,7 +469,6 @@ public class AlertController {
}
private boolean setupButtons() {
- View defaultButton = null;
int BIT_BUTTON_POSITIVE = 1;
int BIT_BUTTON_NEGATIVE = 2;
int BIT_BUTTON_NEUTRAL = 4;
@@ -482,7 +481,6 @@ public class AlertController {
} else {
mButtonPositive.setText(mButtonPositiveText);
mButtonPositive.setVisibility(View.VISIBLE);
- defaultButton = mButtonPositive;
whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
}
@@ -495,9 +493,6 @@ public class AlertController {
mButtonNegative.setText(mButtonNegativeText);
mButtonNegative.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNegative;
- }
whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
}
@@ -510,9 +505,6 @@ public class AlertController {
mButtonNeutral.setText(mButtonNeutralText);
mButtonNeutral.setVisibility(View.VISIBLE);
- if (defaultButton == null) {
- defaultButton = mButtonNeutral;
- }
whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
}
@@ -565,8 +557,6 @@ public class AlertController {
R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
int bottomMedium = a.getResourceId(
R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);
- int centerMedium = a.getResourceId(
- R.styleable.AlertDialog_centerMedium, R.drawable.popup_center_medium);
/*
* We now set the background of all of the sections of the alert.
@@ -596,7 +586,7 @@ public class AlertController {
*/
views[pos] = (contentPanel.getVisibility() == View.GONE)
? null : contentPanel;
- light[pos] = mListView == null ? false : true;
+ light[pos] = mListView != null;
pos++;
if (customPanel != null) {
views[pos] = customPanel;
@@ -657,8 +647,8 @@ public class AlertController {
ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel);
parent.removeView(buttonPanel);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
- AbsListView.LayoutParams.FILL_PARENT,
- AbsListView.LayoutParams.FILL_PARENT);
+ AbsListView.LayoutParams.MATCH_PARENT,
+ AbsListView.LayoutParams.MATCH_PARENT);
buttonPanel.setLayoutParams(params);
mListView.addFooterView(buttonPanel);
*/
diff --git a/core/java/com/android/internal/app/DisableCarModeActivity.java b/core/java/com/android/internal/app/DisableCarModeActivity.java
new file mode 100644
index 0000000..7943c61
--- /dev/null
+++ b/core/java/com/android/internal/app/DisableCarModeActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.app.IUiModeManager;
+import android.app.UiModeManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+public class DisableCarModeActivity extends Activity {
+ private static final String TAG = "DisableCarModeActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ IUiModeManager uiModeManager = IUiModeManager.Stub.asInterface(
+ ServiceManager.getService("uimode"));
+ uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_GO_HOME);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to disable car mode", e);
+ }
+ finish();
+ }
+
+}
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 000f6c4..7e9bbd1 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -24,7 +24,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IMountService;
+import android.os.storage.IMountService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -102,7 +102,7 @@ public class ExternalMediaFormatActivity extends AlertActivity implements Dialog
.getService("mount"));
if (mountService != null) {
try {
- mountService.formatMedia(Environment.getExternalStorageDirectory().toString());
+ mountService.formatVolume(Environment.getExternalStorageDirectory().toString());
} catch (RemoteException e) {
}
}
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
new file mode 100755
index 0000000..0f817b7
--- /dev/null
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.content.pm.PackageInfoLite;
+
+interface IMediaContainerService {
+ String copyResourceToContainer(in Uri packageURI,
+ String containerId,
+ 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);
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 98fb236..24818a8 100755
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -24,7 +24,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IMountService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7466cc4..215e9ae 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -133,31 +133,39 @@ public class ResolverActivity extends AlertActivity implements
filter = null;
}
}
- } else if (data != null && data.getScheme() != null) {
- filter.addDataScheme(data.getScheme());
-
- // Look through the resolved filter to determine which part
- // of it matched the original Intent.
- Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
- if (aIt != null) {
- while (aIt.hasNext()) {
- IntentFilter.AuthorityEntry a = aIt.next();
- if (a.match(data) >= 0) {
- int port = a.getPort();
- filter.addDataAuthority(a.getHost(),
- port >= 0 ? Integer.toString(port) : null);
- break;
+ }
+ if (data != null && data.getScheme() != null) {
+ // We need the data specification if there was no type,
+ // OR if the scheme is not one of our magical "file:"
+ // or "content:" schemes (see IntentFilter for the reason).
+ if (cat != IntentFilter.MATCH_CATEGORY_TYPE
+ || (!"file".equals(data.getScheme())
+ && !"content".equals(data.getScheme()))) {
+ filter.addDataScheme(data.getScheme());
+
+ // Look through the resolved filter to determine which part
+ // of it matched the original Intent.
+ Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
+ if (aIt != null) {
+ while (aIt.hasNext()) {
+ IntentFilter.AuthorityEntry a = aIt.next();
+ if (a.match(data) >= 0) {
+ int port = a.getPort();
+ filter.addDataAuthority(a.getHost(),
+ port >= 0 ? Integer.toString(port) : null);
+ break;
+ }
}
}
- }
- Iterator<PatternMatcher> pIt = ri.filter.pathsIterator();
- if (pIt != null) {
- String path = data.getPath();
- while (path != null && pIt.hasNext()) {
- PatternMatcher p = pIt.next();
- if (p.match(path)) {
- filter.addDataPath(p.getPath(), p.getType());
- break;
+ Iterator<PatternMatcher> pIt = ri.filter.pathsIterator();
+ if (pIt != null) {
+ String path = data.getPath();
+ while (path != null && pIt.hasNext()) {
+ PatternMatcher p = pIt.next();
+ if (p.match(path)) {
+ filter.addDataPath(p.getPath(), p.getType());
+ break;
+ }
}
}
}
diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java
index 5a0fea3..ddddabe 100644
--- a/core/java/com/android/internal/app/RingtonePickerActivity.java
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -276,6 +276,7 @@ public final class RingtonePickerActivity extends AlertActivity implements
public void run() {
if (mSampleRingtonePos == mSilentPos) {
+ mRingtoneManager.stopPreviousRingtone();
return;
}
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index c110f95..83614a8 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -28,11 +28,14 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.Power;
+import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.IMountService;
+import android.os.Vibrator;
+import android.os.storage.IMountService;
+import android.os.storage.IMountShutdownObserver;
import com.android.internal.telephony.ITelephony;
import android.util.Log;
@@ -45,28 +48,38 @@ public final class ShutdownThread extends Thread {
private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
// maximum time we wait for the shutdown broadcast before going on.
private static final int MAX_BROADCAST_TIME = 10*1000;
+ private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
+
+ // length of vibration before shutting down
+ private static final int SHUTDOWN_VIBRATE_MS = 500;
// state tracking
private static Object sIsStartedGuard = new Object();
private static boolean sIsStarted = false;
+ private static boolean mReboot;
+ private static String mRebootReason;
+
// static instance of this thread
private static final ShutdownThread sInstance = new ShutdownThread();
- private final Object mBroadcastDoneSync = new Object();
- private boolean mBroadcastDone;
+ private final Object mActionDoneSync = new Object();
+ private boolean mActionDone;
private Context mContext;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mWakeLock;
private Handler mHandler;
private ShutdownThread() {
}
- /**
+ /**
* Request a clean shutdown, waiting for subsystems to clean up their
* state etc. Must be called from a Looper thread in which its UI
* is shown.
- *
+ *
* @param context Context used to display the shutdown progress dialog.
+ * @param confirm true if user confirmation is needed before shutting down.
*/
public static void shutdown(final Context context, boolean confirm) {
// ensure that only one thread is trying to power down.
@@ -103,6 +116,21 @@ public final class ShutdownThread extends Thread {
}
}
+ /**
+ * Request a clean shutdown, waiting for subsystems to clean up their
+ * state etc. Must be called from a Looper thread in which its UI
+ * is shown.
+ *
+ * @param context Context used to display the shutdown progress dialog.
+ * @param reason code to pass to the kernel (e.g. "recovery"), or null.
+ * @param confirm true if user confirmation is needed before shutting down.
+ */
+ public static void reboot(final Context context, String reason, boolean confirm) {
+ mReboot = true;
+ mRebootReason = reason;
+ shutdown(context, confirm);
+ }
+
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
sIsStarted = true;
@@ -125,18 +153,30 @@ public final class ShutdownThread extends Thread {
// start the thread that initiates shutdown
sInstance.mContext = context;
+ sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ sInstance.mWakeLock = null;
+ if (sInstance.mPowerManager.isScreenOn()) {
+ try {
+ sInstance.mWakeLock = sInstance.mPowerManager.newWakeLock(
+ PowerManager.FULL_WAKE_LOCK, "Shutdown");
+ sInstance.mWakeLock.acquire();
+ } catch (SecurityException e) {
+ Log.w(TAG, "No permission to acquire wake lock", e);
+ sInstance.mWakeLock = null;
+ }
+ }
sInstance.mHandler = new Handler() {
};
sInstance.start();
}
- void broadcastDone() {
- synchronized (mBroadcastDoneSync) {
- mBroadcastDone = true;
- mBroadcastDoneSync.notifyAll();
+ void actionDone() {
+ synchronized (mActionDoneSync) {
+ mActionDone = true;
+ mActionDoneSync.notifyAll();
}
}
-
+
/**
* Makes sure we handle the shutdown gracefully.
* Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
@@ -148,27 +188,27 @@ public final class ShutdownThread extends Thread {
BroadcastReceiver br = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
// We don't allow apps to cancel this, so ignore the result.
- broadcastDone();
+ actionDone();
}
};
Log.i(TAG, "Sending shutdown broadcast...");
// First send the high-level shut down broadcast.
- mBroadcastDone = false;
+ mActionDone = false;
mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
br, mHandler, 0, null, null);
final long endTime = System.currentTimeMillis() + MAX_BROADCAST_TIME;
- synchronized (mBroadcastDoneSync) {
- while (!mBroadcastDone) {
+ synchronized (mActionDoneSync) {
+ while (!mActionDone) {
long delay = endTime - System.currentTimeMillis();
if (delay <= 0) {
Log.w(TAG, "Shutdown broadcast timed out");
break;
}
try {
- mBroadcastDoneSync.wait(delay);
+ mActionDoneSync.wait(delay);
} catch (InterruptedException e) {
}
}
@@ -247,17 +287,59 @@ public final class ShutdownThread extends Thread {
}
// Shutdown MountService to ensure media is in a safe state
- try {
- if (mount != null) {
- mount.shutdown();
- } else {
- Log.w(TAG, "MountService unavailable for shutdown");
+ IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
+ public void onShutDownComplete(int statusCode) throws RemoteException {
+ Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
+ actionDone();
+ }
+ };
+
+ Log.i(TAG, "Shutting down MountService");
+ // Set initial variables and time out time.
+ mActionDone = false;
+ final long endShutTime = System.currentTimeMillis() + MAX_SHUTDOWN_WAIT_TIME;
+ synchronized (mActionDoneSync) {
+ try {
+ if (mount != null) {
+ mount.shutdown(observer);
+ } else {
+ Log.w(TAG, "MountService unavailable for shutdown");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception during MountService shutdown", e);
+ }
+ while (!mActionDone) {
+ long delay = endShutTime - System.currentTimeMillis();
+ if (delay <= 0) {
+ Log.w(TAG, "Shutdown wait timed out");
+ break;
+ }
+ try {
+ mActionDoneSync.wait(delay);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ if (mReboot) {
+ Log.i(TAG, "Rebooting, reason: " + mRebootReason);
+ try {
+ Power.reboot(mRebootReason);
+ } catch (Exception e) {
+ Log.e(TAG, "Reboot failed, will attempt shutdown instead", e);
+ }
+ } else if (SHUTDOWN_VIBRATE_MS > 0) {
+ // vibrate before shutting down
+ Vibrator vibrator = new Vibrator();
+ vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
+ // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
+ try {
+ Thread.sleep(SHUTDOWN_VIBRATE_MS);
+ } catch (InterruptedException e) {
}
- } catch (Exception e) {
- Log.e(TAG, "Exception during MountService shutdown", e);
}
- //shutdown power
+ // Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
Power.shutdown();
}
diff --git a/core/java/com/android/internal/app/UsbStorageActivity.java b/core/java/com/android/internal/app/UsbStorageActivity.java
deleted file mode 100644
index b8a2136..0000000
--- a/core/java/com/android/internal/app/UsbStorageActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.app;
-
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IMountService;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.widget.Toast;
-
-/**
- * This activity is shown to the user for him/her to enable USB mass storage
- * on-demand (that is, when the USB cable is connected). It uses the alert
- * dialog style. It will be launched from a notification.
- */
-public class UsbStorageActivity extends AlertActivity implements DialogInterface.OnClickListener {
-
- private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
-
- /** Used to detect when the USB cable is unplugged, so we can call finish() */
- private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
- handleBatteryChanged(intent);
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Set up the "dialog"
- final AlertController.AlertParams p = mAlertParams;
- p.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
- p.mTitle = getString(com.android.internal.R.string.usb_storage_title);
- p.mMessage = getString(com.android.internal.R.string.usb_storage_message);
- p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_button_mount);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_button_unmount);
- p.mNegativeButtonListener = this;
- setupAlert();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- unregisterReceiver(mBatteryReceiver);
- }
-
- /**
- * {@inheritDoc}
- */
- public void onClick(DialogInterface dialog, int which) {
-
- if (which == POSITIVE_BUTTON) {
- mountAsUsbStorage();
- }
-
- // No matter what, finish the activity
- finish();
- }
-
- private void mountAsUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showSharingError();
- return;
- }
-
- try {
- mountService.setMassStorageEnabled(true);
- } catch (RemoteException e) {
- showSharingError();
- return;
- }
- }
-
- private void handleBatteryChanged(Intent intent) {
- int pluggedType = intent.getIntExtra("plugged", 0);
- if (pluggedType == 0) {
- // It was disconnected from the plug, so finish
- finish();
- }
- }
-
- private void showSharingError() {
- Toast.makeText(this, com.android.internal.R.string.usb_storage_error_message,
- Toast.LENGTH_LONG).show();
- }
-
-}
diff --git a/core/java/com/android/internal/app/UsbStorageStopActivity.java b/core/java/com/android/internal/app/UsbStorageStopActivity.java
deleted file mode 100644
index 557a523..0000000
--- a/core/java/com/android/internal/app/UsbStorageStopActivity.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.app;
-
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IMountService;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.widget.Toast;
-
-/**
- * This activity is shown to the user for him/her to disable USB mass storage.
- * It uses the alert dialog style. It will be launched from a notification.
- */
-public class UsbStorageStopActivity extends AlertActivity implements DialogInterface.OnClickListener {
-
- private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
-
- /** Used to detect when the USB cable is unplugged, so we can call finish() */
- private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
- handleBatteryChanged(intent);
- }
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Set up the "dialog"
- final AlertController.AlertParams p = mAlertParams;
- p.mIconId = com.android.internal.R.drawable.ic_dialog_alert;
- p.mTitle = getString(com.android.internal.R.string.usb_storage_stop_title);
- p.mMessage = getString(com.android.internal.R.string.usb_storage_stop_message);
- p.mPositiveButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_mount);
- p.mPositiveButtonListener = this;
- p.mNegativeButtonText = getString(com.android.internal.R.string.usb_storage_stop_button_unmount);
- p.mNegativeButtonListener = this;
- setupAlert();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- unregisterReceiver(mBatteryReceiver);
- }
-
- /**
- * {@inheritDoc}
- */
- public void onClick(DialogInterface dialog, int which) {
-
- if (which == POSITIVE_BUTTON) {
- stopUsbStorage();
- }
-
- // No matter what, finish the activity
- finish();
- }
-
- private void stopUsbStorage() {
- IMountService mountService = IMountService.Stub.asInterface(ServiceManager
- .getService("mount"));
- if (mountService == null) {
- showStoppingError();
- return;
- }
-
- try {
- mountService.setMassStorageEnabled(false);
- } catch (RemoteException e) {
- showStoppingError();
- return;
- }
- }
-
- private void handleBatteryChanged(Intent intent) {
- int pluggedType = intent.getIntExtra("plugged", 0);
- if (pluggedType == 0) {
- // It was disconnected from the plug, so finish
- finish();
- }
- }
-
- private void showStoppingError() {
- Toast.makeText(this, com.android.internal.R.string.usb_storage_stop_error_message,
- Toast.LENGTH_LONG).show();
- }
-
-}
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index a830ebd..b535fc1 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,7 +16,7 @@
package com.android.internal.backup;
-import android.backup.RestoreSet;
+import android.app.backup.RestoreSet;
import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
@@ -102,7 +102,7 @@ interface IBackupTransport {
int finishBackup();
/**
- * Get the set of backups currently available over this transport.
+ * Get the set of all backups currently available over this transport.
*
* @return Descriptions of the set of restore images available for this device,
* or null if an error occurred (the attempt should be rescheduled).
@@ -110,11 +110,22 @@ interface IBackupTransport {
RestoreSet[] getAvailableRestoreSets();
/**
+ * Get the identifying token of the backup set currently being stored from
+ * this device. This is used in the case of applications wishing to restore
+ * their last-known-good data.
+ *
+ * @return A token that can be passed to {@link #startRestore}, or 0 if there
+ * is no backup set available corresponding to the current device state.
+ */
+ long getCurrentRestoreSet();
+
+ /**
* Start restoring application data from backup. After calling this function,
* alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
* to walk through the actual application data.
*
- * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+ * or {@link #getCurrentRestoreSet}.
* @param packages List of applications to restore (if data is available).
* Application data will be restored in the order given.
* @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index 12bc5a8..b436363 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -1,8 +1,24 @@
+/*
+ * 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 com.android.internal.backup;
-import android.backup.BackupDataInput;
-import android.backup.BackupDataOutput;
-import android.backup.RestoreSet;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.RestoreSet;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -33,6 +49,9 @@ public class LocalTransport extends IBackupTransport.Stub {
private static final String TRANSPORT_DIR_NAME
= "com.android.internal.backup.LocalTransport";
+ // The single hardcoded restore set always has the same (nonzero!) token
+ private static final long RESTORE_TOKEN = 1;
+
private Context mContext;
private PackageManager mPackageManager;
private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
@@ -86,6 +105,9 @@ public class LocalTransport extends IBackupTransport.Stub {
+ " key64=" + base64Key);
if (dataSize >= 0) {
+ if (entityFile.exists()) {
+ entityFile.delete();
+ }
FileOutputStream entity = new FileOutputStream(entityFile);
if (dataSize > bufSize) {
@@ -149,11 +171,16 @@ public class LocalTransport extends IBackupTransport.Stub {
// Restore handling
public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
// one hardcoded restore set
- RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
+ RestoreSet set = new RestoreSet("Local disk image", "flash", RESTORE_TOKEN);
RestoreSet[] array = { set };
return array;
}
+ public long getCurrentRestoreSet() {
+ // The hardcoded restore set always has the same token
+ return RESTORE_TOKEN;
+ }
+
public int startRestore(long token, PackageInfo[] packages) {
if (DEBUG) Log.v(TAG, "start restore " + token);
mRestorePackages = packages;
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
new file mode 100644
index 0000000..4d0a9e0
--- /dev/null
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -0,0 +1,192 @@
+/*
+ * 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 com.android.internal.content;
+
+import android.os.storage.IMountService;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.storage.StorageResultCode;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * Constants used internally between the PackageManager
+ * and media container service transports.
+ * Some utility methods to invoke MountService api.
+ */
+public class PackageHelper {
+ public static final int RECOMMEND_INSTALL_INTERNAL = 1;
+ public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
+ public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
+ public static final int RECOMMEND_FAILED_INVALID_APK = -2;
+ public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
+ public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
+ public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
+ private static final boolean localLOGV = true;
+ private static final String TAG = "PackageHelper";
+ // App installation location settings values
+ public static final int APP_INSTALL_AUTO = 0;
+ public static final int APP_INSTALL_INTERNAL = 1;
+ public static final int APP_INSTALL_EXTERNAL = 2;
+
+ public static IMountService getMountService() {
+ IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ } else {
+ Log.e(TAG, "Can't get mount service");
+ }
+ return null;
+ }
+
+ public static String createSdDir(File tmpPackageFile, String cid,
+ String sdEncKey, int uid) {
+ // Create mount point via MountService
+ IMountService mountService = getMountService();
+ long len = tmpPackageFile.length();
+ int mbLen = (int) (len >> 20);
+ if ((len - (mbLen * 1024 * 1024)) > 0) {
+ mbLen++;
+ }
+ // Add buffer size
+ mbLen++;
+ if (localLOGV) Log.i(TAG, "Size of container " + mbLen + " MB " + len + " bytes");
+
+ try {
+ int rc = mountService.createSecureContainer(
+ cid, mbLen, "fat", sdEncKey, uid);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, "Failed to create secure container " + cid);
+ return null;
+ }
+ String cachePath = mountService.getSecureContainerPath(cid);
+ if (localLOGV) Log.i(TAG, "Created secure container " + cid +
+ " at " + cachePath);
+ return cachePath;
+ } catch (RemoteException e) {
+ Log.e(TAG, "MountService running?");
+ }
+ return null;
+ }
+
+ public static String mountSdDir(String cid, String key, int ownerUid) {
+ try {
+ int rc = getMountService().mountSecureContainer(cid, key, ownerUid);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
+ return null;
+ }
+ return getMountService().getSecureContainerPath(cid);
+ } catch (RemoteException e) {
+ Log.e(TAG, "MountService running?");
+ }
+ return null;
+ }
+
+ public static boolean unMountSdDir(String cid) {
+ try {
+ int rc = getMountService().unmountSecureContainer(cid, true);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "MountService running?");
+ }
+ return false;
+ }
+
+ public static boolean renameSdDir(String oldId, String newId) {
+ try {
+ int rc = getMountService().renameSecureContainer(oldId, newId);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.e(TAG, "Failed to rename " + oldId + " to " +
+ newId + "with rc " + rc);
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.i(TAG, "Failed ot rename " + oldId + " to " + newId +
+ " with exception : " + e);
+ }
+ return false;
+ }
+
+ public static String getSdDir(String cid) {
+ try {
+ return getMountService().getSecureContainerPath(cid);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get container path for " + cid +
+ " with exception " + e);
+ }
+ return null;
+ }
+
+ public static boolean finalizeSdDir(String cid) {
+ try {
+ int rc = getMountService().finalizeSecureContainer(cid);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.i(TAG, "Failed to finalize container " + cid);
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to finalize container " + cid +
+ " with exception " + e);
+ }
+ return false;
+ }
+
+ public static boolean destroySdDir(String cid) {
+ try {
+ if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
+ int rc = getMountService().destroySecureContainer(cid, true);
+ if (rc != StorageResultCode.OperationSucceeded) {
+ Log.i(TAG, "Failed to destroy container " + cid);
+ return false;
+ }
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to destroy container " + cid +
+ " with exception " + e);
+ }
+ return false;
+ }
+
+ public static String[] getSecureContainerList() {
+ try {
+ return getMountService().getSecureContainerList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get secure container list with exception" +
+ e);
+ }
+ return null;
+ }
+
+ public static boolean isContainerMounted(String cid) {
+ try {
+ return getMountService().isSecureContainerMounted(cid);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to find out if container " + cid + " mounted");
+ }
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
new file mode 100644
index 0000000..32d8641
--- /dev/null
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.content;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+
+import java.util.HashSet;
+
+/**
+ * Helper class for monitoring the state of packages: adding, removing,
+ * updating, and disappearing and reappearing on the SD card.
+ */
+public abstract class PackageMonitor extends android.content.BroadcastReceiver {
+ static final IntentFilter sPackageFilt = new IntentFilter();
+ static final IntentFilter sNonDataFilt = new IntentFilter();
+ static final IntentFilter sExternalFilt = new IntentFilter();
+
+ static {
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ sPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+ sPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ sPackageFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sPackageFilt.addDataScheme("package");
+ sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ }
+
+ final HashSet<String> mUpdatingPackages = new HashSet<String>();
+
+ Context mRegisteredContext;
+ String[] mDisappearingPackages;
+ String[] mAppearingPackages;
+ String[] mModifiedPackages;
+ int mChangeType;
+ boolean mSomePackagesChanged;
+
+ String[] mTempArray = new String[1];
+
+ public void register(Context context, boolean externalStorage) {
+ if (mRegisteredContext != null) {
+ throw new IllegalStateException("Already registered");
+ }
+ mRegisteredContext = context;
+ context.registerReceiver(this, sPackageFilt);
+ context.registerReceiver(this, sNonDataFilt);
+ if (externalStorage) {
+ context.registerReceiver(this, sExternalFilt);
+ }
+ }
+
+ public void unregister() {
+ if (mRegisteredContext == null) {
+ throw new IllegalStateException("Not registered");
+ }
+ mRegisteredContext.unregisterReceiver(this);
+ mRegisteredContext = null;
+ }
+
+ //not yet implemented
+ boolean isPackageUpdating(String packageName) {
+ synchronized (mUpdatingPackages) {
+ return mUpdatingPackages.contains(packageName);
+ }
+ }
+
+ public void onBeginPackageChanges() {
+ }
+
+ public void onPackageAdded(String packageName, int uid) {
+ }
+
+ public void onPackageRemoved(String packageName, int uid) {
+ }
+
+ public void onPackageUpdateStarted(String packageName, int uid) {
+ }
+
+ public void onPackageUpdateFinished(String packageName, int uid) {
+ }
+
+ public void onPackageChanged(String packageName, int uid, String[] components) {
+ }
+
+ public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+ return false;
+ }
+
+ public void onUidRemoved(int uid) {
+ }
+
+ public void onPackagesAvailable(String[] packages) {
+ }
+
+ public void onPackagesUnavailable(String[] packages) {
+ }
+
+ public static final int PACKAGE_UNCHANGED = 0;
+ public static final int PACKAGE_UPDATING = 1;
+ public static final int PACKAGE_TEMPORARY_CHANGE = 2;
+ public static final int PACKAGE_PERMANENT_CHANGE = 3;
+
+ public void onPackageDisappeared(String packageName, int reason) {
+ }
+
+ public void onPackageAppeared(String packageName, int reason) {
+ }
+
+ public void onPackageModified(String packageName) {
+ }
+
+ public boolean didSomePackagesChange() {
+ return mSomePackagesChanged;
+ }
+
+ public int isPackageAppearing(String packageName) {
+ if (mAppearingPackages != null) {
+ for (int i=mAppearingPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mAppearingPackages[i])) {
+ return mChangeType;
+ }
+ }
+ }
+ return PACKAGE_UNCHANGED;
+ }
+
+ public boolean anyPackagesAppearing() {
+ return mAppearingPackages != null;
+ }
+
+ public int isPackageDisappearing(String packageName) {
+ if (mDisappearingPackages != null) {
+ for (int i=mDisappearingPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mDisappearingPackages[i])) {
+ return mChangeType;
+ }
+ }
+ }
+ return PACKAGE_UNCHANGED;
+ }
+
+ public boolean anyPackagesDisappearing() {
+ return mDisappearingPackages != null;
+ }
+
+ public boolean isPackageModified(String packageName) {
+ if (mModifiedPackages != null) {
+ for (int i=mModifiedPackages.length-1; i>=0; i--) {
+ if (packageName.equals(mModifiedPackages[i])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void onSomePackagesChanged() {
+ }
+
+ public void onFinishPackageChanges() {
+ }
+
+ String getPackageName(Intent intent) {
+ Uri uri = intent.getData();
+ String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+ return pkg;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onBeginPackageChanges();
+
+ mDisappearingPackages = mAppearingPackages = null;
+ mSomePackagesChanged = false;
+
+ String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ // We consider something to have changed regardless of whether
+ // this is just an update, because the update is now finished
+ // and the contents of the package may have changed.
+ mSomePackagesChanged = true;
+ if (pkg != null) {
+ mAppearingPackages = mTempArray;
+ mTempArray[0] = pkg;
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mModifiedPackages = mTempArray;
+ mChangeType = PACKAGE_UPDATING;
+ onPackageUpdateFinished(pkg, uid);
+ onPackageModified(pkg);
+ } else {
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ onPackageAdded(pkg, uid);
+ }
+ onPackageAppeared(pkg, mChangeType);
+ if (mChangeType == PACKAGE_UPDATING) {
+ synchronized (mUpdatingPackages) {
+ mUpdatingPackages.remove(pkg);
+ }
+ }
+ }
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ if (pkg != null) {
+ mDisappearingPackages = mTempArray;
+ mTempArray[0] = pkg;
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ mChangeType = PACKAGE_UPDATING;
+ synchronized (mUpdatingPackages) {
+ //not used for now
+ //mUpdatingPackages.add(pkg);
+ }
+ onPackageUpdateStarted(pkg, uid);
+ } else {
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ // We only consider something to have changed if this is
+ // not a replace; for a replace, we just need to consider
+ // it when it is re-added.
+ mSomePackagesChanged = true;
+ onPackageRemoved(pkg, uid);
+ }
+ onPackageDisappeared(pkg, mChangeType);
+ }
+ } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+ String pkg = getPackageName(intent);
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+ String[] components = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (pkg != null) {
+ mModifiedPackages = mTempArray;
+ mTempArray[0] = pkg;
+ onPackageChanged(pkg, uid, components);
+ // XXX Don't want this to always cause mSomePackagesChanged,
+ // since it can happen a fair amount.
+ onPackageModified(pkg);
+ }
+ } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
+ mDisappearingPackages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ boolean canRestart = onHandleForceStop(intent,
+ mDisappearingPackages,
+ intent.getIntExtra(Intent.EXTRA_UID, 0), false);
+ if (canRestart) setResultCode(Activity.RESULT_OK);
+ } else if (Intent.ACTION_PACKAGE_RESTARTED.equals(action)) {
+ mDisappearingPackages = new String[] {getPackageName(intent)};
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ onHandleForceStop(intent, mDisappearingPackages,
+ intent.getIntExtra(Intent.EXTRA_UID, 0), true);
+ } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
+ onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mAppearingPackages = pkgList;
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mSomePackagesChanged = true;
+ if (pkgList != null) {
+ onPackagesAvailable(pkgList);
+ for (int i=0; i<pkgList.length; i++) {
+ onPackageAppeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ }
+ }
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ mDisappearingPackages = pkgList;
+ mChangeType = PACKAGE_TEMPORARY_CHANGE;
+ mSomePackagesChanged = true;
+ if (pkgList != null) {
+ onPackagesUnavailable(pkgList);
+ for (int i=0; i<pkgList.length; i++) {
+ onPackageDisappeared(pkgList[i], PACKAGE_TEMPORARY_CHANGE);
+ }
+ }
+ }
+
+ if (mSomePackagesChanged) {
+ onSomePackagesChanged();
+ }
+
+ onFinishPackageChanges();
+ }
+}
diff --git a/core/java/com/android/internal/content/SelectionBuilder.java b/core/java/com/android/internal/content/SelectionBuilder.java
new file mode 100644
index 0000000..b194756
--- /dev/null
+++ b/core/java/com/android/internal/content/SelectionBuilder.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.content;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Helper for building selection clauses for {@link SQLiteDatabase}. Each
+ * appended clause is combined using {@code AND}. This class is <em>not</em>
+ * thread safe.
+ *
+ * @hide
+ */
+public class SelectionBuilder {
+ private StringBuilder mSelection = new StringBuilder();
+ private ArrayList<String> mSelectionArgs = new ArrayList<String>();
+
+ /**
+ * Reset any internal state, allowing this builder to be recycled.
+ */
+ public SelectionBuilder reset() {
+ mSelection.setLength(0);
+ mSelectionArgs.clear();
+ return this;
+ }
+
+ /**
+ * Append the given selection clause to the internal state. Each clause is
+ * surrounded with parenthesis and combined using {@code AND}.
+ */
+ public SelectionBuilder append(String selection, Object... selectionArgs) {
+ if (TextUtils.isEmpty(selection)) {
+ if (selectionArgs != null && selectionArgs.length > 0) {
+ throw new IllegalArgumentException(
+ "Valid selection required when including arguments");
+ }
+
+ // Shortcut when clause is empty
+ return this;
+ }
+
+ if (mSelection.length() > 0) {
+ mSelection.append(" AND ");
+ }
+
+ mSelection.append("(").append(selection).append(")");
+ if (selectionArgs != null) {
+ for (Object arg : selectionArgs) {
+ // TODO: switch to storing direct Object instances once
+ // http://b/2464440 is fixed
+ mSelectionArgs.add(String.valueOf(arg));
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Return selection string for current internal state.
+ *
+ * @see #getSelectionArgs()
+ */
+ public String getSelection() {
+ return mSelection.toString();
+ }
+
+ /**
+ * Return selection arguments for current internal state.
+ *
+ * @see #getSelection()
+ */
+ public String[] getSelectionArgs() {
+ return mSelectionArgs.toArray(new String[mSelectionArgs.size()]);
+ }
+
+ /**
+ * Execute query using the current internal state as {@code WHERE} clause.
+ * Missing arguments as treated as {@code null}.
+ */
+ public Cursor query(SQLiteDatabase db, String table, String[] columns, String orderBy) {
+ return query(db, table, columns, null, null, orderBy, null);
+ }
+
+ /**
+ * Execute query using the current internal state as {@code WHERE} clause.
+ */
+ public Cursor query(SQLiteDatabase db, String table, String[] columns, String groupBy,
+ String having, String orderBy, String limit) {
+ return db.query(table, columns, getSelection(), getSelectionArgs(), groupBy, having,
+ orderBy, limit);
+ }
+
+ /**
+ * Execute update using the current internal state as {@code WHERE} clause.
+ */
+ public int update(SQLiteDatabase db, String table, ContentValues values) {
+ return db.update(table, values, getSelection(), getSelectionArgs());
+ }
+
+ /**
+ * Execute delete using the current internal state as {@code WHERE} clause.
+ */
+ public int delete(SQLiteDatabase db, String table) {
+ return db.delete(table, getSelection(), getSelectionArgs());
+ }
+}
diff --git a/core/java/com/android/internal/content/SyncStateContentProviderHelper.java b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java
index cd6a9a1..274082c 100644
--- a/core/java/com/android/internal/content/SyncStateContentProviderHelper.java
+++ b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java
@@ -50,6 +50,11 @@ public class SyncStateContentProviderHelper {
public static final String PATH = "syncstate";
+ private static final String QUERY_COUNT_SYNC_STATE_ROWS =
+ "SELECT count(*)"
+ + " FROM " + SYNC_STATE_TABLE
+ + " WHERE " + SyncStateContract.Columns._ID + "=?";
+
public void createDatabase(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_TABLE);
db.execSQL("CREATE TABLE " + SYNC_STATE_TABLE + " ("
@@ -96,11 +101,17 @@ public class SyncStateContentProviderHelper {
return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
}
- public void update(SQLiteDatabase db, long rowId, Object data) {
+ public int update(SQLiteDatabase db, long rowId, Object data) {
+ if (DatabaseUtils.longForQuery(db, QUERY_COUNT_SYNC_STATE_ROWS,
+ new String[]{Long.toString(rowId)}) < 1) {
+ return 0;
+ }
db.execSQL("UPDATE " + SYNC_STATE_TABLE
+ " SET " + SyncStateContract.Columns.DATA + "=?"
+ " WHERE " + SyncStateContract.Columns._ID + "=" + rowId,
new Object[]{data});
+ // assume a row was modified since we know it exists
+ return 1;
}
public void onAccountsChanged(SQLiteDatabase db, Account[] accounts) {
diff --git a/core/java/com/android/internal/database/ArrayListCursor.java b/core/java/com/android/internal/database/ArrayListCursor.java
deleted file mode 100644
index 2e1d8f1..0000000
--- a/core/java/com/android/internal/database/ArrayListCursor.java
+++ /dev/null
@@ -1,171 +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 com.android.internal.database;
-
-import android.database.AbstractCursor;
-import android.database.CursorWindow;
-
-import java.lang.System;
-import java.util.ArrayList;
-
-/**
- * A convenience class that presents a two-dimensional ArrayList
- * as a Cursor.
- */
-public class ArrayListCursor extends AbstractCursor {
- private String[] mColumnNames;
- private ArrayList<Object>[] mRows;
-
- @SuppressWarnings({"unchecked"})
- public ArrayListCursor(String[] columnNames, ArrayList<ArrayList> rows) {
- int colCount = columnNames.length;
- boolean foundID = false;
- // Add an _id column if not in columnNames
- for (int i = 0; i < colCount; ++i) {
- if (columnNames[i].compareToIgnoreCase("_id") == 0) {
- mColumnNames = columnNames;
- foundID = true;
- break;
- }
- }
-
- if (!foundID) {
- mColumnNames = new String[colCount + 1];
- System.arraycopy(columnNames, 0, mColumnNames, 0, columnNames.length);
- mColumnNames[colCount] = "_id";
- }
-
- int rowCount = rows.size();
- mRows = new ArrayList[rowCount];
-
- for (int i = 0; i < rowCount; ++i) {
- mRows[i] = rows.get(i);
- if (!foundID) {
- mRows[i].add(i);
- }
- }
- }
-
- @Override
- public void fillWindow(int position, CursorWindow window) {
- if (position < 0 || position > getCount()) {
- return;
- }
-
- window.acquireReference();
- try {
- int oldpos = mPos;
- mPos = position - 1;
- window.clear();
- window.setStartPosition(position);
- int columnNum = getColumnCount();
- window.setNumColumns(columnNum);
- while (moveToNext() && window.allocRow()) {
- for (int i = 0; i < columnNum; i++) {
- final Object data = mRows[mPos].get(i);
- if (data != null) {
- if (data instanceof byte[]) {
- byte[] field = (byte[]) data;
- if (!window.putBlob(field, mPos, i)) {
- window.freeLastRow();
- break;
- }
- } else {
- String field = data.toString();
- if (!window.putString(field, mPos, i)) {
- window.freeLastRow();
- break;
- }
- }
- } else {
- if (!window.putNull(mPos, i)) {
- window.freeLastRow();
- break;
- }
- }
- }
- }
-
- mPos = oldpos;
- } catch (IllegalStateException e){
- // simply ignore it
- } finally {
- window.releaseReference();
- }
- }
-
- @Override
- public int getCount() {
- return mRows.length;
- }
-
- @Override
- public boolean deleteRow() {
- return false;
- }
-
- @Override
- public String[] getColumnNames() {
- return mColumnNames;
- }
-
- @Override
- public byte[] getBlob(int columnIndex) {
- return (byte[]) mRows[mPos].get(columnIndex);
- }
-
- @Override
- public String getString(int columnIndex) {
- Object cell = mRows[mPos].get(columnIndex);
- return (cell == null) ? null : cell.toString();
- }
-
- @Override
- public short getShort(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.shortValue();
- }
-
- @Override
- public int getInt(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.intValue();
- }
-
- @Override
- public long getLong(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.longValue();
- }
-
- @Override
- public float getFloat(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.floatValue();
- }
-
- @Override
- public double getDouble(int columnIndex) {
- Number num = (Number) mRows[mPos].get(columnIndex);
- return num.doubleValue();
- }
-
- @Override
- public boolean isNull(int columnIndex) {
- return mRows[mPos].get(columnIndex) == null;
- }
-}
diff --git a/core/java/android/webkit/HttpDateTime.java b/core/java/com/android/internal/http/HttpDateTime.java
index 2f46f2b..8ebd4aa 100644
--- a/core/java/android/webkit/HttpDateTime.java
+++ b/core/java/com/android/internal/http/HttpDateTime.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.webkit;
+package com.android.internal.http;
import android.text.format.Time;
@@ -22,8 +22,9 @@ import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
-/** {@hide} */
+/**
+ * Helper for parsing an HTTP date.
+ */
public final class HttpDateTime {
/*
@@ -50,13 +51,15 @@ public final class HttpDateTime {
* Wdy Mon DD HH:MM:SS YYYY GMT
*
* HH can be H if the first digit is zero.
+ *
+ * Mon can be the full name of the month.
*/
private static final String HTTP_DATE_RFC_REGEXP =
- "([0-9]{1,2})[- ]([A-Za-z]{3,3})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
private static final String HTTP_DATE_ANSIC_REGEXP =
- "[ ]([A-Za-z]{3,3})[ ]+([0-9]{1,2})[ ]"
+ "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
+ "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
/**
@@ -79,7 +82,7 @@ public final class HttpDateTime {
int second;
}
- public static Long parse(String timeString)
+ public static long parse(String timeString)
throws IllegalArgumentException {
int date = 1;
diff --git a/core/java/com/android/internal/logging/AndroidHandler.java b/core/java/com/android/internal/logging/AndroidHandler.java
index c4a1479..12f6a4f 100644
--- a/core/java/com/android/internal/logging/AndroidHandler.java
+++ b/core/java/com/android/internal/logging/AndroidHandler.java
@@ -17,12 +17,16 @@
package com.android.internal.logging;
import android.util.Log;
+import dalvik.system.DalvikLogging;
+import dalvik.system.DalvikLogHandler;
-import java.util.logging.*;
-import java.util.Date;
-import java.text.MessageFormat;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
/**
* Implements a {@link java.util.logging.Logger} handler that writes to the Android log. The
@@ -77,7 +81,7 @@ import java.io.StringWriter;
* </tr>
* </table>
*/
-public class AndroidHandler extends Handler {
+public class AndroidHandler extends Handler implements DalvikLogHandler {
/**
* Holds the formatter for all Android log handlers.
*/
@@ -118,33 +122,13 @@ public class AndroidHandler extends Handler {
@Override
public void publish(LogRecord record) {
- try {
- int level = getAndroidLevel(record.getLevel());
- String tag = record.getLoggerName();
-
- if (tag == null) {
- // Anonymous logger.
- tag = "null";
- } else {
- // Tags must be <= 23 characters.
- int length = tag.length();
- if (length > 23) {
- // Most loggers use the full class name. Try dropping the
- // package.
- int lastPeriod = tag.lastIndexOf(".");
- if (length - lastPeriod - 1 <= 23) {
- tag = tag.substring(lastPeriod + 1);
- } else {
- // Use last 23 chars.
- tag = tag.substring(tag.length() - 23);
- }
- }
- }
-
- if (!Log.isLoggable(tag, level)) {
- return;
- }
+ int level = getAndroidLevel(record.getLevel());
+ String tag = DalvikLogging.loggerNameToTag(record.getLoggerName());
+ if (!Log.isLoggable(tag, level)) {
+ return;
+ }
+ try {
String message = getFormatter().format(record);
Log.println(level, tag, message);
} catch (RuntimeException e) {
@@ -152,12 +136,26 @@ public class AndroidHandler extends Handler {
}
}
+ public void publish(Logger source, String tag, Level level, String message) {
+ // TODO: avoid ducking into native 2x; we aren't saving any formatter calls
+ int priority = getAndroidLevel(level);
+ if (!Log.isLoggable(tag, priority)) {
+ return;
+ }
+
+ try {
+ Log.println(priority, tag, message);
+ } catch (RuntimeException e) {
+ Log.e("AndroidHandler", "Error logging message.", e);
+ }
+ }
+
/**
* Converts a {@link java.util.logging.Logger} logging level into an Android one.
- *
+ *
* @param level The {@link java.util.logging.Logger} logging level.
- *
- * @return The resulting Android logging level.
+ *
+ * @return The resulting Android logging level.
*/
static int getAndroidLevel(Level level) {
int value = level.intValue();
@@ -171,5 +169,4 @@ public class AndroidHandler extends Handler {
return Log.DEBUG;
}
}
-
}
diff --git a/core/java/com/android/internal/net/DNParser.java b/core/java/com/android/internal/net/DNParser.java
new file mode 100644
index 0000000..5254207
--- /dev/null
+++ b/core/java/com/android/internal/net/DNParser.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+
+import android.util.Log;
+
+import java.io.IOException;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A simple distinguished name(DN) parser.
+ *
+ * <p>This class is based on org.apache.harmony.security.x509.DNParser. It's customized to remove
+ * external references which are unnecessary for our requirements.
+ *
+ * <p>This class is only meant for extracting a string value from a DN. e.g. it doesn't support
+ * values in the hex-string style.
+ *
+ * <p>This class is used by {@link DomainNameValidator} only. However, in order to make this
+ * class visible from unit tests, it's made public.
+ *
+ * @hide
+ */
+public final class DNParser {
+ private static final String TAG = "DNParser";
+
+ /** DN to be parsed. */
+ private final String dn;
+
+ // length of distinguished name string
+ private final int length;
+
+ private int pos, beg, end;
+
+ // tmp vars to store positions of the currently parsed item
+ private int cur;
+
+ // distinguished name chars
+ private char[] chars;
+
+ /**
+ * Exception message thrown when we failed to parse DN, which shouldn't happen because we
+ * only handle DNs that {@link X500Principal#getName} returns, which shouldn't be malformed.
+ */
+ private static final String ERROR_PARSE_ERROR = "Failed to parse DN";
+
+ /**
+ * Constructor.
+ *
+ * @param principal - {@link X500Principal} to be parsed
+ */
+ public DNParser(X500Principal principal) {
+ this.dn = principal.getName(X500Principal.RFC2253);
+ this.length = dn.length();
+ }
+
+ // gets next attribute type: (ALPHA 1*keychar) / oid
+ private String nextAT() throws IOException {
+
+ // skip preceding space chars, they can present after
+ // comma or semicolon (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ if (pos == length) {
+ return null; // reached the end of DN
+ }
+
+ // mark the beginning of attribute type
+ beg = pos;
+
+ // attribute type chars
+ pos++;
+ for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+ // we don't follow exact BNF syntax here:
+ // accept any char except space and '='
+ }
+ if (pos >= length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // mark the end of attribute type
+ end = pos;
+
+ // skip trailing space chars between attribute type and '='
+ // (compatibility with RFC 1779)
+ if (chars[pos] == ' ') {
+ for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+ }
+
+ if (chars[pos] != '=' || pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+
+ pos++; //skip '=' char
+
+ // skip space chars between '=' and attribute value
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ // in case of oid attribute type skip its prefix: "oid." or "OID."
+ // (compatibility with RFC 1779)
+ if ((end - beg > 4) && (chars[beg + 3] == '.')
+ && (chars[beg] == 'O' || chars[beg] == 'o')
+ && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+ && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+ beg += 4;
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+ private String quotedAV() throws IOException {
+
+ pos++;
+ beg = pos;
+ end = beg;
+ while (true) {
+
+ if (pos == length) {
+ // unexpected end of DN
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ if (chars[pos] == '"') {
+ // enclosing quotation was found
+ pos++;
+ break;
+ } else if (chars[pos] == '\\') {
+ chars[end] = getEscaped();
+ } else {
+ // shift char: required for string with escaped chars
+ chars[end] = chars[pos];
+ }
+ pos++;
+ end++;
+ }
+
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+
+ return new String(chars, beg, end - beg);
+ }
+
+ // gets hex string attribute value: "#" hexstring
+ private String hexAV() throws IOException {
+
+ if (pos + 4 >= length) {
+ // encoded byte array must be not less then 4 c
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ beg = pos; // store '#' position
+ pos++;
+ while (true) {
+
+ // check for end of attribute value
+ // looks for space and component separators
+ if (pos == length || chars[pos] == '+' || chars[pos] == ','
+ || chars[pos] == ';') {
+ end = pos;
+ break;
+ }
+
+ if (chars[pos] == ' ') {
+ end = pos;
+ pos++;
+ // skip trailing space chars before comma or semicolon.
+ // (compatibility with RFC 1779)
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ }
+ break;
+ } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+ chars[pos] += 32; //to low case
+ }
+
+ pos++;
+ }
+
+ // verify length of hex string
+ // encoded byte array must be not less then 4 and must be even number
+ int hexLen = end - beg; // skip first '#' char
+ if (hexLen < 5 || (hexLen & 1) == 0) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ // get byte encoding from string representation
+ byte[] encoded = new byte[hexLen / 2];
+ for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+ encoded[i] = (byte) getByte(p);
+ }
+
+ return new String(chars, beg, hexLen);
+ }
+
+ // gets string attribute value: *( stringchar / pair )
+ private String escapedAV() throws IOException {
+
+ beg = pos;
+ end = pos;
+ while (true) {
+
+ if (pos >= length) {
+ // the end of DN has been found
+ return new String(chars, beg, end - beg);
+ }
+
+ switch (chars[pos]) {
+ case '+':
+ case ',':
+ case ';':
+ // separator char has beed found
+ return new String(chars, beg, end - beg);
+ case '\\':
+ // escaped char
+ chars[end++] = getEscaped();
+ pos++;
+ break;
+ case ' ':
+ // need to figure out whether space defines
+ // the end of attribute value or not
+ cur = end;
+
+ pos++;
+ chars[end++] = ' ';
+
+ for (; pos < length && chars[pos] == ' '; pos++) {
+ chars[end++] = ' ';
+ }
+ if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+ || chars[pos] == ';') {
+ // separator char or the end of DN has beed found
+ return new String(chars, beg, cur - beg);
+ }
+ break;
+ default:
+ chars[end++] = chars[pos];
+ pos++;
+ }
+ }
+ }
+
+ // returns escaped char
+ private char getEscaped() throws IOException {
+
+ pos++;
+ if (pos == length) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ case '\\':
+ case ',':
+ case '=':
+ case '+':
+ case '<':
+ case '>':
+ case '#':
+ case ';':
+ case ' ':
+ case '*':
+ case '%':
+ case '_':
+ //FIXME: escaping is allowed only for leading or trailing space char
+ return chars[pos];
+ default:
+ // RFC doesn't explicitly say that escaped hex pair is
+ // interpreted as UTF-8 char. It only contains an example of such DN.
+ return getUTF8();
+ }
+ }
+
+ // decodes UTF-8 char
+ // see http://www.unicode.org for UTF-8 bit distribution table
+ private char getUTF8() throws IOException {
+
+ int res = getByte(pos);
+ pos++; //FIXME tmp
+
+ if (res < 128) { // one byte: 0-7F
+ return (char) res;
+ } else if (res >= 192 && res <= 247) {
+
+ int count;
+ if (res <= 223) { // two bytes: C0-DF
+ count = 1;
+ res = res & 0x1F;
+ } else if (res <= 239) { // three bytes: E0-EF
+ count = 2;
+ res = res & 0x0F;
+ } else { // four bytes: F0-F7
+ count = 3;
+ res = res & 0x07;
+ }
+
+ int b;
+ for (int i = 0; i < count; i++) {
+ pos++;
+ if (pos == length || chars[pos] != '\\') {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ pos++;
+
+ b = getByte(pos);
+ pos++; //FIXME tmp
+ if ((b & 0xC0) != 0x80) {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+
+ res = (res << 6) + (b & 0x3F);
+ }
+ return (char) res;
+ } else {
+ return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+ }
+ }
+
+ // Returns byte representation of a char pair
+ // The char pair is composed of DN char in
+ // specified 'position' and the next char
+ // According to BNF syntax:
+ // hexchar = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+ // / "a" / "b" / "c" / "d" / "e" / "f"
+ private int getByte(int position) throws IOException {
+
+ if ((position + 1) >= length) {
+ // to avoid ArrayIndexOutOfBoundsException
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ int b1, b2;
+
+ b1 = chars[position];
+ if (b1 >= '0' && b1 <= '9') {
+ b1 = b1 - '0';
+ } else if (b1 >= 'a' && b1 <= 'f') {
+ b1 = b1 - 87; // 87 = 'a' - 10
+ } else if (b1 >= 'A' && b1 <= 'F') {
+ b1 = b1 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ b2 = chars[position + 1];
+ if (b2 >= '0' && b2 <= '9') {
+ b2 = b2 - '0';
+ } else if (b2 >= 'a' && b2 <= 'f') {
+ b2 = b2 - 87; // 87 = 'a' - 10
+ } else if (b2 >= 'A' && b2 <= 'F') {
+ b2 = b2 - 55; // 55 = 'A' - 10
+ } else {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ return (b1 << 4) + b2;
+ }
+
+ /**
+ * Parses the DN and returns the attribute value for an attribute type.
+ *
+ * @param attributeType attribute type to look for (e.g. "ca")
+ * @return value of the attribute that first found, or null if none found
+ */
+ public String find(String attributeType) {
+ try {
+ // Initialize internal state.
+ pos = 0;
+ beg = 0;
+ end = 0;
+ cur = 0;
+ chars = dn.toCharArray();
+
+ String attType = nextAT();
+ if (attType == null) {
+ return null;
+ }
+ while (true) {
+ String attValue = "";
+
+ if (pos == length) {
+ return null;
+ }
+
+ switch (chars[pos]) {
+ case '"':
+ attValue = quotedAV();
+ break;
+ case '#':
+ attValue = hexAV();
+ break;
+ case '+':
+ case ',':
+ case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+ //empty attribute value
+ break;
+ default:
+ attValue = escapedAV();
+ }
+
+ if (attributeType.equalsIgnoreCase(attType)) {
+ return attValue;
+ }
+
+ if (pos >= length) {
+ return null;
+ }
+
+ if (chars[pos] == ',' || chars[pos] == ';') {
+ } else if (chars[pos] != '+') {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+
+ pos++;
+ attType = nextAT();
+ if (attType == null) {
+ throw new IOException(ERROR_PARSE_ERROR);
+ }
+ }
+ } catch (IOException e) {
+ // Parse error shouldn't happen, because we only handle DNs that
+ // X500Principal.getName() returns, which shouldn't be malformed.
+ Log.e(TAG, "Failed to parse DN: " + dn);
+ return null;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/net/DbSSLSessionCache.java b/core/java/com/android/internal/net/DbSSLSessionCache.java
deleted file mode 100644
index 842d40b..0000000
--- a/core/java/com/android/internal/net/DbSSLSessionCache.java
+++ /dev/null
@@ -1,289 +0,0 @@
-// Copyright 2009 The Android Open Source Project
-
-package com.android.internal.net;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.net.ssl.SSLSession;
-
-/**
- * Hook into harmony SSL cache to persist the SSL sessions.
- *
- * Current implementation is suitable for saving a small number of hosts -
- * like google services. It can be extended with expiration and more features
- * to support more hosts.
- *
- * {@hide}
- */
-public class DbSSLSessionCache implements SSLClientSessionCache {
- private static final String TAG = "DbSSLSessionCache";
-
- /**
- * Table where sessions are stored.
- */
- public static final String SSL_CACHE_TABLE = "ssl_sessions";
-
- private static final String SSL_CACHE_ID = "_id";
-
- /**
- * Key is host:port - port is not optional.
- */
- private static final String SSL_CACHE_HOSTPORT = "hostport";
-
- /**
- * Base64-encoded DER value of the session.
- */
- private static final String SSL_CACHE_SESSION = "session";
-
- /**
- * Time when the record was added - should be close to the time
- * of the initial session negotiation.
- */
- private static final String SSL_CACHE_TIME_SEC = "time_sec";
-
- public static final String DATABASE_NAME = "ssl_sessions.db";
-
- public static final int DATABASE_VERSION = 2;
-
- /** public for testing
- */
- public static final int SSL_CACHE_ID_COL = 0;
- public static final int SSL_CACHE_HOSTPORT_COL = 1;
- public static final int SSL_CACHE_SESSION_COL = 2;
- public static final int SSL_CACHE_TIME_SEC_COL = 3;
-
- public static final int MAX_CACHE_SIZE = 256;
-
- private final Map<String, byte[]> mExternalCache =
- new HashMap<String, byte[]>();
-
-
- private DatabaseHelper mDatabaseHelper;
-
- private boolean mNeedsCacheLoad = true;
-
- public static final String[] PROJECTION = new String[] {
- SSL_CACHE_ID,
- SSL_CACHE_HOSTPORT,
- SSL_CACHE_SESSION,
- SSL_CACHE_TIME_SEC
- };
-
- private static final Map<String,DbSSLSessionCache> sInstances =
- new HashMap<String,DbSSLSessionCache>();
-
- /**
- * Returns a singleton instance of the DbSSLSessionCache that should be used for this
- * context's package.
- *
- * @param context The context that should be used for getting/creating the singleton instance.
- * @return The singleton instance for the context's package.
- */
- public static synchronized DbSSLSessionCache getInstanceForPackage(Context context) {
- String packageName = context.getPackageName();
- if (sInstances.containsKey(packageName)) {
- return sInstances.get(packageName);
- }
- DbSSLSessionCache cache = new DbSSLSessionCache(context);
- sInstances.put(packageName, cache);
- return cache;
- }
-
- /**
- * Create a SslSessionCache instance, using the specified context to
- * initialize the database.
- *
- * This constructor will use the default database - created for the application
- * context.
- *
- * @param activityContext
- */
- private DbSSLSessionCache(Context activityContext) {
- Context appContext = activityContext.getApplicationContext();
- mDatabaseHelper = new DatabaseHelper(appContext);
- }
-
- /**
- * Create a SslSessionCache that uses a specific database.
- *
- *
- * @param database
- */
- public DbSSLSessionCache(DatabaseHelper database) {
- this.mDatabaseHelper = database;
- }
-
- public void putSessionData(SSLSession session, byte[] der) {
- if (mDatabaseHelper == null) {
- return;
- }
- synchronized (this.getClass()) {
- SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
- if (mExternalCache.size() == MAX_CACHE_SIZE) {
- // remove oldest.
- // TODO: check if the new one is in cached already ( i.e. update ).
- Cursor byTime = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
- PROJECTION, null, null, null, null, SSL_CACHE_TIME_SEC);
- if (byTime.moveToFirst()) {
- // TODO: can I do byTime.deleteRow() ?
- String hostPort = byTime.getString(SSL_CACHE_HOSTPORT_COL);
- db.delete(SSL_CACHE_TABLE,
- SSL_CACHE_HOSTPORT + "= ?" , new String[] { hostPort });
- mExternalCache.remove(hostPort);
- } else {
- Log.w(TAG, "No rows found");
- // something is wrong, clear it
- clear();
- }
- }
- // Serialize native session to standard DER encoding
- long t0 = System.currentTimeMillis();
-
- String b64 = new String(Base64.encodeBase64(der));
- String key = session.getPeerHost() + ":" + session.getPeerPort();
-
- ContentValues values = new ContentValues();
- values.put(SSL_CACHE_HOSTPORT, key);
- values.put(SSL_CACHE_SESSION, b64);
- values.put(SSL_CACHE_TIME_SEC, System.currentTimeMillis() / 1000);
-
- mExternalCache.put(key, der);
-
- try {
- db.insert(SSL_CACHE_TABLE, null /*nullColumnHack */ , values);
- } catch(SQLException ex) {
- // Ignore - nothing we can do to recover, and caller shouldn't
- // be affected.
- Log.w(TAG, "Ignoring SQL exception when caching session", ex);
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- long t1 = System.currentTimeMillis();
- Log.d(TAG, "New SSL session " + session.getPeerHost() +
- " DER len: " + der.length + " " + (t1 - t0));
- }
- }
-
- }
-
- public byte[] getSessionData(String host, int port) {
- // Current (simple) implementation does a single lookup to DB, then saves
- // all entries to the cache.
-
- // This works for google services - i.e. small number of certs.
- // If we extend this to all processes - we should hold a separate cache
- // or do lookups to DB each time.
- if (mDatabaseHelper == null) {
- return null;
- }
- synchronized(this.getClass()) {
- if (mNeedsCacheLoad) {
- // Don't try to load again, if something is wrong on the first
- // request it'll likely be wrong each time.
- mNeedsCacheLoad = false;
- long t0 = System.currentTimeMillis();
-
- Cursor cur = null;
- try {
- cur = mDatabaseHelper.getReadableDatabase().query(SSL_CACHE_TABLE,
- PROJECTION, null, null, null, null, null);
- if (cur.moveToFirst()) {
- do {
- String hostPort = cur.getString(SSL_CACHE_HOSTPORT_COL);
- String value = cur.getString(SSL_CACHE_SESSION_COL);
-
- if (hostPort == null || value == null) {
- continue;
- }
- // TODO: blob support ?
- byte[] der = Base64.decodeBase64(value.getBytes());
- mExternalCache.put(hostPort, der);
- } while (cur.moveToNext());
-
- }
- } catch (SQLException ex) {
- Log.d(TAG, "Error loading SSL cached entries ", ex);
- } finally {
- if (cur != null) {
- cur.close();
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- long t1 = System.currentTimeMillis();
- Log.d(TAG, "LOADED CACHED SSL " + (t1 - t0) + " ms");
- }
- }
- }
-
- String key = host + ":" + port;
-
- return mExternalCache.get(key);
- }
- }
-
- /**
- * Reset the database and internal state.
- * Used for testing or to free space.
- */
- public void clear() {
- synchronized(this) {
- try {
- mExternalCache.clear();
- mNeedsCacheLoad = true;
- mDatabaseHelper.getWritableDatabase().delete(SSL_CACHE_TABLE,
- null, null);
- } catch (SQLException ex) {
- Log.d(TAG, "Error removing SSL cached entries ", ex);
- // ignore - nothing we can do about it
- }
- }
- }
-
- public byte[] getSessionData(byte[] id) {
- // We support client side only - the cache will do nothing for
- // server-side sessions.
- return null;
- }
-
- /** Visible for testing.
- */
- public static class DatabaseHelper extends SQLiteOpenHelper {
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null /* factory */, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + SSL_CACHE_TABLE + " (" +
- SSL_CACHE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- SSL_CACHE_HOSTPORT + " TEXT UNIQUE ON CONFLICT REPLACE," +
- SSL_CACHE_SESSION + " TEXT," +
- SSL_CACHE_TIME_SEC + " INTEGER" +
- ");");
-
- // No index - we load on startup, index would slow down inserts.
- // If we want to scale this to lots of rows - we could use
- // index, but then we'll hit DB a bit too often ( including
- // negative hits )
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- db.execSQL("DROP TABLE IF EXISTS " + SSL_CACHE_TABLE );
- onCreate(db);
- }
-
- }
-
-}
diff --git a/core/java/android/net/http/DomainNameChecker.java b/core/java/com/android/internal/net/DomainNameValidator.java
index e4c8009..dbd5019 100644
--- a/core/java/android/net/http/DomainNameChecker.java
+++ b/core/java/com/android/internal/net/DomainNameValidator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -13,29 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.net;
-package android.net.http;
-import org.bouncycastle.asn1.x509.X509Name;
+import android.util.Config;
+import android.util.Log;
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
-import java.util.Vector;
-/**
- * Implements basic domain-name validation as specified by RFC2818.
- *
- * {@hide}
- */
-public class DomainNameChecker {
+import javax.security.auth.x500.X500Principal;
+
+/** @hide */
+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 Pattern QUICK_IP_PATTERN;
static {
try {
@@ -84,8 +86,8 @@ public class DomainNameChecker {
errorMessage = "unknown host exception";
}
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.isIpAddress(): " + errorMessage);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.isIpAddress(): " + errorMessage);
}
rval = false;
@@ -102,8 +104,8 @@ public class DomainNameChecker {
* @return True iff if there is a domain match as specified by RFC2818
*/
private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchIpAddress(): this domain: " + thisDomain);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchIpAddress(): this domain: " + thisDomain);
}
try {
@@ -118,8 +120,8 @@ public class DomainNameChecker {
if (altNameType.intValue() == ALT_IPA_NAME) {
String altName = (String)(altNameEntry.get(1));
if (altName != null) {
- if (HttpLog.LOGV) {
- HttpLog.v("alternative IP: " + altName);
+ if (LOG_ENABLED) {
+ Log.v(TAG, "alternative IP: " + altName);
}
if (thisDomain.equalsIgnoreCase(altName)) {
return true;
@@ -166,31 +168,25 @@ public class DomainNameChecker {
}
}
} catch (CertificateParsingException e) {
- // one way we can get here is if an alternative name starts with
- // '*' character, which is contrary to one interpretation of the
- // spec (a valid DNS name must start with a letter); there is no
- // good way around this, and in order to be compatible we proceed
- // to check the common name (ie, ignore alternative names)
- if (HttpLog.LOGV) {
- String errorMessage = e.getMessage();
- if (errorMessage == null) {
- errorMessage = "failed to parse certificate";
- }
-
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns(): " + errorMessage);
- }
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "failed to parse certificate";
}
+
+ Log.w(TAG, "DomainNameValidator.matchDns(): " + errorMessage);
+ return false;
}
if (!hasDns) {
- X509Name xName = new X509Name(certificate.getSubjectDN().getName());
- Vector val = xName.getValues();
- Vector oid = xName.getOIDs();
- for (int i = 0; i < oid.size(); i++) {
- if (oid.elementAt(i).equals(X509Name.CN)) {
- return matchDns(thisDomain, (String)(val.elementAt(i)));
- }
+ final String cn = new DNParser(certificate.getSubjectX500Principal())
+ .find("cn");
+ if (LOG_ENABLED) {
+ Log.v(TAG, "Validating subject: DN:"
+ + certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL)
+ + " CN:" + cn);
+ }
+ if (cn != null) {
+ return matchDns(thisDomain, cn);
}
}
@@ -202,9 +198,10 @@ public class DomainNameChecker {
* @param thatDomain The domain name from the certificate
* @return True iff thisDomain matches thatDomain as specified by RFC2818
*/
- private static boolean matchDns(String thisDomain, String thatDomain) {
- if (HttpLog.LOGV) {
- HttpLog.v("DomainNameChecker.matchDns():" +
+ // not private for testing
+ public static boolean matchDns(String thisDomain, String thatDomain) {
+ if (LOG_ENABLED) {
+ Log.v(TAG, "DomainNameValidator.matchDns():" +
" this domain: " + thisDomain +
" that domain: " + thatDomain);
}
@@ -231,7 +228,7 @@ public class DomainNameChecker {
rval = thisDomainTokens[i].equals(thatDomainTokens[i]);
if (!rval) {
// (c) OR we have a special *-match:
- // Z.Y.X matches *.Y.X but does not match *.X
+ // *.Y.X matches Z.Y.X but *.X doesn't match Z.Y.X
rval = (i == 0 && thisDomainTokensNum == thatDomainTokensNum);
if (rval) {
rval = thatDomainTokens[0].equals("*");
@@ -242,10 +239,13 @@ public class DomainNameChecker {
thisDomainTokens[0], thatDomainTokens[0]);
}
}
-
break;
}
}
+ } else {
+ // (e) OR thatHost has a '*.'-prefix of thisHost:
+ // *.Y.X matches Y.X
+ rval = thatDomain.equals("*." + thisDomain);
}
}
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
index ca0345f..e675ef0 100644
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ b/core/java/com/android/internal/os/AtomicFile.java
@@ -45,12 +45,13 @@ public class AtomicFile {
public FileOutputStream startWrite() throws IOException {
// Rename the current file so it may be used as a backup during the next read
if (mBaseName.exists()) {
- if (!mBaseName.renameTo(mBackupName)) {
- mBackupName.delete();
+ if (!mBackupName.exists()) {
if (!mBaseName.renameTo(mBackupName)) {
Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+ " to backup file " + mBackupName);
}
+ } else {
+ mBaseName.delete();
}
}
FileOutputStream str = null;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 5199ada..aadb576 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,9 +16,11 @@
package com.android.internal.os;
+import com.android.internal.util.JournaledFile;
+
import android.bluetooth.BluetoothHeadset;
+import android.net.TrafficStats;
import android.os.BatteryStats;
-import android.os.NetStat;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
@@ -30,6 +32,7 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.Slog;
import android.util.SparseArray;
import java.io.BufferedReader;
@@ -43,6 +46,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* All information we are collecting about things that can happen that impact
@@ -57,12 +61,18 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 42;
+ private static final int VERSION = 43;
+ // The maximum number of names wakelocks we will keep track of
+ // per uid; once the limit is reached, we batch the remaining wakelocks
+ // in to one common name.
+ private static final int MAX_WAKELOCKS_PER_UID = 20;
+
+ private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
+
private static int sNumSpeedSteps;
- private final File mFile;
- private final File mBackupFile;
+ private final JournaledFile mFile;
/**
* The statistics we have collected organized by uids.
@@ -209,7 +219,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// For debugging
public BatteryStatsImpl() {
- mFile = mBackupFile = null;
+ mFile = null;
}
public static interface Unpluggable {
@@ -221,14 +231,15 @@ public final class BatteryStatsImpl extends BatteryStats {
* State for keeping track of counting information.
*/
public static class Counter extends BatteryStats.Counter implements Unpluggable {
- int mCount;
+ final AtomicInteger mCount = new AtomicInteger();
int mLoadedCount;
int mLastCount;
int mUnpluggedCount;
int mPluggedCount;
Counter(ArrayList<Unpluggable> unpluggables, Parcel in) {
- mPluggedCount = mCount = in.readInt();
+ mPluggedCount = in.readInt();
+ mCount.set(mPluggedCount);
mLoadedCount = in.readInt();
mLastCount = in.readInt();
mUnpluggedCount = in.readInt();
@@ -240,18 +251,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void writeToParcel(Parcel out) {
- out.writeInt(mCount);
+ out.writeInt(mCount.get());
out.writeInt(mLoadedCount);
out.writeInt(mLastCount);
out.writeInt(mUnpluggedCount);
}
public void unplug(long batteryUptime, long batteryRealtime) {
- mUnpluggedCount = mCount = mPluggedCount;
+ mUnpluggedCount = mPluggedCount;
+ mCount.set(mPluggedCount);
}
public void plug(long batteryUptime, long batteryRealtime) {
- mPluggedCount = mCount;
+ mPluggedCount = mCount.get();
}
/**
@@ -276,7 +288,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (which == STATS_LAST) {
val = mLastCount;
} else {
- val = mCount;
+ val = mCount.get();
if (which == STATS_UNPLUGGED) {
val -= mUnpluggedCount;
} else if (which != STATS_TOTAL) {
@@ -288,25 +300,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void logState(Printer pw, String prefix) {
- pw.println(prefix + "mCount=" + mCount
+ pw.println(prefix + "mCount=" + mCount.get()
+ " mLoadedCount=" + mLoadedCount + " mLastCount=" + mLastCount
+ " mUnpluggedCount=" + mUnpluggedCount
+ " mPluggedCount=" + mPluggedCount);
}
- void stepLocked() {
- mCount++;
+ void stepAtomic() {
+ mCount.incrementAndGet();
}
void writeSummaryFromParcelLocked(Parcel out) {
- out.writeInt(mCount);
- out.writeInt(mCount - mLoadedCount);
+ int count = mCount.get();
+ out.writeInt(count);
+ out.writeInt(count - mLoadedCount);
}
void readSummaryFromParcelLocked(Parcel in) {
- mCount = mLoadedCount = in.readInt();
+ mLoadedCount = in.readInt();
+ mCount.set(mLoadedCount);
mLastCount = in.readInt();
- mUnpluggedCount = mPluggedCount = mCount;
+ mUnpluggedCount = mPluggedCount = mLoadedCount;
}
}
@@ -320,8 +334,8 @@ public final class BatteryStatsImpl extends BatteryStats {
super(unpluggables);
}
- public void addCountLocked(long count) {
- mCount += count;
+ public void addCountAtomic(long count) {
+ mCount.addAndGet((int)count);
}
}
@@ -877,13 +891,22 @@ public final class BatteryStatsImpl extends BatteryStats {
for (endIndex=startIndex;
endIndex < len && wlBuffer[endIndex] != '\n' && wlBuffer[endIndex] != '\0';
endIndex++);
- endIndex++; // endIndex is an exclusive upper bound.
+ // Don't go over the end of the buffer
+ if (endIndex < len) {
+ endIndex++; // endIndex is an exclusive upper bound.
+ }
String[] nameStringArray = mProcWakelocksName;
long[] wlData = mProcWakelocksData;
+ // Stomp out any bad characters since this is from a circular buffer
+ // A corruption is seen sometimes that results in the vm crashing
+ // This should prevent crashes and the line will probably fail to parse
+ for (int j = startIndex; j < endIndex; j++) {
+ if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
+ }
boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
PROC_WAKELOCKS_FORMAT, nameStringArray, wlData, null);
-
+
name = nameStringArray[0];
count = (int) wlData[1];
// convert nanoseconds to microseconds with rounding.
@@ -1022,8 +1045,8 @@ public final class BatteryStatsImpl extends BatteryStats {
public void doUnplug(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
- u.mStartedTcpBytesReceived = NetStat.getUidRxBytes(u.mUid);
- u.mStartedTcpBytesSent = NetStat.getUidTxBytes(u.mUid);
+ u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
+ u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
}
@@ -1031,10 +1054,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
// Track total mobile data
- doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataStart = getCurrentRadioDataUptime();
mRadioDataUptime = 0;
@@ -1058,10 +1081,10 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
- doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
- doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
- doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
- doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+ doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
+ doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
+ doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
+ doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
// Track radio awake time
mRadioDataUptime = getRadioDataUptime();
mRadioDataStart = -1;
@@ -1115,8 +1138,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteInputEventLocked() {
- mInputEventCounter.stepLocked();
+ public void noteInputEventAtomic() {
+ mInputEventCounter.stepAtomic();
}
public void noteUserActivityLocked(int uid, int event) {
@@ -1519,7 +1542,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeCurrentTcpBytesReceived() {
return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
- ? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+ ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
}
@Override
@@ -1671,7 +1694,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (type < 0) type = 0;
else if (type >= NUM_USER_ACTIVITY_TYPES) type = NUM_USER_ACTIVITY_TYPES-1;
- mUserActivityCounters[type].stepLocked();
+ mUserActivityCounters[type].stepAtomic();
}
@Override
@@ -1696,7 +1719,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public long computeCurrentTcpBytesSent() {
return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
- ? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+ ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
}
void writeToParcelLocked(Parcel out, long batteryRealtime) {
@@ -1757,7 +1780,12 @@ public final class BatteryStatsImpl extends BatteryStats {
String wakelockName = in.readString();
Uid.Wakelock wakelock = new Wakelock();
wakelock.readFromParcelLocked(unpluggables, in);
- mWakelockStats.put(wakelockName, wakelock);
+ if (mWakelockStats.size() < MAX_WAKELOCKS_PER_UID) {
+ // We will just drop some random set of wakelocks if
+ // the previous run of the system was an older version
+ // that didn't impose a limit.
+ mWakelockStats.put(wakelockName, wakelock);
+ }
}
int numSensors = in.readInt();
@@ -2158,7 +2186,7 @@ public final class BatteryStatsImpl extends BatteryStats {
/* Called by ActivityManagerService when CPU times are updated. */
public void addSpeedStepTimes(long[] values) {
for (int i = 0; i < mSpeedBins.length && i < values.length; i++) {
- mSpeedBins[i].addCountLocked(values[i]);
+ mSpeedBins[i].addCountAtomic(values[i]);
}
}
@@ -2583,8 +2611,14 @@ public final class BatteryStatsImpl extends BatteryStats {
public StopwatchTimer getWakeTimerLocked(String name, int type) {
Wakelock wl = mWakelockStats.get(name);
if (wl == null) {
- wl = new Wakelock();
- mWakelockStats.put(name, wl);
+ if (mWakelockStats.size() > MAX_WAKELOCKS_PER_UID) {
+ name = BATCHED_WAKELOCK_NAME;
+ wl = mWakelockStats.get(name);
+ }
+ if (wl == null) {
+ wl = new Wakelock();
+ mWakelockStats.put(name, wl);
+ }
}
StopwatchTimer t = null;
switch (type) {
@@ -2686,8 +2720,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public BatteryStatsImpl(String filename) {
- mFile = new File(filename);
- mBackupFile = new File(filename + ".bak");
+ mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
mStartCount++;
mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -2705,6 +2738,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables);
mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables);
mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables);
+ mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
@@ -2717,7 +2751,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public BatteryStatsImpl(Parcel p) {
- mFile = mBackupFile = null;
+ mFile = null;
readFromParcel(p);
}
@@ -2780,7 +2814,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (m == null) {
// Not crashing might make board bringup easier.
- Log.w(TAG, "Couldn't get kernel wake lock stats");
+ Slog.w(TAG, "Couldn't get kernel wake lock stats");
return;
}
@@ -2919,22 +2953,22 @@ public final class BatteryStatsImpl extends BatteryStats {
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+ return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+ return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesSent(int which) {
- return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+ return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesReceived(int which) {
- return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+ return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
}
@Override
@@ -3028,26 +3062,19 @@ public final class BatteryStatsImpl extends BatteryStats {
return u.getServiceStatsLocked(pkg, name);
}
+ private static JournaledFile makeJournaledFile() {
+ final String base = "/data/system/device_policies.xml";
+ return new JournaledFile(new File(base), new File(base + ".tmp"));
+ }
+
public void writeLocked() {
- if ((mFile == null) || (mBackupFile == null)) {
- Log.w("BatteryStats", "writeLocked: no file associated with this instance");
+ if (mFile == null) {
+ Slog.w("BatteryStats", "writeLocked: no file associated with this instance");
return;
}
- // Keep the old file around until we know the new one has
- // been successfully written.
- if (mFile.exists()) {
- if (mBackupFile.exists()) {
- mBackupFile.delete();
- }
- if (!mFile.renameTo(mBackupFile)) {
- Log.w("BatteryStats", "Failed to back up file before writing new stats");
- return;
- }
- }
-
try {
- FileOutputStream stream = new FileOutputStream(mFile);
+ FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite());
Parcel out = Parcel.obtain();
writeSummaryToParcel(out);
stream.write(out.marshall());
@@ -3055,18 +3082,14 @@ public final class BatteryStatsImpl extends BatteryStats {
stream.flush();
stream.close();
- mBackupFile.delete();
+ mFile.commit();
mLastWriteTime = SystemClock.elapsedRealtime();
return;
} catch (IOException e) {
- Log.w("BatteryStats", "Error writing battery statistics", e);
- }
- if (mFile.exists()) {
- if (!mFile.delete()) {
- Log.w(TAG, "Failed to delete mangled file " + mFile);
- }
+ Slog.w("BatteryStats", "Error writing battery statistics", e);
}
+ mFile.rollback();
}
static byte[] readFully(FileInputStream stream) throws java.io.IOException {
@@ -3093,29 +3116,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void readLocked() {
- if ((mFile == null) || (mBackupFile == null)) {
- Log.w("BatteryStats", "readLocked: no file associated with this instance");
+ if (mFile == null) {
+ Slog.w("BatteryStats", "readLocked: no file associated with this instance");
return;
}
mUidStats.clear();
- FileInputStream stream = null;
- if (mBackupFile.exists()) {
- try {
- stream = new FileInputStream(mBackupFile);
- } catch (java.io.IOException e) {
- // We'll try for the normal settings file.
- }
- }
-
try {
- if (stream == null) {
- if (!mFile.exists()) {
- return;
- }
- stream = new FileInputStream(mFile);
+ File file = mFile.chooseForRead();
+ if (!file.exists()) {
+ return;
}
+ FileInputStream stream = new FileInputStream(file);
byte[] raw = readFully(stream);
Parcel in = Parcel.obtain();
@@ -3125,7 +3138,7 @@ public final class BatteryStatsImpl extends BatteryStats {
readSummaryFromParcel(in);
} catch(java.io.IOException e) {
- Log.e("BatteryStats", "Error reading battery statistics", e);
+ Slog.e("BatteryStats", "Error reading battery statistics", e);
}
}
@@ -3136,7 +3149,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private void readSummaryFromParcel(Parcel in) {
final int version = in.readInt();
if (version != VERSION) {
- Log.w("BatteryStats", "readFromParcel: version got " + version
+ Slog.w("BatteryStats", "readFromParcel: version got " + version
+ ", expected " + VERSION + "; erasing old stats");
return;
}
@@ -3178,6 +3191,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothOnTimer.readSummaryFromParcelLocked(in);
int NKW = in.readInt();
+ if (NKW > 10000) {
+ Slog.w(TAG, "File corrupt: too many kernel wake locks " + NKW);
+ return;
+ }
for (int ikw = 0; ikw < NKW; ikw++) {
if (in.readInt() != 0) {
String kwltName = in.readString();
@@ -3188,6 +3205,10 @@ public final class BatteryStatsImpl extends BatteryStats {
sNumSpeedSteps = in.readInt();
final int NU = in.readInt();
+ if (NU > 10000) {
+ Slog.w(TAG, "File corrupt: too many uids " + NU);
+ return;
+ }
for (int iu = 0; iu < NU; iu++) {
int uid = in.readInt();
Uid u = new Uid(uid);
@@ -3216,6 +3237,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
int NW = in.readInt();
+ if (NW > 10000) {
+ Slog.w(TAG, "File corrupt: too many wake locks " + NW);
+ return;
+ }
for (int iw = 0; iw < NW; iw++) {
String wlName = in.readString();
if (in.readInt() != 0) {
@@ -3230,6 +3255,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
int NP = in.readInt();
+ if (NP > 10000) {
+ Slog.w(TAG, "File corrupt: too many sensors " + NP);
+ return;
+ }
for (int is = 0; is < NP; is++) {
int seNumber = in.readInt();
if (in.readInt() != 0) {
@@ -3239,6 +3268,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
NP = in.readInt();
+ if (NP > 10000) {
+ Slog.w(TAG, "File corrupt: too many processes " + NP);
+ return;
+ }
for (int ip = 0; ip < NP; ip++) {
String procName = in.readString();
Uid.Proc p = u.getProcessStatsLocked(procName);
@@ -3251,6 +3284,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
NP = in.readInt();
+ if (NP > 10000) {
+ Slog.w(TAG, "File corrupt: too many packages " + NP);
+ return;
+ }
for (int ip = 0; ip < NP; ip++) {
String pkgName = in.readString();
Uid.Pkg p = u.getPackageStatsLocked(pkgName);
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index eacf0ce..ba0bf0d 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -76,6 +76,13 @@ public class BinderInternal {
*/
public static final native IBinder getContextObject();
+ /**
+ * Special for system process to not allow incoming calls to run at
+ * background scheduling priority.
+ * @hide
+ */
+ public static final native void disableBackgroundScheduling(boolean disable);
+
static native final void handleGc();
public static void forceGc(String reason) {
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index 35b9251..a94fb1e 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.internal.os;
import android.content.Context;
diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
new file mode 100644
index 0000000..d067926
--- /dev/null
+++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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 com.android.internal.os;
+
+import android.os.DropBoxManager;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the
+ * DropBoxManagerService that actually implements the drop box functionality.
+ *
+ * @see DropBoxManager
+ * @hide
+ */
+interface IDropBoxManagerService {
+ /**
+ * @see DropBoxManager#addText
+ * @see DropBoxManager#addData
+ * @see DropBoxManager#addFile
+ */
+ void add(in DropBoxManager.Entry entry);
+
+ /** @see DropBoxManager#getNextEntry */
+ boolean isTagEnabled(String tag);
+
+ /** @see DropBoxManager#getNextEntry */
+ DropBoxManager.Entry getNextEntry(String tag, long millis);
+}
diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java
index b3d6f20..451340b 100644
--- a/core/java/com/android/internal/os/LoggingPrintStream.java
+++ b/core/java/com/android/internal/os/LoggingPrintStream.java
@@ -16,11 +16,17 @@
package com.android.internal.os;
-import java.io.PrintStream;
-import java.io.OutputStream;
import java.io.IOException;
-import java.util.Locale;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
import java.util.Formatter;
+import java.util.Locale;
/**
* A print stream which logs output line by line.
@@ -31,6 +37,27 @@ abstract class LoggingPrintStream extends PrintStream {
private final StringBuilder builder = new StringBuilder();
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. It may contain the leading bytes of multi-byte characters.
+ * Between writes this buffer is always ready to receive data; ie. the
+ * position is at the first unassigned byte and the limit is the capacity.
+ */
+ private ByteBuffer encodedBytes;
+
+ /**
+ * A buffer that is initialized when raw bytes are first written to this
+ * stream. Between writes this buffer is always clear; ie. the position is
+ * zero and the limit is the capacity.
+ */
+ private CharBuffer decodedChars;
+
+ /**
+ * Decodes bytes to characters using the system default charset. Initialized
+ * when raw bytes are first written to this stream.
+ */
+ private CharsetDecoder decoder;
+
protected LoggingPrintStream() {
super(new OutputStream() {
public void write(int oneByte) throws IOException {
@@ -80,20 +107,48 @@ abstract class LoggingPrintStream extends PrintStream {
}
}
- /*
- * We have no idea of how these bytes are encoded, so just ignore them.
- */
-
- /** Ignored. */
- public void write(int oneByte) {}
+ public void write(int oneByte) {
+ write(new byte[] { (byte) oneByte }, 0, 1);
+ }
- /** Ignored. */
@Override
- public void write(byte buffer[]) {}
+ public void write(byte[] buffer) {
+ write(buffer, 0, buffer.length);
+ }
- /** Ignored. */
@Override
- public void write(byte bytes[], int start, int count) {}
+ public synchronized void write(byte bytes[], int start, int count) {
+ if (decoder == null) {
+ encodedBytes = ByteBuffer.allocate(80);
+ decodedChars = CharBuffer.allocate(80);
+ decoder = Charset.defaultCharset().newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE);
+ }
+
+ int end = start + count;
+ while (start < end) {
+ // copy some bytes from the array to the long-lived buffer. This
+ // way, if we end with a partial character we don't lose it.
+ int numBytes = Math.min(encodedBytes.remaining(), end - start);
+ encodedBytes.put(bytes, start, numBytes);
+ start += numBytes;
+
+ encodedBytes.flip();
+ CoderResult coderResult;
+ do {
+ // decode bytes from the byte buffer into the char buffer
+ coderResult = decoder.decode(encodedBytes, decodedChars, false);
+
+ // copy chars from the char buffer into our string builder
+ decodedChars.flip();
+ builder.append(decodedChars);
+ decodedChars.clear();
+ } while (coderResult.isOverflow());
+ encodedBytes.compact();
+ }
+ flush(false);
+ }
/** Always returns false. */
@Override
diff --git a/core/java/com/android/internal/os/PkgUsageStats.java b/core/java/com/android/internal/os/PkgUsageStats.java
index e847878..1ac191b 100755
--- a/core/java/com/android/internal/os/PkgUsageStats.java
+++ b/core/java/com/android/internal/os/PkgUsageStats.java
@@ -1,3 +1,19 @@
+/*
+ * 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 com.android.internal.os;
import android.os.Parcel;
diff --git a/core/java/com/android/internal/os/RecoverySystem.java b/core/java/com/android/internal/os/RecoverySystem.java
deleted file mode 100644
index c938610..0000000
--- a/core/java/com/android/internal/os/RecoverySystem.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.os.FileUtils;
-import android.os.Power;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Utility class for interacting with the Android recovery partition.
- * The recovery partition is a small standalone system which can perform
- * operations that are difficult while the main system is running, like
- * upgrading system software or reformatting the data partition.
- * Note that most of these operations must be run as root.
- *
- * @hide
- */
-public class RecoverySystem {
- private static final String TAG = "RecoverySystem"; // for logging
-
- // Used to communicate with recovery. See commands/recovery/recovery.c.
- 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");
-
- // Length limits for reading files.
- private static int LOG_FILE_MAX_LENGTH = 8 * 1024;
-
- /**
- * Reboot into the recovery system to install a system update.
- * @param update package to install (must be in /cache or /data).
- * @throws IOException if something goes wrong.
- */
- public static void rebootAndUpdate(File update) throws IOException {
- String path = update.getCanonicalPath();
- if (path.startsWith("/cache/")) {
- path = "CACHE:" + path.substring(7);
- } else if (path.startsWith("/data/")) {
- path = "DATA:" + path.substring(6);
- } else {
- throw new IllegalArgumentException(
- "Must start with /cache or /data: " + path);
- }
- bootCommand("--update_package=" + path);
- }
-
- /**
- * Reboot into the recovery system to wipe the /data partition.
- * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
- * @throws IOException if something goes wrong.
- */
- public static void rebootAndWipe() throws IOException {
- bootCommand("--wipe_data");
- }
-
- /**
- * Reboot into the recovery system with the supplied argument.
- * @param arg to pass to the recovery utility.
- * @throws IOException if something goes wrong.
- */
- private static void bootCommand(String arg) throws IOException {
- RECOVERY_DIR.mkdirs(); // In case we need it
- COMMAND_FILE.delete(); // In case it's not writable
- LOG_FILE.delete();
-
- FileWriter command = new FileWriter(COMMAND_FILE);
- try {
- command.write(arg);
- command.write("\n");
- } finally {
- command.close();
- }
-
- // Having written the command file, go ahead and reboot
- Power.reboot("recovery");
- throw new IOException("Reboot failed (no permissions?)");
- }
-
- /**
- * Called after booting to process and remove recovery-related files.
- * @return the log file from recovery, or null if none was found.
- */
- public static String handleAftermath() {
- // Record the tail of the LOG_FILE
- String log = null;
- try {
- log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
- } catch (FileNotFoundException e) {
- Log.i(TAG, "No recovery log file");
- } catch (IOException e) {
- Log.e(TAG, "Error reading recovery log", e);
- }
-
- // Delete everything in RECOVERY_DIR
- String[] names = RECOVERY_DIR.list();
- for (int i = 0; names != null && i < names.length; i++) {
- File f = new File(RECOVERY_DIR, names[i]);
- if (!f.delete()) {
- Log.e(TAG, "Can't delete: " + f);
- } else {
- Log.i(TAG, "Deleted: " + f);
- }
- }
-
- return log;
- }
-}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index c782c8c..599a7fe 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -17,28 +17,20 @@
package com.android.internal.os;
import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
+import android.app.ApplicationErrorReport;
+import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
-import android.os.ICheckinService;
import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.Build;
-import android.server.data.CrashData;
import android.util.Config;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.logging.AndroidConfig;
import dalvik.system.VMRuntime;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicInteger;
@@ -58,6 +50,10 @@ public class RuntimeInit {
/** true if commonInit() has been called */
private static boolean initialized;
+ private static IBinder mApplicationObject;
+
+ private static volatile boolean mCrashing = false;
+
/**
* Use this to log a message when a thread exits due to an uncaught
* exception. The framework catches these for the main threads, so
@@ -66,26 +62,42 @@ public class RuntimeInit {
private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
try {
- Log.e(TAG, "Uncaught handler: thread " + t.getName()
- + " exiting due to uncaught exception");
- } catch (Throwable error) {
- // Ignore the throwable, since we're in the process of crashing anyway.
- // If we don't, the crash won't happen properly and the process will
- // be left around in a bad state.
+ // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
+ if (mCrashing) return;
+ mCrashing = true;
+
+ if (mApplicationObject == null) {
+ Slog.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
+ } else {
+ Slog.e(TAG, "FATAL EXCEPTION: " + t.getName(), e);
+ }
+
+ // Bring up crash dialog, wait for it to be dismissed
+ ActivityManagerNative.getDefault().handleApplicationCrash(
+ mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
+ } catch (Throwable t2) {
+ try {
+ Slog.e(TAG, "Error reporting crash", t2);
+ } catch (Throwable t3) {
+ // Even Slog.e() fails! Oh well.
+ }
+ } finally {
+ // Try everything to make sure this process goes away.
+ Process.killProcess(Process.myPid());
+ System.exit(10);
}
- crash(TAG, e);
}
}
private static final void commonInit() {
- if (Config.LOGV) Log.d(TAG, "Entered RuntimeInit!");
+ if (Config.LOGV) 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) Log.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
+ if (Config.LOGV) Slog.d(TAG, ">>>>> qwerty keyboard = " + hasQwerty);
if (hasQwerty == 1) {
System.setProperty("qwerty", "1");
}
@@ -125,7 +137,7 @@ public class RuntimeInit {
*/
String trace = SystemProperties.get("ro.kernel.android.tracing");
if (trace.equals("1")) {
- Log.i(TAG, "NOTE: emulator trace profiling enabled");
+ Slog.i(TAG, "NOTE: emulator trace profiling enabled");
Debug.enableEmulatorTraceOutput();
}
@@ -222,7 +234,7 @@ public class RuntimeInit {
*/
finishInit();
- if (Config.LOGV) Log.d(TAG, "Leaving RuntimeInit!");
+ if (Config.LOGV) Slog.d(TAG, "Leaving RuntimeInit!");
}
public static final native void finishInit();
@@ -267,7 +279,7 @@ public class RuntimeInit {
}
if (curArg == argv.length) {
- Log.e(TAG, "Missing classname argument to RuntimeInit!");
+ Slog.e(TAG, "Missing classname argument to RuntimeInit!");
// let the process exit
return;
}
@@ -300,80 +312,22 @@ public class RuntimeInit {
public static native int getQwertyKeyboard();
/**
- * Report a fatal error in the current process. If this is a user-process,
- * a dialog may be displayed informing the user of the error. This
- * function does not return; it forces the current process to exit.
+ * Report a serious error in the current process. May or may not cause
+ * the process to terminate (depends on system settings).
*
- * @param tag to use when logging the error
- * @param t exception that was generated by the error
+ * @param tag to record with the error
+ * @param t exception describing the error site and conditions
*/
- public static void crash(String tag, Throwable t) {
- if (mApplicationObject != null) {
- byte[] crashData = null;
- try {
- // Log exception.
- Log.e(TAG, Log.getStackTraceString(t));
- crashData = marshallException(tag, t);
- if (crashData == null) {
- throw new NullPointerException("Can't marshall crash data");
- }
- } catch (Throwable t2) {
- try {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Error reporting crash: "
- + Log.getStackTraceString(t2));
- } catch (Throwable t3) {
- // Do nothing, must be OOM so we can't format the message
- }
- }
-
- try {
- // Display user-visible error message.
- String msg = t.getMessage();
- if (msg == null) {
- msg = t.toString();
- }
-
- IActivityManager am = ActivityManagerNative.getDefault();
- try {
- int res = am.handleApplicationError(mApplicationObject,
- 0, tag, msg, t.toString(), crashData);
- // Is waiting for the debugger the right thing?
- // For now I have turned off the Debug button, because
- // I'm not sure what we should do if it is actually
- // selected.
- //Log.i(TAG, "Got app error result: " + res);
- if (res == 1) {
- Debug.waitForDebugger();
- return;
- }
- } catch (RemoteException e) {
- }
- } catch (Throwable t2) {
- try {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Error reporting crash: "
- + Log.getStackTraceString(t2));
- } catch (Throwable t3) {
- // Do nothing, must be OOM so we can't format the message
- }
- } finally {
- // Try everything to make sure this process goes away.
- Process.killProcess(Process.myPid());
- System.exit(10);
- }
- } else {
- try {
- Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS. System will crash.");
- Log.e(tag, Log.getStackTraceString(t));
- reportException(tag, t, true); // synchronous
- } catch (Throwable t2) {
- // Do nothing, must be OOM so we can't format the message
- } finally {
- // Try everything to make sure this process goes away.
+ public static void wtf(String tag, Throwable t) {
+ try {
+ if (ActivityManagerNative.getDefault().handleApplicationWtf(
+ mApplicationObject, tag, new ApplicationErrorReport.CrashInfo(t))) {
+ // The Activity Manager has already written us off -- now exit.
Process.killProcess(Process.myPid());
System.exit(10);
}
+ } catch (Throwable t2) {
+ Slog.e(TAG, "Error reporting WTF", t2);
}
}
@@ -381,82 +335,6 @@ public class RuntimeInit {
private static final AtomicInteger sInReportException = new AtomicInteger();
/**
- * Report an error in the current process. The exception information will
- * be handed off to the checkin service and eventually uploaded for analysis.
- * This is expensive! Only use this when the exception indicates a programming
- * error ("should not happen").
- *
- * @param tag to use when logging the error
- * @param t exception that was generated by the error
- * @param sync true to wait for the report, false to "fire and forget"
- */
- public static void reportException(String tag, Throwable t, boolean sync) {
- if (!initialized) {
- // Exceptions during, eg, zygote cannot use this mechanism
- return;
- }
-
- // It's important to prevent an infinite crash-reporting loop:
- // while this function is running, don't let it be called again.
- int reenter = sInReportException.getAndIncrement();
- if (reenter != 0) {
- sInReportException.decrementAndGet();
- Log.e(TAG, "Crash logging skipped, already logging another crash");
- return;
- }
-
- // TODO: Enable callers to specify a level (i.e. warn or error).
- try {
- // Submit crash data to statistics service.
- byte[] crashData = marshallException(tag, t);
- ICheckinService checkin = ICheckinService.Stub.asInterface(
- ServiceManager.getService("checkin"));
- if (checkin == null) {
- Log.e(TAG, "Crash logging skipped, no checkin service");
- } else if (sync) {
- checkin.reportCrashSync(crashData);
- } else {
- checkin.reportCrashAsync(crashData);
- }
- } catch (Throwable t2) {
- // Log exception as a string so we don't get in an infinite loop.
- Log.e(TAG, "Crash logging failed: " + t2);
- } finally {
- sInReportException.decrementAndGet();
- }
- }
-
- private static byte[] marshallException(String tag, Throwable t) {
- // Convert crash data to bytes.
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- DataOutputStream dout = new DataOutputStream(bout);
- try {
- new CrashData(tag, t).write(dout);
- dout.close();
- } catch (IOException e) {
- return null;
- }
- return bout.toByteArray();
- }
-
- /**
- * Replay an encoded CrashData record back into a useable CrashData record. This can be
- * helpful for providing debugging output after a process error.
- *
- * @param crashDataBytes The byte array containing the encoded crash record
- * @return new CrashData record, or null if could not create one.
- */
- public static CrashData unmarshallException(byte[] crashDataBytes) {
- try {
- ByteArrayInputStream bin = new ByteArrayInputStream(crashDataBytes);
- DataInputStream din = new DataInputStream(bin);
- return new CrashData(din);
- } catch (IOException e) {
- return null;
- }
- }
-
- /**
* Set the object identifying this application/process, for reporting VM
* errors.
*/
@@ -471,7 +349,4 @@ public class RuntimeInit {
// Register handlers for DDM messages.
android.ddm.DdmRegister.registerHandlers();
}
-
- private static IBinder mApplicationObject;
-
}
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 51d9570..5f5c7a4 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -1,3 +1,19 @@
+/*
+ * 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 com.android.internal.os;
import dalvik.system.SamplingProfiler;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 631e7d8..da0c5a2 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -295,7 +295,10 @@ class ZygoteConnection {
/** from --peer-wait */
boolean peerWait;
- /** from --enable-debugger, --enable-checkjni, --enable-assert */
+ /**
+ * From --enable-debugger, --enable-checkjni, --enable-assert, and
+ * --enable-safemode
+ */
int debugFlags;
/** from --classpath */
@@ -363,6 +366,8 @@ class ZygoteConnection {
arg.substring(arg.indexOf('=') + 1));
} else if (arg.equals("--enable-debugger")) {
debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+ } else if (arg.equals("--enable-safemode")) {
+ debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
} else if (arg.equals("--enable-checkjni")) {
debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
} else if (arg.equals("--enable-assert")) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 404c513..b677b1e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -66,6 +66,13 @@ public class ZygoteInit {
/** when preloading, GC after allocating this many bytes */
private static final int PRELOAD_GC_THRESHOLD = 50000;
+ /** throw on missing preload, only if this looks like a developer */
+ private static final boolean THROW_ON_MISSING_PRELOAD =
+ "1".equals(SystemProperties.get("persist.service.adb.enable"));
+
+ public static final String USAGE_STRING =
+ " <\"true\"|\"false\" for startSystemServer>";
+
private static LocalServerSocket sServerSocket;
/**
@@ -322,8 +329,8 @@ public class ZygoteInit {
}
}
- if (missingClasses != null &&
- "1".equals(SystemProperties.get("persist.service.adb.enable"))) {
+ if (THROW_ON_MISSING_PRELOAD &&
+ missingClasses != null) {
throw new IllegalStateException(
"Missing class(es) for preloading, update preloaded-classes ["
+ missingClasses + "]");
@@ -597,12 +604,13 @@ public class ZygoteInit {
// If requested, start system server directly from Zygote
if (argv.length != 2) {
- throw new RuntimeException(
- "ZygoteInit.main expects two arguments");
+ throw new RuntimeException(argv[0] + USAGE_STRING);
}
if (argv[1].equals("true")) {
startSystemServer();
+ } else if (!argv[1].equals("false")) {
+ throw new RuntimeException(argv[0] + USAGE_STRING);
}
Log.i(TAG, "Accepting command socket connections");
diff --git a/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java b/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
index 251ecbc..e961116 100644
--- a/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
+++ b/core/java/com/android/internal/service/wallpaper/ImageWallpaper.java
@@ -16,12 +16,15 @@
package com.android.internal.service.wallpaper;
+import com.android.internal.view.WindowManagerPolicyThread;
+
import android.app.WallpaperManager;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Process;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
@@ -43,9 +46,14 @@ public class ImageWallpaper extends WallpaperService {
public void onCreate() {
super.onCreate();
mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
- mThread = new HandlerThread("Wallpaper", Process.THREAD_PRIORITY_FOREGROUND);
- mThread.start();
- setCallbackLooper(mThread.getLooper());
+ Looper looper = WindowManagerPolicyThread.getLooper();
+ if (looper != null) {
+ setCallbackLooper(looper);
+ } else {
+ mThread = new HandlerThread("Wallpaper", Process.THREAD_PRIORITY_FOREGROUND);
+ mThread.start();
+ setCallbackLooper(mThread.getLooper());
+ }
}
public Engine onCreateEngine() {
@@ -55,7 +63,9 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onDestroy() {
super.onDestroy();
- mThread.quit();
+ if (mThread != null) {
+ mThread.quit();
+ }
}
class DrawableEngine extends Engine {
diff --git a/core/java/com/android/internal/util/HanziToPinyin.java b/core/java/com/android/internal/util/HanziToPinyin.java
new file mode 100644
index 0000000..6a4adaa
--- /dev/null
+++ b/core/java/com/android/internal/util/HanziToPinyin.java
@@ -0,0 +1,495 @@
+/*
+ * 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 com.android.internal.util;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * An object to convert Chinese character to its corresponding pinyin string.
+ * For characters with multiple possible pinyin string, only one is selected
+ * according to collator. Polyphone is not supported in this implementation.
+ * This class is implemented to achieve the best runtime performance and minimum
+ * runtime resources with tolerable sacrifice of accuracy. This implementation
+ * highly depends on zh_CN ICU collation data and must be always synchronized with
+ * ICU.
+ */
+public class HanziToPinyin {
+ private static final String TAG = "HanziToPinyin";
+
+ private static final char[] UNIHANS = {
+ '\u5416', '\u54ce', '\u5b89', '\u80ae', '\u51f9', '\u516b', '\u63b0', '\u6273',
+ '\u90a6', '\u52f9', '\u9642', '\u5954', '\u4f3b', '\u7680', '\u782d', '\u706c',
+ '\u618b', '\u6c43', '\u51ab', '\u7676', '\u5cec', '\u5693', '\u5072', '\u53c2',
+ '\u4ed3', '\u64a1', '\u518a', '\u5d7e', '\u564c', '\u6260', '\u62c6', '\u8fbf',
+ '\u4f25', '\u6284', '\u8f66', '\u62bb', '\u9637', '\u5403', '\u5145', '\u62bd',
+ '\u51fa', '\u640b', '\u5ddb', '\u5205', '\u5439', '\u65fe', '\u8e14', '\u5472',
+ '\u4ece', '\u51d1', '\u7c97', '\u6c46', '\u5d14', '\u90a8', '\u6413', '\u5491',
+ '\u5446', '\u4e39', '\u5f53', '\u5200', '\u6074', '\u6265', '\u706f', '\u4efe',
+ '\u55f2', '\u6541', '\u5201', '\u7239', '\u4e01', '\u4e1f', '\u4e1c', '\u543a',
+ '\u5262', '\u8011', '\u5796', '\u5428', '\u591a', '\u59b8', '\u5940', '\u97a5',
+ '\u800c', '\u53d1', '\u5e06', '\u531a', '\u98de', '\u5206', '\u4e30', '\u8985',
+ '\u4ecf', '\u57ba', '\u7d11', '\u592b', '\u7324', '\u65ee', '\u4f85', '\u5e72',
+ '\u5188', '\u768b', '\u6208', '\u7ed9', '\u6839', '\u63ef', '\u55bc', '\u55f0',
+ '\u5de5', '\u52fe', '\u4f30', '\u9e39', '\u4e56', '\u5173', '\u5149', '\u5f52',
+ '\u4e28', '\u8b34', '\u5459', '\u598e', '\u548d', '\u4f44', '\u592f', '\u8320',
+ '\u8bc3', '\u9ed2', '\u62eb', '\u4ea8', '\u53ff', '\u9f41', '\u4e4e', '\u82b1',
+ '\u6000', '\u6b22', '\u5ddf', '\u7070', '\u660f', '\u5419', '\u4e0c', '\u52a0',
+ '\u620b', '\u6c5f', '\u827d', '\u9636', '\u5dfe', '\u5755', '\u5182', '\u4e29',
+ '\u51e5', '\u59e2', '\u5658', '\u519b', '\u5494', '\u5f00', '\u938e', '\u5ffc',
+ '\u5c3b', '\u533c', '\u808e', '\u52a5', '\u7a7a', '\u62a0', '\u625d', '\u5938',
+ '\u84af', '\u5bbd', '\u5321', '\u4e8f', '\u5764', '\u6269', '\u62c9', '\u4f86',
+ '\u5170', '\u5577', '\u635e', '\u4ec2', '\u96f7', '\u8137', '\u68f1', '\u695e',
+ '\u550e', '\u4fe9', '\u5afe', '\u826f', '\u8e7d', '\u57d3', '\u53b8', '\u62ce',
+ '\u6e9c', '\u9f99', '\u5a04', '\u565c', '\u5b6a', '\u62a1', '\u9831', '\u5988',
+ '\u57cb', '\u989f', '\u7264', '\u732b', '\u5445', '\u95e8', '\u6c13', '\u54aa',
+ '\u5b80', '\u55b5', '\u4e5c', '\u6c11', '\u540d', '\u8c2c', '\u6478', '\u725f',
+ '\u6bcd', '\u62cf', '\u8149', '\u56e1', '\u56d4', '\u5b6c', '\u8bb7', '\u5a1e',
+ '\u5ae9', '\u80fd', '\u92b0', '\u62c8', '\u5a18', '\u9e1f', '\u634f', '\u56dc',
+ '\u5b81', '\u599e', '\u519c', '\u7fba', '\u5974', '\u597b', '\u9ec1', '\u90cd',
+ '\u5662', '\u8bb4', '\u5991', '\u62cd', '\u7705', '\u6c78', '\u629b', '\u5478',
+ '\u55b7', '\u5309', '\u4e76', '\u7247', '\u527d', '\u6c15', '\u59d8', '\u4e52',
+ '\u948b', '\u5256', '\u4ec6', '\u4e03', '\u6390', '\u5343', '\u545b', '\u6084',
+ '\u5207', '\u4eb2', '\u9751', '\u5b86', '\u74d7', '\u533a', '\u5cd1', '\u7094',
+ '\u590b', '\u5465', '\u7a63', '\u835b', '\u60f9', '\u4eba', '\u6254', '\u65e5',
+ '\u620e', '\u53b9', '\u909a', '\u5827', '\u6875', '\u95f0', '\u633c', '\u4ee8',
+ '\u6be2', '\u4e09', '\u6852', '\u63bb', '\u8272', '\u68ee', '\u50e7', '\u6740',
+ '\u7b5b', '\u5c71', '\u4f24', '\u5f30', '\u5962', '\u7533', '\u5347', '\u5c38',
+ '\u53ce', '\u4e66', '\u5237', '\u8870', '\u95e9', '\u53cc', '\u8c01', '\u542e',
+ '\u8bf4', '\u53b6', '\u5fea', '\u51c1', '\u82cf', '\u72fb', '\u590a', '\u5b59',
+ '\u5506', '\u4ed6', '\u5b61', '\u574d', '\u6c64', '\u5932', '\u5fd1', '\u81af',
+ '\u5254', '\u5929', '\u65eb', '\u6017', '\u5385', '\u70b5', '\u5077', '\u51f8',
+ '\u6e4d', '\u63a8', '\u541e', '\u8bac', '\u52b8', '\u6b6a', '\u5f2f', '\u5c23',
+ '\u5371', '\u6637', '\u7fc1', '\u631d', '\u4e4c', '\u5915', '\u5477', '\u4ed9',
+ '\u4e61', '\u7071', '\u4e9b', '\u5fc3', '\u5174', '\u51f6', '\u4f11', '\u620c',
+ '\u5405', '\u75b6', '\u7025', '\u4e2b', '\u54bd', '\u592e', '\u5e7a', '\u503b',
+ '\u4e00', '\u4e5a', '\u5e94', '\u5537', '\u4f63', '\u4f18', '\u7ea1', '\u56e6',
+ '\u66f0', '\u8480', '\u5e00', '\u707d', '\u5142', '\u7242', '\u50ae', '\u556b',
+ '\u9c61', '\u600e', '\u66fd', '\u5412', '\u635a', '\u6cbe', '\u5f20', '\u4f4b',
+ '\u8707', '\u8d1e', '\u9eee', '\u4e4b', '\u4e2d', '\u5dde', '\u6731', '\u6293',
+ '\u62fd', '\u4e13', '\u5986', '\u96b9', '\u5b92', '\u5353', '\u4ed4', '\u5b97',
+ '\u90b9', '\u79df', '\u5297', '\u55fa', '\u5c0a', '\u6628',
+ };
+ private final static byte[][] PINYINS = {
+ {65, 00, 00, 00, 00, 00, }, {65, 73, 00, 00, 00, 00, },
+ {65, 78, 00, 00, 00, 00, }, {65, 78, 71, 00, 00, 00, },
+ {65, 79, 00, 00, 00, 00, }, {66, 65, 00, 00, 00, 00, },
+ {66, 65, 73, 00, 00, 00, }, {66, 65, 78, 00, 00, 00, },
+ {66, 65, 78, 71, 00, 00, }, {66, 65, 79, 00, 00, 00, },
+ {66, 69, 73, 00, 00, 00, }, {66, 69, 78, 00, 00, 00, },
+ {66, 69, 78, 71, 00, 00, }, {66, 73, 00, 00, 00, 00, },
+ {66, 73, 65, 78, 00, 00, }, {66, 73, 65, 79, 00, 00, },
+ {66, 73, 69, 00, 00, 00, }, {66, 73, 78, 00, 00, 00, },
+ {66, 73, 78, 71, 00, 00, }, {66, 79, 00, 00, 00, 00, },
+ {66, 85, 00, 00, 00, 00, }, {67, 65, 00, 00, 00, 00, },
+ {67, 65, 73, 00, 00, 00, }, {67, 65, 78, 00, 00, 00, },
+ {67, 65, 78, 71, 00, 00, }, {67, 65, 79, 00, 00, 00, },
+ {67, 69, 00, 00, 00, 00, }, {67, 69, 78, 00, 00, 00, },
+ {67, 69, 78, 71, 00, 00, }, {67, 72, 65, 00, 00, 00, },
+ {67, 72, 65, 73, 00, 00, }, {67, 72, 65, 78, 00, 00, },
+ {67, 72, 65, 78, 71, 00, }, {67, 72, 65, 79, 00, 00, },
+ {67, 72, 69, 00, 00, 00, }, {67, 72, 69, 78, 00, 00, },
+ {67, 72, 69, 78, 71, 00, }, {67, 72, 73, 00, 00, 00, },
+ {67, 72, 79, 78, 71, 00, }, {67, 72, 79, 85, 00, 00, },
+ {67, 72, 85, 00, 00, 00, }, {67, 72, 85, 65, 73, 00, },
+ {67, 72, 85, 65, 78, 00, }, {67, 72, 85, 65, 78, 71, },
+ {67, 72, 85, 73, 00, 00, }, {67, 72, 85, 78, 00, 00, },
+ {67, 72, 85, 79, 00, 00, }, {67, 73, 00, 00, 00, 00, },
+ {67, 79, 78, 71, 00, 00, }, {67, 79, 85, 00, 00, 00, },
+ {67, 85, 00, 00, 00, 00, }, {67, 85, 65, 78, 00, 00, },
+ {67, 85, 73, 00, 00, 00, }, {67, 85, 78, 00, 00, 00, },
+ {67, 85, 79, 00, 00, 00, }, {68, 65, 00, 00, 00, 00, },
+ {68, 65, 73, 00, 00, 00, }, {68, 65, 78, 00, 00, 00, },
+ {68, 65, 78, 71, 00, 00, }, {68, 65, 79, 00, 00, 00, },
+ {68, 69, 00, 00, 00, 00, }, {68, 69, 78, 00, 00, 00, },
+ {68, 69, 78, 71, 00, 00, }, {68, 73, 00, 00, 00, 00, },
+ {68, 73, 65, 00, 00, 00, }, {68, 73, 65, 78, 00, 00, },
+ {68, 73, 65, 79, 00, 00, }, {68, 73, 69, 00, 00, 00, },
+ {68, 73, 78, 71, 00, 00, }, {68, 73, 85, 00, 00, 00, },
+ {68, 79, 78, 71, 00, 00, }, {68, 79, 85, 00, 00, 00, },
+ {68, 85, 00, 00, 00, 00, }, {68, 85, 65, 78, 00, 00, },
+ {68, 85, 73, 00, 00, 00, }, {68, 85, 78, 00, 00, 00, },
+ {68, 85, 79, 00, 00, 00, }, {69, 00, 00, 00, 00, 00, },
+ {69, 78, 00, 00, 00, 00, }, {69, 78, 71, 00, 00, 00, },
+ {69, 82, 00, 00, 00, 00, }, {70, 65, 00, 00, 00, 00, },
+ {70, 65, 78, 00, 00, 00, }, {70, 65, 78, 71, 00, 00, },
+ {70, 69, 73, 00, 00, 00, }, {70, 69, 78, 00, 00, 00, },
+ {70, 69, 78, 71, 00, 00, }, {70, 73, 65, 79, 00, 00, },
+ {70, 79, 00, 00, 00, 00, }, {70, 85, 00, 00, 00, 00, },
+ {70, 79, 85, 00, 00, 00, }, {70, 85, 00, 00, 00, 00, },
+ {71, 85, 73, 00, 00, 00, }, {71, 65, 00, 00, 00, 00, },
+ {71, 65, 73, 00, 00, 00, }, {71, 65, 78, 00, 00, 00, },
+ {71, 65, 78, 71, 00, 00, }, {71, 65, 79, 00, 00, 00, },
+ {71, 69, 00, 00, 00, 00, }, {71, 69, 73, 00, 00, 00, },
+ {71, 69, 78, 00, 00, 00, }, {71, 69, 78, 71, 00, 00, },
+ {74, 73, 69, 00, 00, 00, }, {71, 69, 00, 00, 00, 00, },
+ {71, 79, 78, 71, 00, 00, }, {71, 79, 85, 00, 00, 00, },
+ {71, 85, 00, 00, 00, 00, }, {71, 85, 65, 00, 00, 00, },
+ {71, 85, 65, 73, 00, 00, }, {71, 85, 65, 78, 00, 00, },
+ {71, 85, 65, 78, 71, 00, }, {71, 85, 73, 00, 00, 00, },
+ {71, 85, 78, 00, 00, 00, }, {71, 85, 65, 78, 00, 00, },
+ {71, 85, 79, 00, 00, 00, }, {72, 65, 00, 00, 00, 00, },
+ {72, 65, 73, 00, 00, 00, }, {72, 65, 78, 00, 00, 00, },
+ {72, 65, 78, 71, 00, 00, }, {72, 65, 79, 00, 00, 00, },
+ {72, 69, 00, 00, 00, 00, }, {72, 69, 73, 00, 00, 00, },
+ {72, 69, 78, 00, 00, 00, }, {72, 69, 78, 71, 00, 00, },
+ {72, 79, 78, 71, 00, 00, }, {72, 79, 85, 00, 00, 00, },
+ {72, 85, 00, 00, 00, 00, }, {72, 85, 65, 00, 00, 00, },
+ {72, 85, 65, 73, 00, 00, }, {72, 85, 65, 78, 00, 00, },
+ {72, 85, 65, 78, 71, 00, }, {72, 85, 73, 00, 00, 00, },
+ {72, 85, 78, 00, 00, 00, }, {72, 85, 79, 00, 00, 00, },
+ {74, 73, 00, 00, 00, 00, }, {74, 73, 65, 00, 00, 00, },
+ {74, 73, 65, 78, 00, 00, }, {74, 73, 65, 78, 71, 00, },
+ {74, 73, 65, 79, 00, 00, }, {74, 73, 69, 00, 00, 00, },
+ {74, 73, 78, 00, 00, 00, }, {74, 73, 78, 71, 00, 00, },
+ {74, 73, 79, 78, 71, 00, }, {74, 73, 85, 00, 00, 00, },
+ {74, 85, 00, 00, 00, 00, }, {74, 85, 65, 78, 00, 00, },
+ {74, 85, 69, 00, 00, 00, }, {74, 85, 78, 00, 00, 00, },
+ {75, 65, 00, 00, 00, 00, }, {75, 65, 73, 00, 00, 00, },
+ {75, 65, 78, 00, 00, 00, }, {75, 65, 78, 71, 00, 00, },
+ {75, 65, 79, 00, 00, 00, }, {75, 69, 00, 00, 00, 00, },
+ {75, 69, 78, 00, 00, 00, }, {75, 69, 78, 71, 00, 00, },
+ {75, 79, 78, 71, 00, 00, }, {75, 79, 85, 00, 00, 00, },
+ {75, 85, 00, 00, 00, 00, }, {75, 85, 65, 00, 00, 00, },
+ {75, 85, 65, 73, 00, 00, }, {75, 85, 65, 78, 00, 00, },
+ {75, 85, 65, 78, 71, 00, }, {75, 85, 73, 00, 00, 00, },
+ {75, 85, 78, 00, 00, 00, }, {75, 85, 79, 00, 00, 00, },
+ {76, 65, 00, 00, 00, 00, }, {76, 65, 73, 00, 00, 00, },
+ {76, 65, 78, 00, 00, 00, }, {76, 65, 78, 71, 00, 00, },
+ {76, 65, 79, 00, 00, 00, }, {76, 69, 00, 00, 00, 00, },
+ {76, 69, 73, 00, 00, 00, }, {76, 73, 00, 00, 00, 00, },
+ {76, 73, 78, 71, 00, 00, }, {76, 69, 78, 71, 00, 00, },
+ {76, 73, 00, 00, 00, 00, }, {76, 73, 65, 00, 00, 00, },
+ {76, 73, 65, 78, 00, 00, }, {76, 73, 65, 78, 71, 00, },
+ {76, 73, 65, 79, 00, 00, }, {76, 73, 69, 00, 00, 00, },
+ {76, 73, 78, 00, 00, 00, }, {76, 73, 78, 71, 00, 00, },
+ {76, 73, 85, 00, 00, 00, }, {76, 79, 78, 71, 00, 00, },
+ {76, 79, 85, 00, 00, 00, }, {76, 85, 00, 00, 00, 00, },
+ {76, 85, 65, 78, 00, 00, }, {76, 85, 78, 00, 00, 00, },
+ {76, 85, 79, 00, 00, 00, }, {77, 65, 00, 00, 00, 00, },
+ {77, 65, 73, 00, 00, 00, }, {77, 65, 78, 00, 00, 00, },
+ {77, 65, 78, 71, 00, 00, }, {77, 65, 79, 00, 00, 00, },
+ {77, 69, 73, 00, 00, 00, }, {77, 69, 78, 00, 00, 00, },
+ {77, 69, 78, 71, 00, 00, }, {77, 73, 00, 00, 00, 00, },
+ {77, 73, 65, 78, 00, 00, }, {77, 73, 65, 79, 00, 00, },
+ {77, 73, 69, 00, 00, 00, }, {77, 73, 78, 00, 00, 00, },
+ {77, 73, 78, 71, 00, 00, }, {77, 73, 85, 00, 00, 00, },
+ {77, 79, 00, 00, 00, 00, }, {77, 79, 85, 00, 00, 00, },
+ {77, 85, 00, 00, 00, 00, }, {78, 65, 00, 00, 00, 00, },
+ {78, 65, 73, 00, 00, 00, }, {78, 65, 78, 00, 00, 00, },
+ {78, 65, 78, 71, 00, 00, }, {78, 65, 79, 00, 00, 00, },
+ {78, 69, 00, 00, 00, 00, }, {78, 69, 73, 00, 00, 00, },
+ {78, 69, 78, 00, 00, 00, }, {78, 69, 78, 71, 00, 00, },
+ {78, 73, 00, 00, 00, 00, }, {78, 73, 65, 78, 00, 00, },
+ {78, 73, 65, 78, 71, 00, }, {78, 73, 65, 79, 00, 00, },
+ {78, 73, 69, 00, 00, 00, }, {78, 73, 78, 00, 00, 00, },
+ {78, 73, 78, 71, 00, 00, }, {78, 73, 85, 00, 00, 00, },
+ {78, 79, 78, 71, 00, 00, }, {78, 79, 85, 00, 00, 00, },
+ {78, 85, 00, 00, 00, 00, }, {78, 85, 65, 78, 00, 00, },
+ {78, 85, 78, 00, 00, 00, }, {78, 85, 79, 00, 00, 00, },
+ {79, 00, 00, 00, 00, 00, }, {79, 85, 00, 00, 00, 00, },
+ {80, 65, 00, 00, 00, 00, }, {80, 65, 73, 00, 00, 00, },
+ {80, 65, 78, 00, 00, 00, }, {80, 65, 78, 71, 00, 00, },
+ {80, 65, 79, 00, 00, 00, }, {80, 69, 73, 00, 00, 00, },
+ {80, 69, 78, 00, 00, 00, }, {80, 69, 78, 71, 00, 00, },
+ {80, 73, 00, 00, 00, 00, }, {80, 73, 65, 78, 00, 00, },
+ {80, 73, 65, 79, 00, 00, }, {80, 73, 69, 00, 00, 00, },
+ {80, 73, 78, 00, 00, 00, }, {80, 73, 78, 71, 00, 00, },
+ {80, 79, 00, 00, 00, 00, }, {80, 79, 85, 00, 00, 00, },
+ {80, 85, 00, 00, 00, 00, }, {81, 73, 00, 00, 00, 00, },
+ {81, 73, 65, 00, 00, 00, }, {81, 73, 65, 78, 00, 00, },
+ {81, 73, 65, 78, 71, 00, }, {81, 73, 65, 79, 00, 00, },
+ {81, 73, 69, 00, 00, 00, }, {81, 73, 78, 00, 00, 00, },
+ {81, 73, 78, 71, 00, 00, }, {81, 73, 79, 78, 71, 00, },
+ {81, 73, 85, 00, 00, 00, }, {81, 85, 00, 00, 00, 00, },
+ {81, 85, 65, 78, 00, 00, }, {81, 85, 69, 00, 00, 00, },
+ {81, 85, 78, 00, 00, 00, }, {82, 65, 78, 00, 00, 00, },
+ {82, 65, 78, 71, 00, 00, }, {82, 65, 79, 00, 00, 00, },
+ {82, 69, 00, 00, 00, 00, }, {82, 69, 78, 00, 00, 00, },
+ {82, 69, 78, 71, 00, 00, }, {82, 73, 00, 00, 00, 00, },
+ {82, 79, 78, 71, 00, 00, }, {82, 79, 85, 00, 00, 00, },
+ {82, 85, 00, 00, 00, 00, }, {82, 85, 65, 78, 00, 00, },
+ {82, 85, 73, 00, 00, 00, }, {82, 85, 78, 00, 00, 00, },
+ {82, 85, 79, 00, 00, 00, }, {83, 65, 00, 00, 00, 00, },
+ {83, 65, 73, 00, 00, 00, }, {83, 65, 78, 00, 00, 00, },
+ {83, 65, 78, 71, 00, 00, }, {83, 65, 79, 00, 00, 00, },
+ {83, 69, 00, 00, 00, 00, }, {83, 69, 78, 00, 00, 00, },
+ {83, 69, 78, 71, 00, 00, }, {83, 72, 65, 00, 00, 00, },
+ {83, 72, 65, 73, 00, 00, }, {83, 72, 65, 78, 00, 00, },
+ {83, 72, 65, 78, 71, 00, }, {83, 72, 65, 79, 00, 00, },
+ {83, 72, 69, 00, 00, 00, }, {83, 72, 69, 78, 00, 00, },
+ {83, 72, 69, 78, 71, 00, }, {83, 72, 73, 00, 00, 00, },
+ {83, 72, 79, 85, 00, 00, }, {83, 72, 85, 00, 00, 00, },
+ {83, 72, 85, 65, 00, 00, }, {83, 72, 85, 65, 73, 00, },
+ {83, 72, 85, 65, 78, 00, }, {83, 72, 85, 65, 78, 71, },
+ {83, 72, 85, 73, 00, 00, }, {83, 72, 85, 78, 00, 00, },
+ {83, 72, 85, 79, 00, 00, }, {83, 73, 00, 00, 00, 00, },
+ {83, 79, 78, 71, 00, 00, }, {83, 79, 85, 00, 00, 00, },
+ {83, 85, 00, 00, 00, 00, }, {83, 85, 65, 78, 00, 00, },
+ {83, 85, 73, 00, 00, 00, }, {83, 85, 78, 00, 00, 00, },
+ {83, 85, 79, 00, 00, 00, }, {84, 65, 00, 00, 00, 00, },
+ {84, 65, 73, 00, 00, 00, }, {84, 65, 78, 00, 00, 00, },
+ {84, 65, 78, 71, 00, 00, }, {84, 65, 79, 00, 00, 00, },
+ {84, 69, 00, 00, 00, 00, }, {84, 69, 78, 71, 00, 00, },
+ {84, 73, 00, 00, 00, 00, }, {84, 73, 65, 78, 00, 00, },
+ {84, 73, 65, 79, 00, 00, }, {84, 73, 69, 00, 00, 00, },
+ {84, 73, 78, 71, 00, 00, }, {84, 79, 78, 71, 00, 00, },
+ {84, 79, 85, 00, 00, 00, }, {84, 85, 00, 00, 00, 00, },
+ {84, 85, 65, 78, 00, 00, }, {84, 85, 73, 00, 00, 00, },
+ {84, 85, 78, 00, 00, 00, }, {84, 85, 79, 00, 00, 00, },
+ {87, 65, 00, 00, 00, 00, }, {87, 65, 73, 00, 00, 00, },
+ {87, 65, 78, 00, 00, 00, }, {87, 65, 78, 71, 00, 00, },
+ {87, 69, 73, 00, 00, 00, }, {87, 69, 78, 00, 00, 00, },
+ {87, 69, 78, 71, 00, 00, }, {87, 79, 00, 00, 00, 00, },
+ {87, 85, 00, 00, 00, 00, }, {88, 73, 00, 00, 00, 00, },
+ {88, 73, 65, 00, 00, 00, }, {88, 73, 65, 78, 00, 00, },
+ {88, 73, 65, 78, 71, 00, }, {88, 73, 65, 79, 00, 00, },
+ {88, 73, 69, 00, 00, 00, }, {88, 73, 78, 00, 00, 00, },
+ {88, 73, 78, 71, 00, 00, }, {88, 73, 79, 78, 71, 00, },
+ {88, 73, 85, 00, 00, 00, }, {88, 85, 00, 00, 00, 00, },
+ {88, 85, 65, 78, 00, 00, }, {88, 85, 69, 00, 00, 00, },
+ {88, 85, 78, 00, 00, 00, }, {89, 65, 00, 00, 00, 00, },
+ {89, 65, 78, 00, 00, 00, }, {89, 65, 78, 71, 00, 00, },
+ {89, 65, 79, 00, 00, 00, }, {89, 69, 00, 00, 00, 00, },
+ {89, 73, 00, 00, 00, 00, }, {89, 73, 78, 00, 00, 00, },
+ {89, 73, 78, 71, 00, 00, }, {89, 79, 00, 00, 00, 00, },
+ {89, 79, 78, 71, 00, 00, }, {89, 79, 85, 00, 00, 00, },
+ {89, 85, 00, 00, 00, 00, }, {89, 85, 65, 78, 00, 00, },
+ {89, 85, 69, 00, 00, 00, }, {89, 85, 78, 00, 00, 00, },
+ {90, 65, 00, 00, 00, 00, }, {90, 65, 73, 00, 00, 00, },
+ {90, 65, 78, 00, 00, 00, }, {90, 65, 78, 71, 00, 00, },
+ {90, 65, 79, 00, 00, 00, }, {90, 69, 00, 00, 00, 00, },
+ {90, 69, 73, 00, 00, 00, }, {90, 69, 78, 00, 00, 00, },
+ {90, 69, 78, 71, 00, 00, }, {90, 72, 65, 00, 00, 00, },
+ {90, 72, 65, 73, 00, 00, }, {90, 72, 65, 78, 00, 00, },
+ {90, 72, 65, 78, 71, 00, }, {90, 72, 65, 79, 00, 00, },
+ {90, 72, 69, 00, 00, 00, }, {90, 72, 69, 78, 00, 00, },
+ {90, 72, 69, 78, 71, 00, }, {90, 72, 73, 00, 00, 00, },
+ {90, 72, 79, 78, 71, 00, }, {90, 72, 79, 85, 00, 00, },
+ {90, 72, 85, 00, 00, 00, }, {90, 72, 85, 65, 00, 00, },
+ {90, 72, 85, 65, 73, 00, }, {90, 72, 85, 65, 78, 00, },
+ {90, 72, 85, 65, 78, 71, }, {90, 72, 85, 73, 00, 00, },
+ {90, 72, 85, 78, 00, 00, }, {90, 72, 85, 79, 00, 00, },
+ {90, 73, 00, 00, 00, 00, }, {90, 79, 78, 71, 00, 00, },
+ {90, 79, 85, 00, 00, 00, }, {90, 85, 00, 00, 00, 00, },
+ {90, 85, 65, 78, 00, 00, }, {90, 85, 73, 00, 00, 00, },
+ {90, 85, 78, 00, 00, 00, }, {90, 85, 79, 00, 00, 00, },
+
+ };
+
+ /** First and last Chinese character with known Pinyin according to zh collation */
+ private static final String FIRST_PINYIN_UNIHAN = "\u5416";
+ private static final String LAST_PINYIN_UNIHAN = "\u5497";
+ /** The first Chinese character in Unicode block */
+ private static final char FIRST_UNIHAN = '\u3400';
+ private static final Collator COLLATOR = Collator.getInstance(Locale.CHINA);
+
+ private static HanziToPinyin sInstance;
+ private final boolean mHasChinaCollator;
+
+ public static class Token {
+ /**
+ * Separator between target string for each source char
+ */
+ public static final String SEPARATOR = " ";
+
+ public static final int LATIN = 1;
+ public static final int PINYIN = 2;
+ public static final int UNKNOWN = 3;
+
+ public Token() {
+ }
+
+ public Token(int type, String source, String target) {
+ this.type = type;
+ this.source = source;
+ this.target = target;
+ }
+ /**
+ * Type of this token, ASCII, PINYIN or UNKNOWN.
+ */
+ public int type;
+ /**
+ * Original string before translation.
+ */
+ public String source;
+ /**
+ * Translated string of source. For Han, target is corresponding Pinyin.
+ * Otherwise target is original string in source.
+ */
+ public String target;
+ }
+
+ protected HanziToPinyin(boolean hasChinaCollator) {
+ mHasChinaCollator = hasChinaCollator;
+ }
+
+ public static HanziToPinyin getInstance() {
+ synchronized(HanziToPinyin.class) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+ // Check if zh_CN collation data is available
+ final Locale locale[] = Collator.getAvailableLocales();
+ for (int i = 0; i < locale.length; i++) {
+ if (locale[i].equals(Locale.CHINA)) {
+ sInstance = new HanziToPinyin(true);
+ return sInstance;
+ }
+ }
+ Log.w(TAG, "There is no Chinese collator, HanziToPinyin is disabled");
+ sInstance = new HanziToPinyin(false);
+ return sInstance;
+ }
+ }
+
+ private Token getToken(char character) {
+ Token token = new Token();
+ final String letter = Character.toString(character);
+ token.source = letter;
+ int offset = -1;
+ int cmp;
+ if (character < 256) {
+ token.type = Token.LATIN;
+ token.target = letter;
+ return token;
+ } else if (character < FIRST_UNIHAN) {
+ token.type = Token.UNKNOWN;
+ token.target = letter;
+ return token;
+ } else {
+ cmp = COLLATOR.compare(letter, FIRST_PINYIN_UNIHAN);
+ if (cmp < 0) {
+ token.type = Token.UNKNOWN;
+ token.target = letter;
+ return token;
+ } else if (cmp == 0) {
+ token.type = Token.PINYIN;
+ offset = 0;
+ } else {
+ cmp = COLLATOR.compare(letter, LAST_PINYIN_UNIHAN);
+ if (cmp > 0) {
+ token.type = Token.UNKNOWN;
+ token.target = letter;
+ return token;
+ } else if (cmp == 0) {
+ token.type = Token.PINYIN;
+ offset = UNIHANS.length - 1;
+ }
+ }
+ }
+
+ token.type = Token.PINYIN;
+ if (offset < 0) {
+ int begin = 0;
+ int end = UNIHANS.length - 1;
+ while (begin <= end) {
+ offset = (begin + end) / 2;
+ final String unihan = Character.toString(UNIHANS[offset]);
+ cmp = COLLATOR.compare(letter, unihan);
+ if (cmp == 0) {
+ break;
+ } else if (cmp > 0) {
+ begin = offset + 1;
+ } else {
+ end = offset - 1;
+ }
+ }
+ }
+ if (cmp < 0) {
+ offset--;
+ }
+ StringBuilder pinyin = new StringBuilder();
+ for (int j = 0; j < PINYINS[offset].length && PINYINS[offset][j] != 0; j++) {
+ pinyin.append((char)PINYINS[offset][j]);
+ }
+ token.target = pinyin.toString();
+ return token;
+ }
+
+ /**
+ * Convert the input to a array of tokens. The sequence of ASCII or Unknown
+ * characters without space will be put into a Token, One Hanzi character
+ * which has pinyin will be treated as a Token.
+ * If these is no China collator, the empty token array is returned.
+ */
+ public ArrayList<Token> get(final String input) {
+ ArrayList<Token> tokens = new ArrayList<Token>();
+ if (!mHasChinaCollator || TextUtils.isEmpty(input)) {
+ // return empty tokens.
+ return tokens;
+ }
+ final int inputLength = input.length();
+ final StringBuilder sb = new StringBuilder();
+ int tokenType = Token.LATIN;
+ // Go through the input, create a new token when
+ // a. Token type changed
+ // b. Get the Pinyin of current charater.
+ // c. current character is space.
+ for (int i = 0; i < inputLength; i++) {
+ final char character = input.charAt(i);
+ if (character == ' ') {
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ } else if (character < 256) {
+ if (tokenType != Token.LATIN && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = Token.LATIN;
+ sb.append(character);
+ } else if (character < FIRST_UNIHAN) {
+ if (tokenType != Token.UNKNOWN && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = Token.UNKNOWN;
+ sb.append(character);
+ } else {
+ Token t = getToken(character);
+ if (t.type == Token.PINYIN) {
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokens.add(t);
+ tokenType = Token.PINYIN;
+ } else {
+ if (tokenType != t.type && sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ tokenType = t.type;
+ sb.append(character);
+ }
+ }
+ }
+ if (sb.length() > 0) {
+ addToken(sb, tokens, tokenType);
+ }
+ return tokens;
+ }
+
+ private void addToken(final StringBuilder sb, final ArrayList<Token> tokens,
+ final int tokenType) {
+ String str = sb.toString();
+ tokens.add(new Token(tokenType, str, str));
+ sb.setLength(0);
+ }
+
+}
diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/HierarchicalState.java
new file mode 100644
index 0000000..b37f46c
--- /dev/null
+++ b/core/java/com/android/internal/util/HierarchicalState.java
@@ -0,0 +1,76 @@
+/**
+ * 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 com.android.internal.util;
+
+import android.os.Message;
+
+/**
+ * {@hide}
+ *
+ * The class for implementing states in a HierarchicalStateMachine
+ */
+public class HierarchicalState {
+
+ /**
+ * Constructor
+ */
+ protected HierarchicalState() {
+ }
+
+ /**
+ * Called when a state is entered.
+ */
+ protected void enter() {
+ }
+
+ /**
+ * Called when a message is to be processed by the
+ * state machine.
+ *
+ * This routine is never reentered thus no synchronization
+ * is needed as only one processMessage method will ever be
+ * executing within a state machine at any given time. This
+ * does mean that processing by this routine must be completed
+ * as expeditiously as possible as no subsequent messages will
+ * be processed until this routine returns.
+ *
+ * @param msg to process
+ * @return true if processing has completed and false
+ * if the parent state's processMessage should
+ * be invoked.
+ */
+ protected boolean processMessage(Message msg) {
+ return false;
+ }
+
+ /**
+ * Called when a state is exited.
+ */
+ protected void exit() {
+ }
+
+ /**
+ * @return name of state, but default returns the states
+ * class name. An instance name would be better but requiring
+ * it seems unnecessary.
+ */
+ public String getName() {
+ String name = getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+}
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
new file mode 100644
index 0000000..9911f48
--- /dev/null
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -0,0 +1,1307 @@
+/**
+ * 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 com.android.internal.util;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * {@hide}
+ *
+ * A hierarchical state machine is a state machine which processes messages
+ * and can have states arranged hierarchically. A state is a <code>HierarchicalState</code>
+ * object and must implement <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
+ * The enter/exit methods are equivalent to the construction and destruction
+ * in Object Oriented programming and are used to perform initialization and
+ * cleanup of the state respectively. The <code>getName</code> method returns the
+ * name of the state the default implementation returns the class name it may be
+ * desirable to have this return the name of the state instance name instead.
+ * In particular if a particular state class has multiple instances.
+ *
+ * When a state machine is created <code>addState</code> is used to build the
+ * hierarchy and <code>setInitialState</code> is used to identify which of these
+ * is the initial state. After construction the programmer calls <code>start</code>
+ * which initializes the state machine and calls <code>enter</code> for all of the initial
+ * state's hierarchy, starting at its eldest parent. For example given the simple
+ * state machine below after start is called mP1.enter will have been called and
+ * then mS1.enter.
+<code>
+ mP1
+ / \
+ mS2 mS1 ----> initial state
+</code>
+ * After the state machine is created and started, messages are sent to a state
+ * machine using <code>sendMessage</code and the messages are created using
+ * <code>obtainMessage</code>. When the state machine receives a message the
+ * current state's <code>processMessage</code> is invoked. In the above example
+ * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
+ * to change the current state to a new state
+ *
+ * Each state in the state machine may have a zero or one parent states and if
+ * a child state is unable to handle a message it may have the message processed
+ * by its parent by returning false. If a message is never processed <code>unhandledMessage</code>
+ * will be invoked to give one last chance for the state machine to process
+ * the message.
+ *
+ * When all processing is completed a state machine may choose to call
+ * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
+ * returns the state machine will transfer to an internal <code>HaltingState</code>
+ * and invoke <code>halting</code>. Any message subsequently received by the state
+ * machine will cause <code>haltedProcessMessage</code> to be invoked.
+ *
+ * If it is desirable to completely stop the state machine call <code>quit</code>. This
+ * will exit the current state and its parent and then exit from the controlling thread
+ * and no further messages will be processed.
+ *
+ * In addition to <code>processMessage</code> each <code>HierarchicalState</code> has
+ * an <code>enter</code> method and <code>exit</exit> method which may be overridden.
+ *
+ * Since the states are arranged in a hierarchy transitioning to a new state
+ * causes current states to be exited and new states to be entered. To determine
+ * the list of states to be entered/exited the common parent closest to
+ * the current state is found. We then exit from the current state and its
+ * parent's up to but not including the common parent state and then enter all
+ * of the new states below the common parent down to the destination state.
+ * If there is no common parent all states are exited and then the new states
+ * are entered.
+ *
+ * Two other methods that states can use are <code>deferMessage</code> and
+ * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends
+ * a message but places it on the front of the queue rather than the back. The
+ * <code>deferMessage</code> causes the message to be saved on a list until a
+ * transition is made to a new state. At which time all of the deferred messages
+ * will be put on the front of the state machine queue with the oldest message
+ * at the front. These will then be processed by the new current state before
+ * any other messages that are on the queue or might be added later. Both of
+ * these are protected and may only be invoked from within a state machine.
+ *
+ * To illustrate some of these properties we'll use state machine with 8
+ * state hierarchy:
+<code>
+ mP0
+ / \
+ mP1 mS0
+ / \
+ mS2 mS1
+ / \ \
+ mS3 mS4 mS5 ---> initial state
+</code>
+ *
+ * After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
+ * So the order of calling processMessage when a message is received is mS5,
+ * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
+ * message by returning false.
+ *
+ * Now assume mS5.processMessage receives a message it can handle, and during
+ * the handling determines the machine should changes states. It would call
+ * transitionTo(mS4) and return true. Immediately after returning from
+ * processMessage the state machine runtime will find the common parent,
+ * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
+ * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
+ * when the next message is received mS4.processMessage will be invoked.
+ *
+ * To assist in describing an HSM a simple grammar has been created which
+ * is informally defined here and a formal EBNF description is at the end
+ * of the class comment.
+ *
+ * An HSM starts with the name and includes a set of hierarchical states.
+ * A state is preceeded by one or more plus signs (+), to indicate its
+ * depth and a hash (#) if its the initial state. Child states follow their
+ * parents and have one more plus sign then their parent. Inside a state
+ * are a series of messages, the actions they perform and if the processing
+ * is complete ends with a period (.). If processing isn't complete and
+ * the parent should process the message it ends with a caret (^). The
+ * actions include send a message ($MESSAGE), defer a message (%MESSAGE),
+ * transition to a new state (>MESSAGE) and an if statement
+ * (if ( expression ) { list of actions }.)
+ *
+ * The Hsm HelloWorld could documented as:
+ *
+ * HelloWorld {
+ * + # mState1.
+ * }
+ *
+ * and interpreted as HSM HelloWorld:
+ *
+ * mState1 a root state (single +) and initial state (#) which
+ * processes all messages completely, the period (.).
+ *
+ * The implementation is:
+<code>
+class HelloWorld extends HierarchicalStateMachine {
+ Hsm1(String name) {
+ super(name);
+ addState(mState1);
+ setInitialState(mState1);
+ }
+
+ public static HelloWorld makeHelloWorld() {
+ HelloWorld hw = new HelloWorld("hw");
+ hw.start();
+ return hw;
+ }
+
+ class State1 extends HierarchicalState {
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "Hello World");
+ return true;
+ }
+ }
+ State1 mState1 = new State1();
+}
+
+void testHelloWorld() {
+ HelloWorld hw = makeHelloWorld();
+ hw.sendMessage(hw.obtainMessage());
+}
+</code>
+ *
+ * A more interesting state machine is one of four states
+ * with two independent parent states.
+<code>
+ mP1 mP2
+ / \
+ mS2 mS1
+</code>
+ *
+ * documented as:
+ *
+ * Hsm1 {
+ * + mP1 {
+ * CMD_2 {
+ * $CMD_3
+ * %CMD_2
+ * >mS2
+ * }.
+ * }
+ * ++ # mS1 { CMD_1{ >mS1 }^ }
+ * ++ mS2 {
+ * CMD_2{$CMD_4}.
+ * CMD_3{%CMD_3 ; >mP2}.
+ * }
+ *
+ * + mP2 e($CMD_5) {
+ * CMD_3, CMD_4.
+ * CMD_5{>HALT}.
+ * }
+ * }
+ *
+ * and interpreted as HierarchicalStateMachine Hsm1:
+ *
+ * mP1 a root state.
+ * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2
+ *
+ * mS1 a child of mP1 is the initial state:
+ * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it.
+ *
+ * mS2 a child of mP1:
+ * processes message CMD_2 which send CMD_4
+ * processes message CMD_3 which defers CMD_3 and transitions to mP2
+ *
+ * mP2 a root state.
+ * on enter it sends CMD_5
+ * processes message CMD_3
+ * processes message CMD_4
+ * processes message CMD_5 which transitions to halt state
+ *
+ * The implementation is below and also in HierarchicalStateMachineTest:
+<code>
+class Hsm1 extends HierarchicalStateMachine {
+ private static final String TAG = "hsm1";
+
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ Log.d(TAG, "makeHsm1 E");
+ Hsm1 sm = new Hsm1("hsm1");
+ sm.start();
+ Log.d(TAG, "makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ Log.d(TAG, "ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ Log.d(TAG, "ctor X");
+ }
+
+ class P1 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mP1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "mP1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(obtainMessage(CMD_3));
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = true;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mP1.exit");
+ }
+ }
+
+ class S1 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mS1.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return true;
+ } else {
+ // Let parent process all other messages
+ return false;
+ }
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mS1.exit");
+ }
+ }
+
+ class S2 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mS2.enter");
+ }
+ @Override public boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "mS2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(obtainMessage(CMD_4));
+ retVal = true;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = true;
+ break;
+ default:
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mS2.exit");
+ }
+ }
+
+ class P2 extends HierarchicalState {
+ @Override public void enter() {
+ Log.d(TAG, "mP2.enter");
+ sendMessage(obtainMessage(CMD_5));
+ }
+ @Override public boolean processMessage(Message message) {
+ Log.d(TAG, "P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return true;
+ }
+ @Override public void exit() {
+ Log.d(TAG, "mP2.exit");
+ }
+ }
+
+ @Override
+ protected void halting() {
+ Log.d(TAG, "halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+}
+</code>
+ *
+ * If this is executed by sending two messages CMD_1 and CMD_2
+ * (Note the synchronize is only needed because we use hsm.wait())
+ *
+ * Hsm1 hsm = makeHsm1();
+ * synchronize(hsm) {
+ * hsm.sendMessage(obtainMessage(hsm.CMD_1));
+ * hsm.sendMessage(obtainMessage(hsm.CMD_2));
+ * try {
+ * // wait for the messages to be handled
+ * hsm.wait();
+ * } catch (InterruptedException e) {
+ * Log.e(TAG, "exception while waiting " + e.getMessage());
+ * }
+ * }
+ *
+ *
+ * The output is:
+ *
+ * D/hsm1 ( 1999): makeHsm1 E
+ * D/hsm1 ( 1999): ctor E
+ * D/hsm1 ( 1999): ctor X
+ * D/hsm1 ( 1999): mP1.enter
+ * D/hsm1 ( 1999): mS1.enter
+ * D/hsm1 ( 1999): makeHsm1 X
+ * D/hsm1 ( 1999): mS1.processMessage what=1
+ * D/hsm1 ( 1999): mS1.exit
+ * D/hsm1 ( 1999): mS1.enter
+ * D/hsm1 ( 1999): mS1.processMessage what=2
+ * D/hsm1 ( 1999): mP1.processMessage what=2
+ * D/hsm1 ( 1999): mS1.exit
+ * D/hsm1 ( 1999): mS2.enter
+ * D/hsm1 ( 1999): mS2.processMessage what=2
+ * D/hsm1 ( 1999): mS2.processMessage what=3
+ * D/hsm1 ( 1999): mS2.exit
+ * D/hsm1 ( 1999): mP1.exit
+ * D/hsm1 ( 1999): mP2.enter
+ * D/hsm1 ( 1999): mP2.processMessage what=3
+ * D/hsm1 ( 1999): mP2.processMessage what=4
+ * D/hsm1 ( 1999): mP2.processMessage what=5
+ * D/hsm1 ( 1999): mP2.exit
+ * D/hsm1 ( 1999): halting
+ *
+ * Here is the HSM a BNF grammar, this is a first stab at creating an
+ * HSM description language, suggestions corrections or alternatives
+ * would be much appreciated.
+ *
+ * Legend:
+ * {} ::= zero or more
+ * {}+ ::= one or more
+ * [] ::= zero or one
+ * () ::= define a group with "or" semantics.
+ *
+ * HSM EBNF:
+ * HSM = HSM_NAME "{" { STATE }+ "}" ;
+ * HSM_NAME = alpha_numeric_name ;
+ * STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ;
+ * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ;
+ * STATE_DEPTH = "+" ;
+ * INITIAL_STATE_INDICATOR = "#"
+ * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
+ * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ;
+ * MSG_LIST = { MSG_NAME { "," MSG_NAME } };
+ * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
+ * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ;
+ * SEND_ACTION = "$" MSG_NAME ;
+ * DEFER_ACTION = "%" MSG_NAME ;
+ * TRANSITION_ACTION = ">" STATE_NAME ;
+ * HALT_ACTION = ">" HALT ;
+ * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ;
+ * ACTION_LIST = ACTION { (";" | "\n") ACTION } ;
+ * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ;
+ * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}"
+ * PROCESS_IN_PARENT_OR_COMPLETE = "^" ;
+ * PROCESS_COMPLETE = "." ;
+ * STATE_NAME = alpha_numeric_name ;
+ * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ;
+ * ALL_OTHER_MESSAGES = "*" ;
+ * EXP = boolean_expression ;
+ *
+ * Idioms:
+ * * { %* }. ::= All other messages will be deferred.
+ */
+public class HierarchicalStateMachine {
+
+ private static final String TAG = "HierarchicalStateMachine";
+ private String mName;
+
+ public static final int HSM_QUIT_CMD = -1;
+
+ private static class HsmHandler extends Handler {
+
+ /** The debug flag */
+ private boolean mDbg = false;
+
+ /** The quit object */
+ private static final Object mQuitObj = new Object();
+
+ /** A list of messages that this state machine has processed */
+ private ProcessedMessages mProcessedMessages = new ProcessedMessages();
+
+ /** true if construction of the state machine has not been completed */
+ private boolean mIsConstructionCompleted;
+
+ /** Stack used to manage the current hierarchy of states */
+ private StateInfo mStateStack[];
+
+ /** Top of mStateStack */
+ private int mStateStackTopIndex = -1;
+
+ /** A temporary stack used to manage the state stack */
+ private StateInfo mTempStateStack[];
+
+ /** The top of the mTempStateStack */
+ private int mTempStateStackCount;
+
+ /** State used when state machine is halted */
+ private HaltingState mHaltingState = new HaltingState();
+
+ /** State used when state machine is quitting */
+ private QuittingState mQuittingState = new QuittingState();
+
+ /** Reference to the HierarchicalStateMachine */
+ private HierarchicalStateMachine mHsm;
+
+ /**
+ * Information about a state.
+ * Used to maintain the hierarchy.
+ */
+ private class StateInfo {
+ /** The state */
+ HierarchicalState state;
+
+ /** The parent of this state, null if there is no parent */
+ StateInfo parentStateInfo;
+
+ /** True when the state has been entered and on the stack */
+ boolean active;
+
+ /**
+ * Convert StateInfo to string
+ */
+ @Override
+ public String toString() {
+ return "state=" + state.getName() + ",active=" + active
+ + ",parent=" + ((parentStateInfo == null) ?
+ "null" : parentStateInfo.state.getName());
+ }
+ }
+
+ /** The map of all of the states in the state machine */
+ private HashMap<HierarchicalState, StateInfo> mStateInfo =
+ new HashMap<HierarchicalState, StateInfo>();
+
+ /** The initial state that will process the first message */
+ private HierarchicalState mInitialState;
+
+ /** The destination state when transitionTo has been invoked */
+ private HierarchicalState mDestState;
+
+ /** The list of deferred messages */
+ private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
+
+ /**
+ * State entered when transitionToHaltingState is called.
+ */
+ private class HaltingState extends HierarchicalState {
+ @Override
+ public boolean processMessage(Message msg) {
+ mHsm.haltedProcessMessage(msg);
+ return true;
+ }
+ }
+
+ /**
+ * State entered when a valid quit message is handled.
+ */
+ private class QuittingState extends HierarchicalState {
+ @Override
+ public boolean processMessage(Message msg) {
+ // Ignore
+ return false;
+ }
+ }
+
+ /**
+ * Handle messages sent to the state machine by calling
+ * the current state's processMessage. It also handles
+ * the enter/exit calls and placing any deferred messages
+ * back onto the queue when transitioning to a new state.
+ */
+ @Override
+ public final void handleMessage(Message msg) {
+ if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
+
+ /**
+ * Check that construction was completed
+ */
+ if (!mIsConstructionCompleted) {
+ Log.e(TAG, "The start method not called, ignore msg: " + msg);
+ return;
+ }
+
+ /**
+ * Process the message abiding by the hierarchical semantics
+ * and perform any requested transitions.
+ */
+ processMsg(msg);
+ performTransitions();
+
+ if (mDbg) Log.d(TAG, "handleMessage: X");
+ }
+
+ /**
+ * Do any transitions
+ */
+ private void performTransitions() {
+ /**
+ * If transitionTo has been called, exit and then enter
+ * the appropriate states. We loop on this to allow
+ * enter and exit methods to use transitionTo.
+ */
+ HierarchicalState destState = null;
+ while (mDestState != null) {
+ if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
+
+ /**
+ * Save mDestState locally and set to null
+ * to know if enter/exit use transitionTo.
+ */
+ destState = mDestState;
+ mDestState = null;
+
+ /**
+ * Determine the states to exit and enter and return the
+ * common ancestor state of the enter/exit states. Then
+ * invoke the exit methods then the enter methods.
+ */
+ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+ invokeExitMethods(commonStateInfo);
+ int stateStackEnteringIndex = moveTempStateStackToStateStack();
+ invokeEnterMethods(stateStackEnteringIndex);
+
+
+ /**
+ * Since we have transitioned to a new state we need to have
+ * any deferred messages moved to the front of the message queue
+ * so they will be processed before any other messages in the
+ * message queue.
+ */
+ moveDeferredMessageAtFrontOfQueue();
+ }
+
+ /**
+ * After processing all transitions check and
+ * see if the last transition was to quit or halt.
+ */
+ if (destState != null) {
+ if (destState == mQuittingState) {
+ /**
+ * We are quitting so ignore all messages.
+ */
+ mHsm.quitting();
+ if (mHsm.mHsmThread != null) {
+ // If we made the thread then quit looper
+ getLooper().quit();
+ }
+ } else if (destState == mHaltingState) {
+ /**
+ * Call halting() if we've transitioned to the halting
+ * state. All subsequent messages will be processed in
+ * in the halting state which invokes haltedProcessMessage(msg);
+ */
+ mHsm.halting();
+ }
+ }
+ }
+
+ /**
+ * Complete the construction of the state machine.
+ */
+ private final void completeConstruction() {
+ if (mDbg) Log.d(TAG, "completeConstruction: E");
+
+ /**
+ * Determine the maximum depth of the state hierarchy
+ * so we can allocate the state stacks.
+ */
+ int maxDepth = 0;
+ for (StateInfo si : mStateInfo.values()) {
+ int depth = 0;
+ for (StateInfo i = si; i != null; depth++) {
+ i = i.parentStateInfo;
+ }
+ if (maxDepth < depth) {
+ maxDepth = depth;
+ }
+ }
+ if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
+
+ mStateStack = new StateInfo[maxDepth];
+ mTempStateStack = new StateInfo[maxDepth];
+ setupInitialStateStack();
+
+ /**
+ * Construction is complete call all enter methods
+ * starting at the first entry.
+ */
+ mIsConstructionCompleted = true;
+ invokeEnterMethods(0);
+
+ /**
+ * Perform any transitions requested by the enter methods
+ */
+ performTransitions();
+
+ if (mDbg) Log.d(TAG, "completeConstruction: X");
+ }
+
+ /**
+ * Process the message. If the current state doesn't handle
+ * it, call the states parent and so on. If it is never handled then
+ * call the state machines unhandledMessage method.
+ */
+ private final void processMsg(Message msg) {
+ StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
+ if (mDbg) {
+ Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ }
+ while (!curStateInfo.state.processMessage(msg)) {
+ /**
+ * Not processed
+ */
+ curStateInfo = curStateInfo.parentStateInfo;
+ if (curStateInfo == null) {
+ /**
+ * No parents left so it's not handled
+ */
+ mHsm.unhandledMessage(msg);
+ if (isQuit(msg)) {
+ transitionTo(mQuittingState);
+ }
+ break;
+ }
+ if (mDbg) {
+ Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ }
+ }
+
+ /**
+ * Record that we processed the message
+ */
+ if (curStateInfo != null) {
+ HierarchicalState orgState = mStateStack[mStateStackTopIndex].state;
+ mProcessedMessages.add(msg, curStateInfo.state, orgState);
+ } else {
+ mProcessedMessages.add(msg, null, null);
+ }
+ }
+
+ /**
+ * Call the exit method for each state from the top of stack
+ * up to the common ancestor state.
+ */
+ private final void invokeExitMethods(StateInfo commonStateInfo) {
+ while ((mStateStackTopIndex >= 0) &&
+ (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+ HierarchicalState curState = mStateStack[mStateStackTopIndex].state;
+ if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
+ curState.exit();
+ mStateStack[mStateStackTopIndex].active = false;
+ mStateStackTopIndex -= 1;
+ }
+ }
+
+ /**
+ * Invoke the enter method starting at the entering index to top of state stack
+ */
+ private final void invokeEnterMethods(int stateStackEnteringIndex) {
+ for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+ if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
+ mStateStack[i].state.enter();
+ mStateStack[i].active = true;
+ }
+ }
+
+ /**
+ * Move the deferred message to the front of the message queue.
+ */
+ private final void moveDeferredMessageAtFrontOfQueue() {
+ /**
+ * The oldest messages on the deferred list must be at
+ * the front of the queue so start at the back, which
+ * as the most resent message and end with the oldest
+ * messages at the front of the queue.
+ */
+ for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) {
+ Message curMsg = mDeferredMessages.get(i);
+ if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+ sendMessageAtFrontOfQueue(curMsg);
+ }
+ mDeferredMessages.clear();
+ }
+
+ /**
+ * Move the contents of the temporary stack to the state stack
+ * reversing the order of the items on the temporary stack as
+ * they are moved.
+ *
+ * @return index into mStateState where entering needs to start
+ */
+ private final int moveTempStateStackToStateStack() {
+ int startingIndex = mStateStackTopIndex + 1;
+ int i = mTempStateStackCount - 1;
+ int j = startingIndex;
+ while (i >= 0) {
+ if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
+ mStateStack[j] = mTempStateStack[i];
+ j += 1;
+ i -= 1;
+ }
+
+ mStateStackTopIndex = j - 1;
+ if (mDbg) {
+ Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
+ + mStateStackTopIndex + ",startingIndex=" + startingIndex
+ + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
+ }
+ return startingIndex;
+ }
+
+ /**
+ * Setup the mTempStateStack with the states we are going to enter.
+ *
+ * This is found by searching up the destState's ancestors for a
+ * state that is already active i.e. StateInfo.active == true.
+ * The destStae and all of its inactive parents will be on the
+ * TempStateStack as the list of states to enter.
+ *
+ * @return StateInfo of the common ancestor for the destState and
+ * current state or null if there is no common parent.
+ */
+ private final StateInfo setupTempStateStackWithStatesToEnter(HierarchicalState destState) {
+ /**
+ * Search up the parent list of the destination state for an active
+ * state. Use a do while() loop as the destState must always be entered
+ * even if it is active. This can happen if we are exiting/entering
+ * the current state.
+ */
+ mTempStateStackCount = 0;
+ StateInfo curStateInfo = mStateInfo.get(destState);
+ do {
+ mTempStateStack[mTempStateStackCount++] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ } while ((curStateInfo != null) && !curStateInfo.active);
+
+ if (mDbg) {
+ Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+ }
+ return curStateInfo;
+ }
+
+ /**
+ * Initialize StateStack to mInitialState.
+ */
+ private final void setupInitialStateStack() {
+ if (mDbg) {
+ Log.d(TAG, "setupInitialStateStack: E mInitialState="
+ + mInitialState.getName());
+ }
+
+ StateInfo curStateInfo = mStateInfo.get(mInitialState);
+ for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
+ mTempStateStack[mTempStateStackCount] = curStateInfo;
+ curStateInfo = curStateInfo.parentStateInfo;
+ }
+
+ // Empty the StateStack
+ mStateStackTopIndex = -1;
+
+ moveTempStateStackToStateStack();
+ }
+
+ /**
+ * @return current state
+ */
+ private final HierarchicalState getCurrentState() {
+ return mStateStack[mStateStackTopIndex].state;
+ }
+
+ /**
+ * Add a new state to the state machine. Bottom up addition
+ * of states is allowed but the same state may only exist
+ * in one hierarchy.
+ *
+ * @param state the state to add
+ * @param parent the parent of state
+ * @return stateInfo for this state
+ */
+ private final StateInfo addState(HierarchicalState state, HierarchicalState parent) {
+ if (mDbg) {
+ Log.d(TAG, "addStateInternal: E state=" + state.getName()
+ + ",parent=" + ((parent == null) ? "" : parent.getName()));
+ }
+ StateInfo parentStateInfo = null;
+ if (parent != null) {
+ parentStateInfo = mStateInfo.get(parent);
+ if (parentStateInfo == null) {
+ // Recursively add our parent as it's not been added yet.
+ parentStateInfo = addState(parent, null);
+ }
+ }
+ StateInfo stateInfo = mStateInfo.get(state);
+ if (stateInfo == null) {
+ stateInfo = new StateInfo();
+ mStateInfo.put(state, stateInfo);
+ }
+
+ // Validate that we aren't adding the same state in two different hierarchies.
+ if ((stateInfo.parentStateInfo != null) &&
+ (stateInfo.parentStateInfo != parentStateInfo)) {
+ throw new RuntimeException("state already added");
+ }
+ stateInfo.state = state;
+ stateInfo.parentStateInfo = parentStateInfo;
+ stateInfo.active = false;
+ if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
+ return stateInfo;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param looper for dispatching messages
+ * @param hsm the hierarchical state machine
+ */
+ private HsmHandler(Looper looper, HierarchicalStateMachine hsm) {
+ super(looper);
+ mHsm = hsm;
+
+ addState(mHaltingState, null);
+ addState(mQuittingState, null);
+ }
+
+ /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */
+ private final void setInitialState(HierarchicalState initialState) {
+ if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
+ mInitialState = initialState;
+ }
+
+ /** @see HierarchicalStateMachine#transitionTo(HierarchicalState) */
+ private final void transitionTo(HierarchicalState destState) {
+ if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + destState.getName());
+ mDestState = destState;
+ }
+
+ /** @see HierarchicalStateMachine#deferMessage(Message) */
+ private final void deferMessage(Message msg) {
+ if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
+
+ /* Copy the "msg" to "newMsg" as "msg" will be recycled */
+ Message newMsg = obtainMessage();
+ newMsg.copyFrom(msg);
+
+ mDeferredMessages.add(newMsg);
+ }
+
+ /** @see HierarchicalStateMachine#deferMessage(Message) */
+ private final void quit() {
+ if (mDbg) Log.d(TAG, "quit:");
+ sendMessage(obtainMessage(HSM_QUIT_CMD, mQuitObj));
+ }
+
+ /** @see HierarchicalStateMachine#isQuit(Message) */
+ private final boolean isQuit(Message msg) {
+ return (msg.what == HSM_QUIT_CMD) && (msg.obj == mQuitObj);
+ }
+
+ /** @see HierarchicalStateMachine#isDbg() */
+ private final boolean isDbg() {
+ return mDbg;
+ }
+
+ /** @see HierarchicalStateMachine#setDbg(boolean) */
+ private final void setDbg(boolean dbg) {
+ mDbg = dbg;
+ }
+
+ /** @see HierarchicalStateMachine#setProcessedMessagesSize(int) */
+ private final void setProcessedMessagesSize(int maxSize) {
+ mProcessedMessages.setSize(maxSize);
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessagesSize() */
+ private final int getProcessedMessagesSize() {
+ return mProcessedMessages.size();
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessagesCount() */
+ private final int getProcessedMessagesCount() {
+ return mProcessedMessages.count();
+ }
+
+ /** @see HierarchicalStateMachine#getProcessedMessage(int) */
+ private final ProcessedMessages.Info getProcessedMessage(int index) {
+ return mProcessedMessages.get(index);
+ }
+
+ }
+
+ private HsmHandler mHsmHandler;
+ private HandlerThread mHsmThread;
+
+ /**
+ * Initialize.
+ *
+ * @param looper for this state machine
+ * @param name of the state machine
+ */
+ private void initStateMachine(String name, Looper looper) {
+ mName = name;
+ mHsmHandler = new HsmHandler(looper, this);
+ }
+
+ /**
+ * Constructor creates an HSM with its own thread.
+ *
+ * @param name of the state machine
+ */
+ protected HierarchicalStateMachine(String name) {
+ mHsmThread = new HandlerThread(name);
+ mHsmThread.start();
+ Looper looper = mHsmThread.getLooper();
+
+ initStateMachine(name, looper);
+ }
+
+ /**
+ * Constructor creates an HSMStateMachine using the looper.
+ *
+ * @param name of the state machine
+ */
+ protected HierarchicalStateMachine(String name, Looper looper) {
+ initStateMachine(name, looper);
+ }
+
+ /**
+ * Add a new state to the state machine
+ * @param state the state to add
+ * @param parent the parent of state
+ */
+ protected final void addState(HierarchicalState state, HierarchicalState parent) {
+ mHsmHandler.addState(state, parent);
+ }
+ /**
+ * @return current state
+ */
+ protected final HierarchicalState getCurrentState() {
+ return mHsmHandler.getCurrentState();
+ }
+
+
+ /**
+ * Add a new state to the state machine, parent will be null
+ * @param state to add
+ */
+ protected final void addState(HierarchicalState state) {
+ mHsmHandler.addState(state, null);
+ }
+
+ /**
+ * Set the initial state. This must be invoked before
+ * and messages are sent to the state machine.
+ *
+ * @param initialState is the state which will receive the first message.
+ */
+ protected final void setInitialState(HierarchicalState initialState) {
+ mHsmHandler.setInitialState(initialState);
+ }
+
+ /**
+ * transition to destination state. Upon returning
+ * from processMessage the current state's exit will
+ * be executed and upon the next message arriving
+ * destState.enter will be invoked.
+ *
+ * @param destState will be the state that receives the next message.
+ */
+ protected final void transitionTo(HierarchicalState destState) {
+ mHsmHandler.transitionTo(destState);
+ }
+
+ /**
+ * transition to halt state. Upon returning
+ * from processMessage we will exit all current
+ * states, execute the halting() method and then
+ * all subsequent messages haltedProcessMesage
+ * will be called.
+ */
+ protected final void transitionToHaltingState() {
+ mHsmHandler.transitionTo(mHsmHandler.mHaltingState);
+ }
+
+ /**
+ * Defer this message until next state transition.
+ * Upon transitioning all deferred messages will be
+ * placed on the queue and reprocessed in the original
+ * order. (i.e. The next state the oldest messages will
+ * be processed first)
+ *
+ * @param msg is deferred until the next transition.
+ */
+ protected final void deferMessage(Message msg) {
+ mHsmHandler.deferMessage(msg);
+ }
+
+
+ /**
+ * Called when message wasn't handled
+ *
+ * @param msg that couldn't be handled.
+ */
+ protected void unhandledMessage(Message msg) {
+ Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+ }
+
+ /**
+ * Called for any message that is received after
+ * transitionToHalting is called.
+ */
+ protected void haltedProcessMessage(Message msg) {
+ }
+
+ /**
+ * Called after the message that called transitionToHalting
+ * is called and should be overridden by StateMachine's that
+ * call transitionToHalting.
+ */
+ protected void halting() {
+ }
+
+ /**
+ * Called after the quitting message was NOT handled and
+ * just before the quit actually occurs.
+ */
+ protected void quitting() {
+ }
+
+ /**
+ * @return the name
+ */
+ public final String getName() {
+ return mName;
+ }
+
+ /**
+ * Set size of messages to maintain and clears all current messages.
+ *
+ * @param maxSize number of messages to maintain at anyone time.
+ */
+ public final void setProcessedMessagesSize(int maxSize) {
+ mHsmHandler.setProcessedMessagesSize(maxSize);
+ }
+
+ /**
+ * @return number of messages processed
+ */
+ public final int getProcessedMessagesSize() {
+ return mHsmHandler.getProcessedMessagesSize();
+ }
+
+ /**
+ * @return the total number of messages processed
+ */
+ public final int getProcessedMessagesCount() {
+ return mHsmHandler.getProcessedMessagesCount();
+ }
+
+ /**
+ * @return a processed message
+ */
+ public final ProcessedMessages.Info getProcessedMessage(int index) {
+ return mHsmHandler.getProcessedMessage(index);
+ }
+
+ /**
+ * @return Handler
+ */
+ public final Handler getHandler() {
+ return mHsmHandler;
+ }
+
+ /**
+ * Get a message and set Message.target = this.
+ *
+ * @return message
+ */
+ public final Message obtainMessage()
+ {
+ return Message.obtain(mHsmHandler);
+ }
+
+ /**
+ * Get a message and set Message.target = this and what
+ *
+ * @param what is the assigned to Message.what.
+ * @return message
+ */
+ public final Message obtainMessage(int what) {
+ return Message.obtain(mHsmHandler, what);
+ }
+
+ /**
+ * Get a message and set Message.target = this,
+ * what and obj.
+ *
+ * @param what is the assigned to Message.what.
+ * @param obj is assigned to Message.obj.
+ * @return message
+ */
+ public final Message obtainMessage(int what, Object obj)
+ {
+ return Message.obtain(mHsmHandler, what, obj);
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ */
+ public final void sendMessage(int what) {
+ mHsmHandler.sendMessage(obtainMessage(what));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ */
+ public final void sendMessage(int what, Object obj) {
+ mHsmHandler.sendMessage(obtainMessage(what,obj));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ */
+ public final void sendMessage(Message msg) {
+ mHsmHandler.sendMessage(msg);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ */
+ public final void sendMessageDelayed(int what, long delayMillis) {
+ mHsmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ */
+ public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
+ mHsmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ */
+ public final void sendMessageDelayed(Message msg, long delayMillis) {
+ mHsmHandler.sendMessageDelayed(msg, delayMillis);
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of HierarchicalStateMachine.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
+ mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of HierarchicalStateMachine.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what) {
+ mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of HierarchicalStateMachine.
+ */
+ protected final void sendMessageAtFrontOfQueue(Message msg) {
+ mHsmHandler.sendMessageAtFrontOfQueue(msg);
+ }
+
+ /**
+ * Conditionally quit the looper and stop execution.
+ *
+ * This sends the HSM_QUIT_MSG to the state machine and
+ * if not handled by any state's processMessage then the
+ * state machine will be stopped and no further messages
+ * will be processed.
+ */
+ public final void quit() {
+ mHsmHandler.quit();
+ }
+
+ /**
+ * @return ture if msg is quit
+ */
+ protected final boolean isQuit(Message msg) {
+ return mHsmHandler.isQuit(msg);
+ }
+
+ /**
+ * @return if debugging is enabled
+ */
+ public boolean isDbg() {
+ return mHsmHandler.isDbg();
+ }
+
+ /**
+ * Set debug enable/disabled.
+ *
+ * @param dbg is true to enable debugging.
+ */
+ public void setDbg(boolean dbg) {
+ mHsmHandler.setDbg(dbg);
+ }
+
+ /**
+ * Start the state machine.
+ */
+ public void start() {
+ /** Send the complete construction message */
+ mHsmHandler.completeConstruction();
+ }
+}
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
new file mode 100644
index 0000000..af0c6c6
--- /dev/null
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.android.internal.util;
+
+import java.io.File;
+import java.io.IOException;
+
+public class JournaledFile {
+ File mReal;
+ File mTemp;
+ boolean mWriting;
+
+ public JournaledFile(File real, File temp) {
+ mReal = real;
+ mTemp = temp;
+ }
+
+ /** Returns the file for you to read.
+ * @more
+ * Prefers the real file. If it doesn't exist, uses the temp one, and then copies
+ * it to the real one. If there is both a real file and a temp one, assumes that the
+ * temp one isn't fully written and deletes it.
+ */
+ public File chooseForRead() {
+ File result;
+ if (mReal.exists()) {
+ result = mReal;
+ if (mTemp.exists()) {
+ mTemp.delete();
+ }
+ } else if (mTemp.exists()) {
+ result = mTemp;
+ mTemp.renameTo(mReal);
+ } else {
+ return mReal;
+ }
+ return result;
+ }
+
+ /**
+ * Returns a file for you to write.
+ * @more
+ * If a write is already happening, throws. In other words, you must provide your
+ * own locking.
+ * <p>
+ * Call {@link #commit} to commit the changes, or {@link #rollback} to forget the changes.
+ */
+ public File chooseForWrite() {
+ if (mWriting) {
+ throw new IllegalStateException("uncommitted write already in progress");
+ }
+ if (!mReal.exists()) {
+ // If the real one doesn't exist, it's either because this is the first time
+ // or because something went wrong while copying them. In this case, we can't
+ // trust anything that's in temp. In order to have the chooseForRead code not
+ // use the temporary one until it's fully written, create an empty file
+ // for real, which will we'll shortly delete.
+ try {
+ mReal.createNewFile();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+
+ if (mTemp.exists()) {
+ mTemp.delete();
+ }
+ mWriting = true;
+ return mTemp;
+ }
+
+ /**
+ * Commit changes.
+ */
+ public void commit() {
+ if (!mWriting) {
+ throw new IllegalStateException("no file to commit");
+ }
+ mWriting = false;
+ mTemp.renameTo(mReal);
+ }
+
+ /**
+ * Roll back changes.
+ */
+ public void rollback() {
+ if (!mWriting) {
+ throw new IllegalStateException("no file to roll back");
+ }
+ mWriting = false;
+ mTemp.delete();
+ }
+}
diff --git a/core/java/com/android/internal/util/ProcessedMessages.java b/core/java/com/android/internal/util/ProcessedMessages.java
new file mode 100644
index 0000000..244474e
--- /dev/null
+++ b/core/java/com/android/internal/util/ProcessedMessages.java
@@ -0,0 +1,198 @@
+/**
+ * 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 com.android.internal.util;
+
+import android.os.Message;
+
+import java.util.Vector;
+
+/**
+ * {@hide}
+ *
+ * A list of messages recently processed by the state machine.
+ *
+ * The class maintains a list of messages that have been most
+ * recently processed. The list is finite and may be set in the
+ * constructor or by calling setSize. The public interface also
+ * includes size which returns the number of recent messages,
+ * count which is the number of message processed since the
+ * the last setSize, get which returns a processed message and
+ * add which adds a processed messaged.
+ */
+public class ProcessedMessages {
+
+ public static final int DEFAULT_SIZE = 20;
+
+ /**
+ * The information maintained for a processed message.
+ */
+ public class Info {
+ private int what;
+ private HierarchicalState state;
+ private HierarchicalState orgState;
+
+ /**
+ * Constructor
+ * @param message
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ Info(Message message, HierarchicalState state, HierarchicalState orgState) {
+ update(message, state, orgState);
+ }
+
+ /**
+ * Update the information in the record.
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
+ this.what = message.what;
+ this.state = state;
+ this.orgState = orgState;
+ }
+
+ /**
+ * @return the command that was executing
+ */
+ public int getWhat() {
+ return what;
+ }
+
+ /**
+ * @return the state that handled this message
+ */
+ public HierarchicalState getState() {
+ return state;
+ }
+
+ /**
+ * @return the original state that received the message.
+ */
+ public HierarchicalState getOriginalState() {
+ return orgState;
+ }
+
+ /**
+ * @return as string
+ */
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("what=");
+ sb.append(what);
+ sb.append(" state=");
+ sb.append(cn(state));
+ sb.append(" orgState=");
+ sb.append(cn(orgState));
+ return sb.toString();
+ }
+
+ /**
+ * @return an objects class name
+ */
+ private String cn(Object n) {
+ if (n == null) {
+ return "null";
+ } else {
+ String name = n.getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+ }
+ }
+
+ private Vector<Info> mMessages = new Vector<Info>();
+ private int mMaxSize = DEFAULT_SIZE;
+ private int mOldestIndex = 0;
+ private int mCount = 0;
+
+ /**
+ * Constructor
+ */
+ ProcessedMessages() {
+ }
+
+ ProcessedMessages(int maxSize) {
+ setSize(maxSize);
+ }
+
+ /**
+ * Set size of messages to maintain and clears all current messages.
+ *
+ * @param maxSize number of messages to maintain at anyone time.
+ */
+ void setSize(int maxSize) {
+ mMaxSize = maxSize;
+ mCount = 0;
+ mMessages.clear();
+ }
+
+ /**
+ * @return the number of recent messages.
+ */
+ int size() {
+ return mMessages.size();
+ }
+
+ /**
+ * @return the total number of messages processed since size was set.
+ */
+ int count() {
+ return mCount;
+ }
+
+ /**
+ * @return the information on a particular record. 0 is the oldest
+ * record and size()-1 is the newest record. If the index is to
+ * large null is returned.
+ */
+ Info get(int index) {
+ int nextIndex = mOldestIndex + index;
+ if (nextIndex >= mMaxSize) {
+ nextIndex -= mMaxSize;
+ }
+ if (nextIndex >= size()) {
+ return null;
+ } else {
+ return mMessages.get(nextIndex);
+ }
+ }
+
+ /**
+ * Add a processed message.
+ *
+ * @param message
+ * @param state that handled the message
+ * @param orgState is the first state the received the message but
+ * did not processes the message.
+ */
+ void add(Message message, HierarchicalState state, HierarchicalState orgState) {
+ mCount += 1;
+ if (mMessages.size() < mMaxSize) {
+ mMessages.add(new Info(message, state, orgState));
+ } else {
+ Info info = mMessages.get(mOldestIndex);
+ mOldestIndex += 1;
+ if (mOldestIndex >= mMaxSize) {
+ mOldestIndex = 0;
+ }
+ info.update(message, state, orgState);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 948e313..8d8df16 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -85,7 +86,7 @@ public class XmlUtils
// XXX This code is copied from Integer.decode() so we don't
// have to instantiate an Integer!
-
+
int value;
int sign = 1;
int index = 0;
@@ -132,21 +133,21 @@ public class XmlUtils
public static final int
parseUnsignedIntAttribute(CharSequence charSeq)
- {
+ {
String value = charSeq.toString();
long bits;
int index = 0;
int len = value.length();
int base = 10;
-
+
if ('0' == value.charAt(index)) {
// Quick check for zero by itself
if (index == (len - 1))
return 0;
-
+
char c = value.charAt(index + 1);
-
+
if ('x' == c || 'X' == c) { // check for hex
index += 2;
base = 16;
@@ -158,17 +159,17 @@ public class XmlUtils
index++;
base = 16;
}
-
+
return (int) Long.parseLong(value.substring(index), base);
}
/**
* Flatten a Map into an output stream as XML. The map can later be
* read back with readMapXml().
- *
+ *
* @param val The map to be flattened.
* @param out Where to write the XML data.
- *
+ *
* @see #writeMapXml(Map, String, XmlSerializer)
* @see #writeListXml
* @see #writeValueXml
@@ -187,10 +188,10 @@ public class XmlUtils
/**
* Flatten a List into an output stream as XML. The list can later be
* read back with readListXml().
- *
+ *
* @param val The list to be flattened.
* @param out Where to write the XML data.
- *
+ *
* @see #writeListXml(List, String, XmlSerializer)
* @see #writeMapXml
* @see #writeValueXml
@@ -210,12 +211,12 @@ public class XmlUtils
/**
* Flatten a Map into an XmlSerializer. The map can later be read back
* with readThisMapXml().
- *
+ *
* @param val The map to be flattened.
* @param name Name attribute to include with this list's tag, or null for
* none.
* @param out XmlSerializer to write the map into.
- *
+ *
* @see #writeMapXml(Map, OutputStream)
* @see #writeListXml
* @see #writeValueXml
@@ -249,12 +250,12 @@ public class XmlUtils
/**
* Flatten a List into an XmlSerializer. The list can later be read back
* with readThisListXml().
- *
+ *
* @param val The list to be flattened.
* @param name Name attribute to include with this list's tag, or null for
* none.
* @param out XmlSerializer to write the list into.
- *
+ *
* @see #writeListXml(List, OutputStream)
* @see #writeMapXml
* @see #writeValueXml
@@ -287,19 +288,19 @@ public class XmlUtils
/**
* Flatten a byte[] into an XmlSerializer. The list can later be read back
* with readThisByteArrayXml().
- *
+ *
* @param val The byte array to be flattened.
* @param name Name attribute to include with this array's tag, or null for
* none.
* @param out XmlSerializer to write the array into.
- *
+ *
* @see #writeMapXml
* @see #writeValueXml
*/
public static final void writeByteArrayXml(byte[] val, String name,
XmlSerializer out)
throws XmlPullParserException, java.io.IOException {
-
+
if (val == null) {
out.startTag(null, "null");
out.endTag(null, "null");
@@ -313,7 +314,7 @@ public class XmlUtils
final int N = val.length;
out.attribute(null, "num", Integer.toString(N));
-
+
StringBuilder sb = new StringBuilder(val.length*2);
for (int i=0; i<N; i++) {
int b = val[i];
@@ -331,12 +332,12 @@ public class XmlUtils
/**
* Flatten an int[] into an XmlSerializer. The list can later be read back
* with readThisIntArrayXml().
- *
+ *
* @param val The int array to be flattened.
* @param name Name attribute to include with this array's tag, or null for
* none.
* @param out XmlSerializer to write the array into.
- *
+ *
* @see #writeMapXml
* @see #writeValueXml
* @see #readThisIntArrayXml
@@ -344,7 +345,7 @@ public class XmlUtils
public static final void writeIntArrayXml(int[] val, String name,
XmlSerializer out)
throws XmlPullParserException, java.io.IOException {
-
+
if (val == null) {
out.startTag(null, "null");
out.endTag(null, "null");
@@ -355,7 +356,7 @@ public class XmlUtils
if (name != null) {
out.attribute(null, "name", name);
}
-
+
final int N = val.length;
out.attribute(null, "num", Integer.toString(N));
@@ -371,15 +372,15 @@ public class XmlUtils
/**
* Flatten an object's value into an XmlSerializer. The value can later
* be read back with readThisValueXml().
- *
+ *
* Currently supported value types are: null, String, Integer, Long,
* Float, Double Boolean, Map, List.
- *
+ *
* @param v The object to be flattened.
* @param name Name attribute to include with this value's tag, or null
* for none.
* @param out XmlSerializer to write the object into.
- *
+ *
* @see #writeMapXml
* @see #writeListXml
* @see #readValueXml
@@ -451,11 +452,11 @@ public class XmlUtils
/**
* Read a HashMap from an InputStream containing XML. The stream can
* previously have been written by writeMapXml().
- *
+ *
* @param in The InputStream from which to read.
- *
+ *
* @return HashMap The resulting map.
- *
+ *
* @see #readListXml
* @see #readValueXml
* @see #readThisMapXml
@@ -472,11 +473,11 @@ public class XmlUtils
/**
* Read an ArrayList from an InputStream containing XML. The stream can
* previously have been written by writeListXml().
- *
+ *
* @param in The InputStream from which to read.
- *
+ *
* @return HashMap The resulting list.
- *
+ *
* @see #readMapXml
* @see #readValueXml
* @see #readThisListXml
@@ -494,14 +495,14 @@ public class XmlUtils
* Read a HashMap object from an XmlPullParser. The XML data could
* previously have been generated by writeMapXml(). The XmlPullParser
* must be positioned <em>after</em> the tag that begins the map.
- *
+ *
* @param parser The XmlPullParser from which to read the map data.
* @param endTag Name of the tag that will end the map, usually "map".
* @param name An array of one string, used to return the name attribute
* of the map's tag.
- *
+ *
* @return HashMap The newly generated map.
- *
+ *
* @see #readMapXml
*/
public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name)
@@ -538,14 +539,14 @@ public class XmlUtils
* Read an ArrayList object from an XmlPullParser. The XML data could
* previously have been generated by writeListXml(). The XmlPullParser
* must be positioned <em>after</em> the tag that begins the list.
- *
+ *
* @param parser The XmlPullParser from which to read the list data.
* @param endTag Name of the tag that will end the list, usually "list".
* @param name An array of one string, used to return the name attribute
* of the list's tag.
- *
+ *
* @return HashMap The newly generated list.
- *
+ *
* @see #readListXml
*/
public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name)
@@ -577,20 +578,20 @@ public class XmlUtils
* Read an int[] object from an XmlPullParser. The XML data could
* previously have been generated by writeIntArrayXml(). The XmlPullParser
* must be positioned <em>after</em> the tag that begins the list.
- *
+ *
* @param parser The XmlPullParser from which to read the list data.
* @param endTag Name of the tag that will end the list, usually "list".
* @param name An array of one string, used to return the name attribute
* of the list's tag.
- *
+ *
* @return Returns a newly generated int[].
- *
+ *
* @see #readListXml
*/
public static final int[] readThisIntArrayXml(XmlPullParser parser,
String endTag, String[] name)
throws XmlPullParserException, java.io.IOException {
-
+
int num;
try {
num = Integer.parseInt(parser.getAttributeValue(null, "num"));
@@ -601,7 +602,7 @@ public class XmlUtils
throw new XmlPullParserException(
"Not a number in num attribute in byte-array");
}
-
+
int[] array = new int[num];
int i = 0;
@@ -618,7 +619,7 @@ public class XmlUtils
} catch (NumberFormatException e) {
throw new XmlPullParserException(
"Not a number in value attribute in item");
- }
+ }
} else {
throw new XmlPullParserException(
"Expected item tag at: " + parser.getName());
@@ -646,13 +647,13 @@ public class XmlUtils
* previously have been written with writeMapXml(), writeListXml(), or
* writeValueXml(). The XmlPullParser must be positioned <em>at</em> the
* tag that defines the value.
- *
+ *
* @param parser The XmlPullParser from which to read the object.
* @param name An array of one string, used to return the name attribute
* of the value's tag.
- *
+ *
* @return Object The newly generated value object.
- *
+ *
* @see #readMapXml
* @see #readListXml
* @see #writeValueXml
@@ -778,19 +779,19 @@ public class XmlUtils
if (type != parser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
-
+
if (!parser.getName().equals(firstElementName)) {
throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
", expected " + firstElementName);
}
}
-
+
public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException
{
int type;
while ((type=parser.next()) != parser.START_TAG
&& type != parser.END_DOCUMENT) {
;
- }
+ }
}
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 15dcbd6..b13d656 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -1,5 +1,22 @@
+/*
+ * 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 com.android.internal.view;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -17,7 +34,7 @@ public class BaseIWindow extends IWindow.Stub {
}
public void resized(int w, int h, Rect coveredInsets,
- Rect visibleInsets, boolean reportDraw) {
+ Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (reportDraw) {
try {
mSession.finishDrawing(this);
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index 2823689..e0d3a5f 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -1,3 +1,19 @@
+/*
+ * 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 com.android.internal.view;
import android.graphics.Canvas;
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 106392d..a765e38 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.internal.view;
import android.os.Bundle;
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index b92cb45..3c44e58 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.internal.view;
import com.android.internal.view.IInputContext;
diff --git a/core/java/com/android/internal/view/WindowManagerPolicyThread.java b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
new file mode 100644
index 0000000..c8c38bb
--- /dev/null
+++ b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.os.Looper;
+
+/**
+ * Static storage of the thread running the window manager policy, to
+ * share with others.
+ */
+public class WindowManagerPolicyThread {
+ static Thread mThread;
+ static Looper mLooper;
+
+ public static void set(Thread thread, Looper looper) {
+ mThread = thread;
+ mLooper = looper;
+ }
+
+ public static Thread getThread() {
+ return mThread;
+ }
+
+ public static Looper getLooper() {
+ return mLooper;
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 66e15c1..3c5b422 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -254,7 +254,7 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
if (lp == null) {
// Default layout parameters
lp = new IconMenuView.LayoutParams(
- IconMenuView.LayoutParams.FILL_PARENT, IconMenuView.LayoutParams.FILL_PARENT);
+ IconMenuView.LayoutParams.MATCH_PARENT, IconMenuView.LayoutParams.MATCH_PARENT);
}
// Set the desired width of item
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index bba2ee2..beb57ba 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -179,6 +179,10 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
*/
private void layoutItems(int width) {
int numItems = getChildCount();
+ if (numItems == 0) {
+ mLayoutNumRows = 0;
+ return;
+ }
// Start with the least possible number of rows
int curNumRows =
@@ -470,15 +474,18 @@ public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuVi
// Get the desired height of the icon menu view (last row of items does
// not have a divider below)
+ final int layoutNumRows = mLayoutNumRows;
final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
- mLayoutNumRows - mHorizontalDividerHeight;
+ layoutNumRows - mHorizontalDividerHeight;
// Maximum possible width and desired height
setMeasuredDimension(measuredWidth,
resolveSize(desiredHeight, heightMeasureSpec));
// Position the children
- positionChildren(mMeasuredWidth, mMeasuredHeight);
+ if (layoutNumRows > 0) {
+ positionChildren(mMeasuredWidth, mMeasuredHeight);
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index 70f040a..d7438d6 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -101,11 +101,19 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn
}
}
}
- } else if (event.getAction() == KeyEvent.ACTION_UP
- && event.isTracking() && !event.isCanceled()) {
- mMenu.close(true);
- dialog.dismiss();
- return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
+ Window win = mDialog.getWindow();
+ if (win != null) {
+ View decor = win.getDecorView();
+ if (decor != null) {
+ KeyEvent.DispatcherState ds = decor.getKeyDispatcherState();
+ if (ds != null && ds.isTracking(event)) {
+ mMenu.close(true);
+ dialog.dismiss();
+ return true;
+ }
+ }
+ }
}
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 1543b62..9b58205 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,10 +16,12 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuView.ItemView;
+import java.lang.ref.WeakReference;
+import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
@@ -28,12 +30,14 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
-import java.lang.ref.WeakReference;
+import com.android.internal.view.menu.MenuView.ItemView;
/**
* @hide
*/
public final class MenuItemImpl implements MenuItem {
+ private static final String TAG = "MenuItemImpl";
+
private final int mId;
private final int mGroup;
private final int mCategoryOrder;
@@ -147,8 +151,12 @@ public final class MenuItemImpl implements MenuItem {
}
if (mIntent != null) {
- mMenu.getContext().startActivity(mIntent);
- return true;
+ try {
+ mMenu.getContext().startActivity(mIntent);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
+ }
}
return false;
diff --git a/core/java/com/android/internal/widget/ContactHeaderWidget.java b/core/java/com/android/internal/widget/ContactHeaderWidget.java
index c4f9988..f421466 100644
--- a/core/java/com/android/internal/widget/ContactHeaderWidget.java
+++ b/core/java/com/android/internal/widget/ContactHeaderWidget.java
@@ -99,6 +99,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
Contacts.LOOKUP_KEY,
Contacts.PHOTO_ID,
Contacts.DISPLAY_NAME,
+ Contacts.PHONETIC_NAME,
Contacts.STARRED,
Contacts.CONTACT_PRESENCE,
Contacts.CONTACT_STATUS,
@@ -110,14 +111,15 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
int LOOKUP_KEY = 1;
int PHOTO_ID = 2;
int DISPLAY_NAME = 3;
+ int PHONETIC_NAME = 4;
//TODO: We need to figure out how we're going to get the phonetic name.
//static final int HEADER_PHONETIC_NAME_COLUMN_INDEX
- int STARRED = 4;
- int CONTACT_PRESENCE_STATUS = 5;
- int CONTACT_STATUS = 6;
- int CONTACT_STATUS_TIMESTAMP = 7;
- int CONTACT_STATUS_RES_PACKAGE = 8;
- int CONTACT_STATUS_LABEL = 9;
+ int STARRED = 5;
+ int CONTACT_PRESENCE_STATUS = 6;
+ int CONTACT_STATUS = 7;
+ int CONTACT_STATUS_TIMESTAMP = 8;
+ int CONTACT_STATUS_RES_PACKAGE = 9;
+ int CONTACT_STATUS_LABEL = 10;
}
private interface PhotoQuery {
@@ -173,7 +175,6 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
mDisplayNameView = (TextView) findViewById(R.id.name);
mAggregateBadge = findViewById(R.id.aggregate_badge);
- mAggregateBadge.setVisibility(View.GONE);
mPhoneticNameView = (TextView) findViewById(R.id.phonetic_name);
@@ -268,9 +269,19 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
bindContactInfo(cursor);
Uri lookupUri = Contacts.getLookupUri(cursor.getLong(ContactQuery._ID),
cursor.getString(ContactQuery.LOOKUP_KEY));
- startPhotoQuery(cursor.getLong(ContactQuery.PHOTO_ID),
- lookupUri, false /* don't reset query handler */);
- invalidate();
+
+ final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+
+ if (photoId == 0) {
+ mPhotoView.setImageBitmap(loadPlaceholderPhoto(null));
+ if (cookie != null && cookie instanceof Uri) {
+ mPhotoView.assignContactUri((Uri) cookie);
+ }
+ invalidate();
+ } else {
+ startPhotoQuery(photoId, lookupUri,
+ false /* don't reset query handler */);
+ }
} else {
// shouldn't really happen
setDisplayName(null, null);
@@ -321,7 +332,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
}
/**
- * Turn on/off showing of the aggregate bage element.
+ * Turn on/off showing of the aggregate badge element.
*/
public void showAggregateBadge(boolean showBagde) {
mAggregateBadge.setVisibility(showBagde ? View.VISIBLE : View.GONE);
@@ -380,8 +391,11 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
*/
public void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
mDisplayNameView.setText(displayName);
- if (mPhoneticNameView != null) {
+ if (!TextUtils.isEmpty(phoneticName)) {
mPhoneticNameView.setText(phoneticName);
+ mPhoneticNameView.setVisibility(View.VISIBLE);
+ } else {
+ mPhoneticNameView.setVisibility(View.GONE);
}
}
@@ -475,7 +489,7 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
resetAsyncQueryHandler();
}
- mQueryHandler.startQuery(TOKEN_CONTACT_INFO, null, contactUri, ContactQuery.COLUMNS,
+ mQueryHandler.startQuery(TOKEN_CONTACT_INFO, contactUri, contactUri, ContactQuery.COLUMNS,
null, null, null);
}
@@ -524,10 +538,9 @@ public class ContactHeaderWidget extends FrameLayout implements View.OnClickList
* Bind the contact details provided by the given {@link Cursor}.
*/
protected void bindContactInfo(Cursor c) {
- // TODO: Bring back phonetic name
final String displayName = c.getString(ContactQuery.DISPLAY_NAME);
- final String phoneticName = null;
- this.setDisplayName(displayName, null);
+ final String phoneticName = c.getString(ContactQuery.PHONETIC_NAME);
+ this.setDisplayName(displayName, phoneticName);
final boolean starred = c.getInt(ContactQuery.STARRED) != 0;
mStarredView.setChecked(starred);
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index fa47ff6..23e2277 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@ import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@ import java.util.Calendar;
/**
* Displays the time
*/
-public class DigitalClock extends LinearLayout {
+public class DigitalClock extends RelativeLayout {
private final static String M12 = "h:mm";
private final static String M24 = "kk:mm";
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 58056bd..dbbd286 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -16,19 +16,28 @@
package com.android.internal.widget;
+import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.security.MessageDigest;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.widget.Button;
+import com.android.internal.R;
+import com.android.internal.telephony.ITelephony;
import com.google.android.collect.Lists;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
@@ -38,8 +47,9 @@ import java.util.List;
public class LockPatternUtils {
private static final String TAG = "LockPatternUtils";
-
+
private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "/system/password.key";
/**
* The maximum number of incorrect attempts before the user is prevented
@@ -74,33 +84,78 @@ public class LockPatternUtils {
* attempt for it to be counted against the counts that affect
* {@link #FAILED_ATTEMPTS_BEFORE_TIMEOUT} and {@link #FAILED_ATTEMPTS_BEFORE_RESET}
*/
- public static final int MIN_PATTERN_REGISTER_FAIL = 3;
+ public static final int MIN_PATTERN_REGISTER_FAIL = 3;
private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
- private final static String PATTERN_EVER_CHOSEN = "lockscreen.patterneverchosen";
+ private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+ public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
+ private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
+ private final Context mContext;
private final ContentResolver mContentResolver;
-
+ private DevicePolicyManager mDevicePolicyManager;
private static String sLockPatternFilename;
-
+ private static String sLockPasswordFilename;
+
+ public DevicePolicyManager getDevicePolicyManager() {
+ if (mDevicePolicyManager == null) {
+ mDevicePolicyManager =
+ (DevicePolicyManager)mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (mDevicePolicyManager == null) {
+ Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?",
+ new IllegalStateException("Stack trace:"));
+ }
+ }
+ return mDevicePolicyManager;
+ }
/**
* @param contentResolver Used to look up and save settings.
*/
- public LockPatternUtils(ContentResolver contentResolver) {
- mContentResolver = contentResolver;
+ public LockPatternUtils(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
// Initialize the location of gesture lock file
if (sLockPatternFilename == null) {
- sLockPatternFilename = android.os.Environment.getDataDirectory()
+ sLockPatternFilename = android.os.Environment.getDataDirectory()
.getAbsolutePath() + LOCK_PATTERN_FILE;
+ sLockPasswordFilename = android.os.Environment.getDataDirectory()
+ .getAbsolutePath() + LOCK_PASSWORD_FILE;
}
+
+ }
+
+ public int getRequestedMinimumPasswordLength() {
+ return getDevicePolicyManager().getPasswordMinimumLength(null);
+ }
+
+
+ /**
+ * Gets the device policy password mode. If the mode is non-specific, returns
+ * MODE_PATTERN which allows the user to choose anything.
+ */
+ public int getRequestedPasswordQuality() {
+ return getDevicePolicyManager().getPasswordQuality(null);
+ }
+
+ /**
+ * Returns the actual password mode, as set by keyguard after updating the password.
+ *
+ * @return
+ */
+ public void reportFailedPasswordAttempt() {
+ getDevicePolicyManager().reportFailedPasswordAttempt();
+ }
+
+ public void reportSuccessfulPasswordAttempt() {
+ getDevicePolicyManager().reportSuccessfulPasswordAttempt();
}
/**
* Check to see if a pattern matches the saved pattern. If no pattern exists,
* always returns true.
* @param pattern The pattern to check.
- * @return Whether the pattern matchees the stored one.
+ * @return Whether the pattern matches the stored one.
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern) {
try {
@@ -122,14 +177,41 @@ public class LockPatternUtils {
}
/**
- * Check to see if the user has stored a lock pattern.
- * @return Whether a saved pattern exists.
+ * Check to see if a password matches the saved password. If no password exists,
+ * always returns true.
+ * @param password The password to check.
+ * @return Whether the password matches the stored one.
*/
- public boolean savedPatternExists() {
+ public boolean checkPassword(String password) {
+ try {
+ // Read all the bytes from the file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r");
+ final byte[] stored = new byte[(int) raf.length()];
+ int got = raf.read(stored, 0, stored.length);
+ raf.close();
+ if (got <= 0) {
+ return true;
+ }
+ // Compare the hash from the file with the entered password's hash
+ return Arrays.equals(stored, passwordToHash(password));
+ } catch (FileNotFoundException fnfe) {
+ return true;
+ } catch (IOException ioe) {
+ return true;
+ }
+ }
+
+ /**
+ * Checks to see if the given file exists and contains any data. Returns true if it does,
+ * false otherwise.
+ * @param filename
+ * @return true if file exists and is non-empty.
+ */
+ private boolean nonEmptyFileExists(String filename) {
try {
// Check if we can read a byte from the file
- RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r");
- byte first = raf.readByte();
+ RandomAccessFile raf = new RandomAccessFile(filename, "r");
+ raf.readByte();
raf.close();
return true;
} catch (FileNotFoundException fnfe) {
@@ -140,13 +222,72 @@ public class LockPatternUtils {
}
/**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPatternExists() {
+ return nonEmptyFileExists(sLockPatternFilename);
+ }
+
+ /**
+ * Check to see if the user has stored a lock pattern.
+ * @return Whether a saved pattern exists.
+ */
+ public boolean savedPasswordExists() {
+ return nonEmptyFileExists(sLockPasswordFilename);
+ }
+
+ /**
* Return true if the user has ever chosen a pattern. This is true even if the pattern is
* currently cleared.
*
* @return True if the user has ever chosen a pattern.
*/
public boolean isPatternEverChosen() {
- return getBoolean(PATTERN_EVER_CHOSEN);
+ return getBoolean(PATTERN_EVER_CHOSEN_KEY);
+ }
+
+ /**
+ * Used by device policy manager to validate the current password
+ * information it has.
+ */
+ public int getActivePasswordQuality() {
+ int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ switch (getKeyguardStoredPasswordQuality()) {
+ case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
+ if (isLockPatternEnabled()) {
+ activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ }
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
+ if (isLockPasswordEnabled()) {
+ activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ }
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
+ if (isLockPasswordEnabled()) {
+ activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+ }
+ break;
+ case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ if (isLockPasswordEnabled()) {
+ activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ }
+ break;
+ }
+ return activePasswordQuality;
+ }
+
+ /**
+ * Clear any lock pattern or password.
+ */
+ public void clearLock() {
+ getDevicePolicyManager().setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+ saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ setLockPatternEnabled(false);
+ saveLockPattern(null);
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
}
/**
@@ -166,7 +307,16 @@ public class LockPatternUtils {
raf.write(hash, 0, hash.length);
}
raf.close();
- setBoolean(PATTERN_EVER_CHOSEN, true);
+ DevicePolicyManager dpm = getDevicePolicyManager();
+ if (pattern != null) {
+ setBoolean(PATTERN_EVER_CHOSEN_KEY, true);
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ dpm.setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size());
+ } else {
+ dpm.setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+ }
} catch (FileNotFoundException fnfe) {
// Cant do much, unless we want to fail over to using the settings provider
Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename);
@@ -177,6 +327,87 @@ public class LockPatternUtils {
}
/**
+ * Compute the password quality from the given password string.
+ */
+ static public int computePasswordQuality(String password) {
+ boolean hasDigit = false;
+ boolean hasNonDigit = false;
+ final int len = password.length();
+ for (int i = 0; i < len; i++) {
+ if (Character.isDigit(password.charAt(i))) {
+ hasDigit = true;
+ } else {
+ hasNonDigit = true;
+ }
+ }
+
+ if (hasNonDigit && hasDigit) {
+ return DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ }
+ if (hasNonDigit) {
+ return DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+ }
+ if (hasDigit) {
+ return DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+ }
+ return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ }
+
+ /**
+ * Save a lock password. Does not ensure that the password is as good
+ * as the requested mode, but will adjust the mode to be as good as the
+ * pattern.
+ * @param password The password to save
+ * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ */
+ public void saveLockPassword(String password, int quality) {
+ // Compute the hash
+ final byte[] hash = passwordToHash(password);
+ try {
+ // Write the hash to file
+ RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw");
+ // Truncate the file if pattern is null, to clear the lock
+ if (password == null) {
+ raf.setLength(0);
+ } else {
+ raf.write(hash, 0, hash.length);
+ }
+ raf.close();
+ DevicePolicyManager dpm = getDevicePolicyManager();
+ if (password != null) {
+ int computedQuality = computePasswordQuality(password);
+ setLong(PASSWORD_TYPE_KEY, computedQuality);
+ if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ dpm.setActivePasswordState(computedQuality, password.length());
+ } else {
+ // The password is not anything.
+ dpm.setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+ }
+ } else {
+ dpm.setActivePasswordState(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0);
+ }
+ } catch (FileNotFoundException fnfe) {
+ // Cant do much, unless we want to fail over to using the settings provider
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ } catch (IOException ioe) {
+ // Cant do much
+ Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename);
+ }
+ }
+
+ /**
+ * Retrieves the quality mode we're in.
+ * {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ *
+ * @return stored password quality
+ */
+ public int getKeyguardStoredPasswordQuality() {
+ return (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ }
+
+ /**
* Deserialize a pattern.
* @param string The pattern serialized with {@link #patternToString}
* @return The pattern.
@@ -210,7 +441,7 @@ public class LockPatternUtils {
}
return new String(res);
}
-
+
/*
* Generate an SHA-1 hash for the pattern. Not the most secure, but it is
* at least a second level of protection. First level is that the file
@@ -218,11 +449,11 @@ public class LockPatternUtils {
* @param pattern the gesture pattern.
* @return the hash of the pattern in a byte array.
*/
- static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
+ private static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
if (pattern == null) {
return null;
}
-
+
final int patternSize = pattern.size();
byte[] res = new byte[patternSize];
for (int i = 0; i < patternSize; i++) {
@@ -238,46 +469,108 @@ public class LockPatternUtils {
}
}
+ private String getSalt() {
+ long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0);
+ if (salt == 0) {
+ try {
+ salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
+ setLong(LOCK_PASSWORD_SALT_KEY, salt);
+ Log.v(TAG, "Initialized lock password salt");
+ } catch (NoSuchAlgorithmException e) {
+ // Throw an exception rather than storing a password we'll never be able to recover
+ throw new IllegalStateException("Couldn't get SecureRandom number", e);
+ }
+ }
+ return Long.toHexString(salt);
+ }
+
+ /*
+ * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
+ * Not the most secure, but it is at least a second level of protection. First level is that
+ * the file is in a location only readable by the system process.
+ * @param password the gesture pattern.
+ * @return the hash of the pattern in a byte array.
+ */
+ public byte[] passwordToHash(String password) {
+ if (password == null) {
+ return null;
+ }
+ String algo = null;
+ byte[] hashed = null;
+ try {
+ byte[] saltedPassword = (password + getSalt()).getBytes();
+ byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
+ byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
+ hashed = (toHex(sha1) + toHex(md5)).getBytes();
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "Failed to encode string because of missing algorithm: " + algo);
+ }
+ return hashed;
+ }
+
+ private static String toHex(byte[] ary) {
+ final String hex = "0123456789ABCDEF";
+ String ret = "";
+ for (int i = 0; i < ary.length; i++) {
+ ret += hex.charAt((ary[i] >> 4) & 0xf);
+ ret += hex.charAt(ary[i] & 0xf);
+ }
+ return ret;
+ }
+
+ /**
+ * @return Whether the lock password is enabled.
+ */
+ public boolean isLockPasswordEnabled() {
+ long mode = getLong(PASSWORD_TYPE_KEY, 0);
+ return savedPasswordExists() &&
+ (mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC);
+ }
+
/**
* @return Whether the lock pattern is enabled.
*/
public boolean isLockPatternEnabled() {
- return getBoolean(Settings.System.LOCK_PATTERN_ENABLED);
+ return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED)
+ && getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
+ == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
}
/**
* Set whether the lock pattern is enabled.
*/
public void setLockPatternEnabled(boolean enabled) {
- setBoolean(Settings.System.LOCK_PATTERN_ENABLED, enabled);
+ setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, enabled);
}
/**
* @return Whether the visible pattern is enabled.
*/
public boolean isVisiblePatternEnabled() {
- return getBoolean(Settings.System.LOCK_PATTERN_VISIBLE);
+ return getBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE);
}
/**
* Set whether the visible pattern is enabled.
*/
public void setVisiblePatternEnabled(boolean enabled) {
- setBoolean(Settings.System.LOCK_PATTERN_VISIBLE, enabled);
+ setBoolean(Settings.Secure.LOCK_PATTERN_VISIBLE, enabled);
}
/**
* @return Whether tactile feedback for the pattern is enabled.
*/
public boolean isTactileFeedbackEnabled() {
- return getBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
+ return getBoolean(Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
}
/**
* Set whether tactile feedback for the pattern is enabled.
*/
public void setTactileFeedbackEnabled(boolean enabled) {
- setBoolean(Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled);
+ setBoolean(Settings.Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED, enabled);
}
/**
@@ -339,27 +632,71 @@ public class LockPatternUtils {
return nextAlarm;
}
- private boolean getBoolean(String systemSettingKey) {
+ private boolean getBoolean(String secureSettingKey) {
return 1 ==
- android.provider.Settings.System.getInt(
- mContentResolver,
- systemSettingKey, 0);
+ android.provider.Settings.Secure.getInt(mContentResolver, secureSettingKey, 0);
+ }
+
+ private void setBoolean(String secureSettingKey, boolean enabled) {
+ android.provider.Settings.Secure.putInt(mContentResolver, secureSettingKey,
+ enabled ? 1 : 0);
}
- private void setBoolean(String systemSettingKey, boolean enabled) {
- android.provider.Settings.System.putInt(
- mContentResolver,
- systemSettingKey,
- enabled ? 1 : 0);
+ private long getLong(String secureSettingKey, long def) {
+ return android.provider.Settings.Secure.getLong(mContentResolver, secureSettingKey, def);
}
- private long getLong(String systemSettingKey, long def) {
- return android.provider.Settings.System.getLong(mContentResolver, systemSettingKey, def);
+ private void setLong(String secureSettingKey, long value) {
+ android.provider.Settings.Secure.putLong(mContentResolver, secureSettingKey, value);
}
- private void setLong(String systemSettingKey, long value) {
- android.provider.Settings.System.putLong(mContentResolver, systemSettingKey, value);
+ public boolean isSecure() {
+ long mode = getKeyguardStoredPasswordQuality();
+ final boolean isPattern = mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+ final boolean isPassword = mode == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
+ || mode == DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+ final boolean secure = isPattern && isLockPatternEnabled() && savedPatternExists()
+ || isPassword && savedPasswordExists();
+ return secure;
}
+ /**
+ * Sets the text on the emergency button to indicate what action will be taken.
+ * If there's currently a call in progress, the button will take them to the call
+ * @param button the button to update
+ */
+ public void updateEmergencyCallButtonState(Button button) {
+ int newState = TelephonyManager.getDefault().getCallState();
+ int textId;
+ if (newState == TelephonyManager.CALL_STATE_OFFHOOK) {
+ // show "return to call" text and show phone icon
+ textId = R.string.lockscreen_return_to_call;
+ int phoneCallIcon = R.drawable.stat_sys_phone_call;
+ button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
+ } else {
+ textId = R.string.lockscreen_emergency_call;
+ int emergencyIcon = R.drawable.ic_emergency;
+ button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
+ }
+ button.setText(textId);
+ }
+ /**
+ * Resumes a call in progress. Typically launched from the EmergencyCall button
+ * on various lockscreens.
+ *
+ * @return true if we were able to tell InCallScreen to show.
+ */
+ public boolean resumeCall() {
+ ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+ try {
+ if (phone != null && phone.showCallScreen()) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ // What can we do?
+ }
+ return false;
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 6adce6d..007e7b9 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -21,6 +21,7 @@ import com.android.internal.R;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -50,6 +51,11 @@ import java.util.List;
* "correct" states.
*/
public class LockPatternView extends View {
+ // Aspect to use when rendering this view
+ private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height
+ private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h)
+ private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
+
// Vibrator pattern for creating a tactile bump
private static final long[] DEFAULT_VIBE_PATTERN = {0, 1, 40, 41};
@@ -116,12 +122,14 @@ public class LockPatternView extends View {
private int mBitmapWidth;
private int mBitmapHeight;
-
+
private Vibrator vibe; // Vibrator for creating tactile feedback
private long[] mVibePattern;
+ private int mAspect;
+
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
@@ -237,6 +245,20 @@ public class LockPatternView extends View {
super(context, attrs);
vibe = new Vibrator();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView);
+
+ final String aspect = a.getString(R.styleable.LockPatternView_aspect);
+
+ if ("square".equals(aspect)) {
+ mAspect = ASPECT_SQUARE;
+ } else if ("lock_width".equals(aspect)) {
+ mAspect = ASPECT_LOCK_WIDTH;
+ } else if ("lock_height".equals(aspect)) {
+ mAspect = ASPECT_LOCK_HEIGHT;
+ } else {
+ mAspect = ASPECT_SQUARE;
+ }
+
setClickable(true);
mPathPaint.setAntiAlias(true);
@@ -425,8 +447,22 @@ public class LockPatternView extends View {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
- final int squareSide = Math.min(width, height);
- setMeasuredDimension(squareSide, squareSide);
+ int viewWidth = width;
+ int viewHeight = height;
+ switch (mAspect) {
+ case ASPECT_SQUARE:
+ viewWidth = viewHeight = Math.min(width, height);
+ break;
+ case ASPECT_LOCK_WIDTH:
+ viewWidth = width;
+ viewHeight = Math.min(width, height);
+ break;
+ case ASPECT_LOCK_HEIGHT:
+ viewWidth = Math.min(width, height);
+ viewHeight = height;
+ break;
+ }
+ setMeasuredDimension(viewWidth, viewHeight);
}
/**
@@ -890,17 +926,17 @@ public class LockPatternView extends View {
Matrix matrix = new Matrix();
final int cellWidth = mBitmapCircleDefault.getWidth();
final int cellHeight = mBitmapCircleDefault.getHeight();
-
+
// the up arrow bitmap is at 12:00, so find the rotation from x axis and add 90 degrees.
final float theta = (float) Math.atan2(
(double) (endRow - startRow), (double) (endColumn - startColumn));
- final float angle = (float) Math.toDegrees(theta) + 90.0f;
-
+ final float angle = (float) Math.toDegrees(theta) + 90.0f;
+
// compose matrix
matrix.setTranslate(leftX + offsetX, topY + offsetY); // transform to cell position
matrix.preRotate(angle, cellWidth / 2.0f, cellHeight / 2.0f); // rotate about cell center
matrix.preTranslate((cellWidth - arrow.getWidth()) / 2.0f, 0.0f); // translate to 12:00 pos
- canvas.drawBitmap(arrow, matrix, mPaint);
+ canvas.drawBitmap(arrow, matrix, mPaint);
}
/**
@@ -1004,7 +1040,7 @@ public class LockPatternView extends View {
mInStealthMode = (Boolean) in.readValue(null);
mTactileFeedbackEnabled = (Boolean) in.readValue(null);
}
-
+
public String getSerializedPattern() {
return mSerializedPattern;
}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboard.java b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
new file mode 100644
index 0000000..e1a6737
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboard.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES 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 java.util.Locale;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Paint.Align;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.util.Log;
+import com.android.internal.R;
+
+/**
+ * A basic, embed-able keyboard designed for password entry. Allows entry of all Latin-1 characters.
+ *
+ * It has two modes: alpha and numeric. In alpha mode, it allows all Latin-1 characters and enables
+ * an additional keyboard with symbols. In numeric mode, it shows a 12-key DTMF dialer-like
+ * keypad with alpha characters hints.
+ */
+public class PasswordEntryKeyboard extends Keyboard {
+ private static final String TAG = "PasswordEntryKeyboard";
+ private static final int SHIFT_OFF = 0;
+ private static final int SHIFT_ON = 1;
+ private static final int SHIFT_LOCKED = 2;
+ public static final int KEYCODE_SPACE = ' ';
+
+ private Drawable mShiftIcon;
+ private Drawable mShiftLockIcon;
+ private Drawable mShiftLockPreviewIcon;
+ private Drawable mOldShiftIcon;
+ private Drawable mOldShiftPreviewIcon;
+ private Drawable mSpaceIcon;
+ private Key mShiftKey;
+ private Key mEnterKey;
+ private Key mF1Key;
+ private Key mSpaceKey;
+ private Locale mLocale;
+ private Resources mRes;
+ private int mExtensionResId;
+ private int mShiftState = SHIFT_OFF;
+
+ static int sSpacebarVerticalCorrection;
+
+ public PasswordEntryKeyboard(Context context, int xmlLayoutResId) {
+ this(context, xmlLayoutResId, 0);
+ }
+
+ public PasswordEntryKeyboard(Context context, int xmlLayoutResId, int mode) {
+ super(context, xmlLayoutResId, mode);
+ final Resources res = context.getResources();
+ mRes = res;
+ mShiftIcon = res.getDrawable(R.drawable.sym_keyboard_shift);
+ mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
+ mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
+ mShiftLockPreviewIcon.setBounds(0, 0,
+ mShiftLockPreviewIcon.getIntrinsicWidth(),
+ mShiftLockPreviewIcon.getIntrinsicHeight());
+ mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
+ sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
+ R.dimen.password_keyboard_spacebar_vertical_correction);
+ }
+
+ public PasswordEntryKeyboard(Context context, int layoutTemplateResId,
+ CharSequence characters, int columns, int horizontalPadding) {
+ super(context, layoutTemplateResId, characters, columns, horizontalPadding);
+ }
+
+ @Override
+ protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
+ XmlResourceParser parser) {
+ LatinKey key = new LatinKey(res, parent, x, y, parser);
+ final int code = key.codes[0];
+ if (code >=0 && code != '\n' && (code < 32 || code > 127)) {
+ // Log.w(TAG, "Key code for " + key.label + " is not latin-1");
+ key.label = " ";
+ key.setEnabled(false);
+ }
+ switch (key.codes[0]) {
+ case 10:
+ mEnterKey = key;
+ break;
+ case PasswordEntryKeyboardView.KEYCODE_F1:
+ mF1Key = key;
+ break;
+ case 32:
+ mSpaceKey = key;
+ break;
+ }
+ return key;
+ }
+
+ /**
+ * Allows enter key resources to be overridden
+ * @param res resources to grab given items from
+ * @param previewId preview drawable shown on enter key
+ * @param iconId normal drawable shown on enter key
+ * @param labelId string shown on enter key
+ */
+ void setEnterKeyResources(Resources res, int previewId, int iconId, int labelId) {
+ if (mEnterKey != null) {
+ // Reset some of the rarely used attributes.
+ mEnterKey.popupCharacters = null;
+ mEnterKey.popupResId = 0;
+ mEnterKey.text = null;
+
+ mEnterKey.iconPreview = res.getDrawable(previewId);
+ mEnterKey.icon = res.getDrawable(iconId);
+ mEnterKey.label = res.getText(labelId);
+
+ // Set the initial size of the preview icon
+ if (mEnterKey.iconPreview != null) {
+ mEnterKey.iconPreview.setBounds(0, 0,
+ mEnterKey.iconPreview.getIntrinsicWidth(),
+ mEnterKey.iconPreview.getIntrinsicHeight());
+ }
+ }
+ }
+
+ /**
+ * Allows shiftlock to be turned on. See {@link #setShiftLocked(boolean)}
+ *
+ */
+ void enableShiftLock() {
+ int index = getShiftKeyIndex();
+ if (index >= 0) {
+ mShiftKey = getKeys().get(index);
+ if (mShiftKey instanceof LatinKey) {
+ ((LatinKey)mShiftKey).enableShiftLock();
+ }
+ mOldShiftIcon = mShiftKey.icon;
+ mOldShiftPreviewIcon = mShiftKey.iconPreview;
+ }
+ }
+
+ /**
+ * Turn on shift lock. This turns on the LED for this key, if it has one.
+ * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
+ * or {@link KeyboardView#invalidateAllKeys()}
+ *
+ * @param shiftLocked
+ */
+ void setShiftLocked(boolean shiftLocked) {
+ if (mShiftKey != null) {
+ if (shiftLocked) {
+ mShiftKey.on = true;
+ mShiftKey.icon = mShiftLockIcon;
+ mShiftState = SHIFT_LOCKED;
+ } else {
+ mShiftKey.on = false;
+ mShiftKey.icon = mShiftLockIcon;
+ mShiftState = SHIFT_ON;
+ }
+ }
+ }
+
+ /**
+ * Turn on shift mode. Sets shift mode and turns on icon for shift key.
+ * It should be followed by a call to {@link KeyboardView#invalidateKey(int)}
+ * or {@link KeyboardView#invalidateAllKeys()}
+ *
+ * @param shiftLocked
+ */
+ @Override
+ public boolean setShifted(boolean shiftState) {
+ boolean shiftChanged = false;
+ if (mShiftKey != null) {
+ if (shiftState == false) {
+ shiftChanged = mShiftState != SHIFT_OFF;
+ mShiftState = SHIFT_OFF;
+ mShiftKey.on = false;
+ mShiftKey.icon = mOldShiftIcon;
+ } else if (mShiftState == SHIFT_OFF) {
+ shiftChanged = mShiftState == SHIFT_OFF;
+ mShiftState = SHIFT_ON;
+ mShiftKey.on = false;
+ mShiftKey.icon = mShiftIcon;
+ }
+ } else {
+ return super.setShifted(shiftState);
+ }
+ return shiftChanged;
+ }
+
+ /**
+ * Whether or not keyboard is shifted.
+ * @return true if keyboard state is shifted.
+ */
+ @Override
+ public boolean isShifted() {
+ if (mShiftKey != null) {
+ return mShiftState != SHIFT_OFF;
+ } else {
+ return super.isShifted();
+ }
+ }
+
+ static class LatinKey extends Keyboard.Key {
+ private boolean mShiftLockEnabled;
+ private boolean mEnabled = true;
+
+ public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
+ XmlResourceParser parser) {
+ super(res, parent, x, y, parser);
+ if (popupCharacters != null && popupCharacters.length() == 0) {
+ // If there is a keyboard with no keys specified in popupCharacters
+ popupResId = 0;
+ }
+ }
+
+ void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ void enableShiftLock() {
+ mShiftLockEnabled = true;
+ }
+
+ @Override
+ public void onReleased(boolean inside) {
+ if (!mShiftLockEnabled) {
+ super.onReleased(inside);
+ } else {
+ pressed = !pressed;
+ }
+ }
+
+ /**
+ * Overriding this method so that we can reduce the target area for certain keys.
+ */
+ @Override
+ public boolean isInside(int x, int y) {
+ if (!mEnabled) {
+ return false;
+ }
+ final int code = codes[0];
+ if (code == KEYCODE_SHIFT || code == KEYCODE_DELETE) {
+ y -= height / 10;
+ if (code == KEYCODE_SHIFT) x += width / 6;
+ if (code == KEYCODE_DELETE) x -= width / 6;
+ } else if (code == KEYCODE_SPACE) {
+ y += PasswordEntryKeyboard.sSpacebarVerticalCorrection;
+ }
+ return super.isInside(x, y);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
new file mode 100644
index 0000000..53720e4
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewRoot;
+import com.android.internal.R;
+
+public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
+
+ public static final int KEYBOARD_MODE_ALPHA = 0;
+ public static final int KEYBOARD_MODE_NUMERIC = 1;
+ private static final int KEYBOARD_STATE_NORMAL = 0;
+ private static final int KEYBOARD_STATE_SHIFTED = 1;
+ private static final int KEYBOARD_STATE_CAPSLOCK = 2;
+ private static final String TAG = "PasswordEntryKeyboardHelper";
+ private int mKeyboardMode = KEYBOARD_MODE_ALPHA;
+ private int mKeyboardState = KEYBOARD_STATE_NORMAL;
+ private PasswordEntryKeyboard mQwertyKeyboard;
+ private PasswordEntryKeyboard mQwertyKeyboardShifted;
+ private PasswordEntryKeyboard mSymbolsKeyboard;
+ private PasswordEntryKeyboard mSymbolsKeyboardShifted;
+ private PasswordEntryKeyboard mNumericKeyboard;
+ private Context mContext;
+ private View mTargetView;
+ private KeyboardView mKeyboardView;
+ private long[] mVibratePattern;
+ private Vibrator mVibrator;
+
+ public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
+ mContext = context;
+ mTargetView = targetView;
+ mKeyboardView = keyboardView;
+ createKeyboards();
+ mKeyboardView.setOnKeyboardActionListener(this);
+ mVibrator = new Vibrator();
+ }
+
+ public boolean isAlpha() {
+ return mKeyboardMode == KEYBOARD_MODE_ALPHA;
+ }
+
+ private void createKeyboards() {
+ mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric);
+ mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_qwerty, R.id.mode_normal);
+ mQwertyKeyboard.enableShiftLock();
+
+ mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_qwerty_shifted,
+ R.id.mode_normal);
+ mQwertyKeyboardShifted.enableShiftLock();
+ mQwertyKeyboardShifted.setShifted(true); // always shifted.
+
+ mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols);
+ mSymbolsKeyboard.enableShiftLock();
+
+ mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext,
+ R.xml.password_kbd_symbols_shift);
+ mSymbolsKeyboardShifted.enableShiftLock();
+ mSymbolsKeyboardShifted.setShifted(true); // always shifted
+ }
+
+ public void setKeyboardMode(int mode) {
+ switch (mode) {
+ case KEYBOARD_MODE_ALPHA:
+ mKeyboardView.setKeyboard(mQwertyKeyboard);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ final boolean visiblePassword = Settings.System.getInt(
+ mContext.getContentResolver(),
+ Settings.System.TEXT_SHOW_PASSWORD, 1) != 0;
+ mKeyboardView.setPreviewEnabled(visiblePassword);
+ break;
+ case KEYBOARD_MODE_NUMERIC:
+ mKeyboardView.setKeyboard(mNumericKeyboard);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ mKeyboardView.setPreviewEnabled(false); // never show popup for numeric keypad
+ break;
+ }
+ mKeyboardMode = mode;
+ }
+
+ private void sendKeyEventsToTarget(int character) {
+ Handler handler = mTargetView.getHandler();
+ KeyEvent[] events = KeyCharacterMap.load(KeyCharacterMap.ALPHA).getEvents(
+ new char[] { (char) character });
+ if (events != null) {
+ final int N = events.length;
+ for (int i=0; i<N; i++) {
+ 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));
+ }
+ }
+ }
+
+ public void sendDownUpKeyEvents(int keyEventCode) {
+ long eventTime = SystemClock.uptimeMillis();
+ Handler handler = mTargetView.getHandler();
+ handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ handler.sendMessage(handler.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME,
+ new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE)));
+ }
+
+ public void onKey(int primaryCode, int[] keyCodes) {
+ if (primaryCode == Keyboard.KEYCODE_DELETE) {
+ handleBackspace();
+ } else if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+ handleShift();
+ } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
+ handleClose();
+ return;
+ } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE && mKeyboardView != null) {
+ handleModeChange();
+ } else {
+ handleCharacter(primaryCode, keyCodes);
+ // Switch back to old keyboard if we're not in capslock mode
+ if (mKeyboardState == KEYBOARD_STATE_SHIFTED) {
+ // skip to the unlocked state
+ mKeyboardState = KEYBOARD_STATE_CAPSLOCK;
+ handleShift();
+ }
+ }
+ }
+
+ /**
+ * Sets and enables vibrate pattern. If id is 0 (or can't be loaded), vibrate is disabled.
+ * @param id resource id for array containing vibrate pattern.
+ */
+ public void setVibratePattern(int id) {
+ int[] tmpArray = null;
+ try {
+ tmpArray = mContext.getResources().getIntArray(id);
+ } catch (Resources.NotFoundException e) {
+ if (id != 0) {
+ Log.e(TAG, "Vibrate pattern missing", e);
+ }
+ }
+ if (tmpArray == null) {
+ mVibratePattern = null;
+ return;
+ }
+ mVibratePattern = new long[tmpArray.length];
+ for (int i = 0; i < tmpArray.length; i++) {
+ mVibratePattern[i] = tmpArray[i];
+ }
+ }
+
+ private void handleModeChange() {
+ final Keyboard current = mKeyboardView.getKeyboard();
+ Keyboard next = null;
+ if (current == mQwertyKeyboard || current == mQwertyKeyboardShifted) {
+ next = mSymbolsKeyboard;
+ } else if (current == mSymbolsKeyboard || current == mSymbolsKeyboardShifted) {
+ next = mQwertyKeyboard;
+ }
+ if (next != null) {
+ mKeyboardView.setKeyboard(next);
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ }
+ }
+
+ private void handleBackspace() {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ }
+
+ private void handleShift() {
+ if (mKeyboardView == null) {
+ return;
+ }
+ Keyboard current = mKeyboardView.getKeyboard();
+ PasswordEntryKeyboard next = null;
+ final boolean isAlphaMode = current == mQwertyKeyboard
+ || current == mQwertyKeyboardShifted;
+ if (mKeyboardState == KEYBOARD_STATE_NORMAL) {
+ mKeyboardState = isAlphaMode ? KEYBOARD_STATE_SHIFTED : KEYBOARD_STATE_CAPSLOCK;
+ next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted;
+ } else if (mKeyboardState == KEYBOARD_STATE_SHIFTED) {
+ mKeyboardState = KEYBOARD_STATE_CAPSLOCK;
+ next = isAlphaMode ? mQwertyKeyboardShifted : mSymbolsKeyboardShifted;
+ } else if (mKeyboardState == KEYBOARD_STATE_CAPSLOCK) {
+ mKeyboardState = KEYBOARD_STATE_NORMAL;
+ next = isAlphaMode ? mQwertyKeyboard : mSymbolsKeyboard;
+ }
+ if (next != null) {
+ if (next != current) {
+ mKeyboardView.setKeyboard(next);
+ }
+ next.setShiftLocked(mKeyboardState == KEYBOARD_STATE_CAPSLOCK);
+ mKeyboardView.setShifted(mKeyboardState != KEYBOARD_STATE_NORMAL);
+ }
+ }
+
+ private void handleCharacter(int primaryCode, int[] keyCodes) {
+ // Maybe turn off shift if not in capslock mode.
+ if (mKeyboardView.isShifted() && primaryCode != ' ' && primaryCode != '\n') {
+ primaryCode = Character.toUpperCase(primaryCode);
+ }
+ sendKeyEventsToTarget(primaryCode);
+ }
+
+ private void handleClose() {
+
+ }
+
+ public void onPress(int primaryCode) {
+ if (mVibratePattern != null) {
+ mVibrator.vibrate(mVibratePattern, -1);
+ }
+ }
+
+ public void onRelease(int primaryCode) {
+
+ }
+
+ public void onText(CharSequence text) {
+
+ }
+
+ public void swipeDown() {
+
+ }
+
+ public void swipeLeft() {
+
+ }
+
+ public void swipeRight() {
+
+ }
+
+ public void swipeUp() {
+
+ }
+};
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
new file mode 100644
index 0000000..3e6f6f3
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.inputmethodservice.KeyboardView;
+import android.util.AttributeSet;
+
+public class PasswordEntryKeyboardView extends KeyboardView {
+
+ static final int KEYCODE_OPTIONS = -100;
+ static final int KEYCODE_SHIFT_LONGPRESS = -101;
+ static final int KEYCODE_VOICE = -102;
+ static final int KEYCODE_F1 = -103;
+ static final int KEYCODE_NEXT_LANGUAGE = -104;
+
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public PasswordEntryKeyboardView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
new file mode 100644
index 0000000..f487a16
--- /dev/null
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+
+public class PointerLocationView extends View {
+ public static class PointerState {
+ private final ArrayList<Float> mXs = new ArrayList<Float>();
+ private final ArrayList<Float> mYs = new ArrayList<Float>();
+ private boolean mCurDown;
+ private int mCurX;
+ private int mCurY;
+ private float mCurPressure;
+ private float mCurSize;
+ private int mCurWidth;
+ private VelocityTracker mVelocity;
+ }
+
+ private final ViewConfiguration mVC;
+ private final Paint mTextPaint;
+ private final Paint mTextBackgroundPaint;
+ private final Paint mTextLevelPaint;
+ private final Paint mPaint;
+ private final Paint mTargetPaint;
+ private final Paint mPathPaint;
+ private final FontMetricsInt mTextMetrics = new FontMetricsInt();
+ private int mHeaderBottom;
+ private boolean mCurDown;
+ private int mCurNumPointers;
+ private int mMaxNumPointers;
+ private final ArrayList<PointerState> mPointers
+ = new ArrayList<PointerState>();
+
+ private boolean mPrintCoords = true;
+
+ public PointerLocationView(Context c) {
+ super(c);
+ setFocusable(true);
+ mVC = ViewConfiguration.get(c);
+ mTextPaint = new Paint();
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setTextSize(10
+ * getResources().getDisplayMetrics().density);
+ mTextPaint.setARGB(255, 0, 0, 0);
+ mTextBackgroundPaint = new Paint();
+ mTextBackgroundPaint.setAntiAlias(false);
+ mTextBackgroundPaint.setARGB(128, 255, 255, 255);
+ mTextLevelPaint = new Paint();
+ mTextLevelPaint.setAntiAlias(false);
+ mTextLevelPaint.setARGB(192, 255, 0, 0);
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setARGB(255, 255, 255, 255);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(2);
+ mTargetPaint = new Paint();
+ mTargetPaint.setAntiAlias(false);
+ mTargetPaint.setARGB(255, 0, 0, 192);
+ mPathPaint = new Paint();
+ mPathPaint.setAntiAlias(false);
+ mPathPaint.setARGB(255, 0, 96, 255);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setStrokeWidth(1);
+
+ PointerState ps = new PointerState();
+ ps.mVelocity = VelocityTracker.obtain();
+ mPointers.add(ps);
+ }
+
+ public void setPrintCoords(boolean state) {
+ mPrintCoords = state;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mTextPaint.getFontMetricsInt(mTextMetrics);
+ mHeaderBottom = -mTextMetrics.ascent+mTextMetrics.descent+2;
+ if (false) {
+ Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent
+ + " descent=" + mTextMetrics.descent
+ + " leading=" + mTextMetrics.leading
+ + " top=" + mTextMetrics.top
+ + " bottom=" + mTextMetrics.bottom);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ synchronized (mPointers) {
+ final int w = getWidth();
+ final int itemW = w/7;
+ final int base = -mTextMetrics.ascent+1;
+ final int bottom = mHeaderBottom;
+
+ final int NP = mPointers.size();
+
+ if (NP > 0) {
+ final PointerState ps = mPointers.get(0);
+ canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
+ canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers,
+ 1, base, mTextPaint);
+
+ final int N = ps.mXs.size();
+ if ((mCurDown && ps.mCurDown) || N == 0) {
+ canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
+ canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint);
+ canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
+ canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint);
+ } else {
+ float dx = ps.mXs.get(N-1) - ps.mXs.get(0);
+ float dy = ps.mYs.get(N-1) - ps.mYs.get(0);
+ canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
+ Math.abs(dx) < mVC.getScaledTouchSlop()
+ ? mTextBackgroundPaint : mTextLevelPaint);
+ canvas.drawText("dX: " + String.format("%.1f", dx), 1 + itemW, base, mTextPaint);
+ canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
+ Math.abs(dy) < mVC.getScaledTouchSlop()
+ ? mTextBackgroundPaint : mTextLevelPaint);
+ canvas.drawText("dY: " + String.format("%.1f", dy), 1 + itemW * 2, base, mTextPaint);
+ }
+
+ canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
+ int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000);
+ canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint);
+
+ canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
+ velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000);
+ canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint);
+
+ canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
+ canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1,
+ bottom, mTextLevelPaint);
+ canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5,
+ base, mTextPaint);
+
+ canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
+ canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1,
+ bottom, mTextLevelPaint);
+ canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6,
+ base, mTextPaint);
+ }
+
+ for (int p=0; p<NP; p++) {
+ final PointerState ps = mPointers.get(p);
+
+ if (mCurDown && ps.mCurDown) {
+ canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint);
+ canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint);
+ int pressureLevel = (int)(ps.mCurPressure*255);
+ mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
+ canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint);
+ canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint);
+ }
+ }
+
+ for (int p=0; p<NP; p++) {
+ final PointerState ps = mPointers.get(p);
+
+ final int N = ps.mXs.size();
+ float lastX=0, lastY=0;
+ boolean haveLast = false;
+ boolean drawn = false;
+ mPaint.setARGB(255, 128, 255, 255);
+ for (int i=0; i<N; i++) {
+ float x = ps.mXs.get(i);
+ float y = ps.mYs.get(i);
+ if (Float.isNaN(x)) {
+ haveLast = false;
+ continue;
+ }
+ if (haveLast) {
+ canvas.drawLine(lastX, lastY, x, y, mPathPaint);
+ canvas.drawPoint(lastX, lastY, mPaint);
+ drawn = true;
+ }
+ lastX = x;
+ lastY = y;
+ haveLast = true;
+ }
+
+ if (drawn) {
+ if (ps.mVelocity != null) {
+ mPaint.setARGB(255, 255, 64, 128);
+ float xVel = ps.mVelocity.getXVelocity() * (1000/60);
+ float yVel = ps.mVelocity.getYVelocity() * (1000/60);
+ canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
+ } else {
+ canvas.drawPoint(lastX, lastY, mPaint);
+ }
+ }
+ }
+ }
+ }
+
+ public void addTouchEvent(MotionEvent event) {
+ synchronized (mPointers) {
+ int action = event.getAction();
+
+ //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action)
+ // + " pointers=" + event.getPointerCount());
+
+ 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) {
+ for (int p=0; p<NP; p++) {
+ final PointerState ps = mPointers.get(p);
+ ps.mXs.clear();
+ ps.mYs.clear();
+ ps.mVelocity = VelocityTracker.obtain();
+ ps.mCurDown = false;
+ }
+ mPointers.get(0).mCurDown = true;
+ mMaxNumPointers = 0;
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer 1: DOWN");
+ }
+ }
+
+ if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
+ final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int id = event.getPointerId(index);
+ while (NP <= id) {
+ PointerState ps = new PointerState();
+ ps.mVelocity = VelocityTracker.obtain();
+ mPointers.add(ps);
+ NP++;
+ }
+ final PointerState ps = mPointers.get(id);
+ ps.mVelocity = VelocityTracker.obtain();
+ ps.mCurDown = true;
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer " + (id+1) + ": DOWN");
+ }
+ }
+
+ final int NI = event.getPointerCount();
+
+ mCurDown = action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL;
+ mCurNumPointers = mCurDown ? NI : 0;
+ if (mMaxNumPointers < mCurNumPointers) {
+ mMaxNumPointers = mCurNumPointers;
+ }
+
+ for (int i=0; i<NI; i++) {
+ final int id = event.getPointerId(i);
+ final PointerState ps = mPointers.get(id);
+ ps.mVelocity.addMovement(event);
+ ps.mVelocity.computeCurrentVelocity(1);
+ final int N = event.getHistorySize();
+ for (int j=0; j<N; j++) {
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer " + (id+1) + ": ("
+ + event.getHistoricalX(i, j)
+ + ", " + event.getHistoricalY(i, j) + ")"
+ + " Prs=" + event.getHistoricalPressure(i, j)
+ + " Size=" + event.getHistoricalSize(i, j));
+ }
+ ps.mXs.add(event.getHistoricalX(i, j));
+ ps.mYs.add(event.getHistoricalY(i, j));
+ }
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer " + (id+1) + ": ("
+ + event.getX(i) + ", " + event.getY(i) + ")"
+ + " Prs=" + event.getPressure(i)
+ + " Size=" + event.getSize(i));
+ }
+ ps.mXs.add(event.getX(i));
+ ps.mYs.add(event.getY(i));
+ ps.mCurX = (int)event.getX(i);
+ ps.mCurY = (int)event.getY(i);
+ //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX
+ // + "," + ps.mCurY + ")");
+ ps.mCurPressure = event.getPressure(i);
+ ps.mCurSize = event.getSize(i);
+ ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
+ }
+
+ if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+ final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ final int id = event.getPointerId(index);
+ final PointerState ps = mPointers.get(id);
+ ps.mXs.add(Float.NaN);
+ ps.mYs.add(Float.NaN);
+ ps.mCurDown = false;
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer " + (id+1) + ": UP");
+ }
+ }
+
+ if (action == MotionEvent.ACTION_UP) {
+ for (int i=0; i<NI; i++) {
+ final int id = event.getPointerId(i);
+ final PointerState ps = mPointers.get(id);
+ if (ps.mCurDown) {
+ ps.mCurDown = false;
+ if (mPrintCoords) {
+ Log.i("Pointer", "Pointer " + (id+1) + ": UP");
+ }
+ }
+ }
+ }
+
+ //if (mCurDown) {
+ // mRect.union(mCurX-mCurWidth-3, mCurY-mCurWidth-3,
+ // mCurX+mCurWidth+3, mCurY+mCurWidth+3);
+ //}
+ //invalidate(mRect);
+ postInvalidate();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ addTouchEvent(event);
+ return true;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ Log.i("Pointer", "Trackball: " + event);
+ return super.onTrackballEvent(event);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 07955c4..9152729 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -23,8 +23,6 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Log;
@@ -34,7 +32,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import android.view.animation.Animation.AnimationListener;
@@ -200,7 +197,7 @@ public class SlidingTab extends ViewGroup {
// Create hint TextView
text = new TextView(parent.getContext());
text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.FILL_PARENT));
+ LayoutParams.MATCH_PARENT));
text.setBackgroundResource(barId);
text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
// hint.setSingleLine(); // Hmm.. this causes the text to disappear off-screen
@@ -479,7 +476,9 @@ public class SlidingTab extends ViewGroup {
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
- throw new RuntimeException(LOG_TAG + " cannot have UNSPECIFIED dimensions");
+ Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec"
+ +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")",
+ new RuntimeException(LOG_TAG + "stack:"));
}
mLeftSlider.measure();
@@ -557,6 +556,9 @@ public class SlidingTab extends ViewGroup {
public void reset(boolean animate) {
mLeftSlider.reset(animate);
mRightSlider.reset(animate);
+ if (!animate) {
+ mAnimating = false;
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/VerticalTextSpinner.java b/core/java/com/android/internal/widget/VerticalTextSpinner.java
deleted file mode 100644
index 50c528c..0000000
--- a/core/java/com/android/internal/widget/VerticalTextSpinner.java
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.text.TextPaint;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-
-
-public class VerticalTextSpinner extends View {
-
- private static final int SELECTOR_ARROW_HEIGHT = 15;
-
- private static final int TEXT_SPACING = 18;
- private static final int TEXT_MARGIN_RIGHT = 25;
- private static final int TEXT_SIZE = 22;
-
- /* Keep the calculations as this is really a for loop from
- * -2 to 2 but precalculated so we don't have to do in the onDraw.
- */
- private static final int TEXT1_Y = (TEXT_SIZE * (-2 + 2)) + (TEXT_SPACING * (-2 + 1));
- private static final int TEXT2_Y = (TEXT_SIZE * (-1 + 2)) + (TEXT_SPACING * (-1 + 1));
- private static final int TEXT3_Y = (TEXT_SIZE * (0 + 2)) + (TEXT_SPACING * (0 + 1));
- private static final int TEXT4_Y = (TEXT_SIZE * (1 + 2)) + (TEXT_SPACING * (1 + 1));
- private static final int TEXT5_Y = (TEXT_SIZE * (2 + 2)) + (TEXT_SPACING * (2 + 1));
-
- private static final int SCROLL_MODE_NONE = 0;
- private static final int SCROLL_MODE_UP = 1;
- private static final int SCROLL_MODE_DOWN = 2;
-
- private static final long DEFAULT_SCROLL_INTERVAL_MS = 400;
- private static final int SCROLL_DISTANCE = TEXT_SIZE + TEXT_SPACING;
- private static final int MIN_ANIMATIONS = 4;
-
- private final Drawable mBackgroundFocused;
- private final Drawable mSelectorFocused;
- private final Drawable mSelectorNormal;
- private final int mSelectorDefaultY;
- private final int mSelectorMinY;
- private final int mSelectorMaxY;
- private final int mSelectorHeight;
- private final TextPaint mTextPaintDark;
- private final TextPaint mTextPaintLight;
-
- private int mSelectorY;
- private Drawable mSelector;
- private int mDownY;
- private boolean isDraggingSelector;
- private int mScrollMode;
- private long mScrollInterval;
- private boolean mIsAnimationRunning;
- private boolean mStopAnimation;
- private boolean mWrapAround = true;
-
- private int mTotalAnimatedDistance;
- private int mNumberOfAnimations;
- private long mDelayBetweenAnimations;
- private int mDistanceOfEachAnimation;
-
- private String[] mTextList;
- private int mCurrentSelectedPos;
- private OnChangedListener mListener;
-
- private String mText1;
- private String mText2;
- private String mText3;
- private String mText4;
- private String mText5;
-
- public interface OnChangedListener {
- void onChanged(
- VerticalTextSpinner spinner, int oldPos, int newPos, String[] items);
- }
-
- public VerticalTextSpinner(Context context) {
- this(context, null);
- }
-
- public VerticalTextSpinner(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public VerticalTextSpinner(Context context, AttributeSet attrs,
- int defStyle) {
- super(context, attrs, defStyle);
-
- mBackgroundFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_background);
- mSelectorFocused = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_selected);
- mSelectorNormal = context.getResources().getDrawable(com.android.internal.R.drawable.pickerbox_unselected);
-
- mSelectorHeight = mSelectorFocused.getIntrinsicHeight();
- mSelectorDefaultY = (mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight) / 2;
- mSelectorMinY = 0;
- mSelectorMaxY = mBackgroundFocused.getIntrinsicHeight() - mSelectorHeight;
-
- mSelector = mSelectorNormal;
- mSelectorY = mSelectorDefaultY;
-
- mTextPaintDark = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaintDark.setTextSize(TEXT_SIZE);
- mTextPaintDark.setColor(context.getResources().getColor(com.android.internal.R.color.primary_text_light));
-
- mTextPaintLight = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaintLight.setTextSize(TEXT_SIZE);
- mTextPaintLight.setColor(context.getResources().getColor(com.android.internal.R.color.secondary_text_dark));
-
- mScrollMode = SCROLL_MODE_NONE;
- mScrollInterval = DEFAULT_SCROLL_INTERVAL_MS;
- calculateAnimationValues();
- }
-
- public void setOnChangeListener(OnChangedListener listener) {
- mListener = listener;
- }
-
- public void setItems(String[] textList) {
- mTextList = textList;
- calculateTextPositions();
- }
-
- public void setSelectedPos(int selectedPos) {
- mCurrentSelectedPos = selectedPos;
- calculateTextPositions();
- postInvalidate();
- }
-
- public void setScrollInterval(long interval) {
- mScrollInterval = interval;
- calculateAnimationValues();
- }
-
- public void setWrapAround(boolean wrap) {
- mWrapAround = wrap;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
-
- /* This is a bit confusing, when we get the key event
- * DPAD_DOWN we actually roll the spinner up. When the
- * key event is DPAD_UP we roll the spinner down.
- */
- if ((keyCode == KeyEvent.KEYCODE_DPAD_UP) && canScrollDown()) {
- mScrollMode = SCROLL_MODE_DOWN;
- scroll();
- mStopAnimation = true;
- return true;
- } else if ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && canScrollUp()) {
- mScrollMode = SCROLL_MODE_UP;
- scroll();
- mStopAnimation = true;
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- private boolean canScrollDown() {
- return (mCurrentSelectedPos > 0) || mWrapAround;
- }
-
- private boolean canScrollUp() {
- return ((mCurrentSelectedPos < (mTextList.length - 1)) || mWrapAround);
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction,
- Rect previouslyFocusedRect) {
- if (gainFocus) {
- setBackgroundDrawable(mBackgroundFocused);
- mSelector = mSelectorFocused;
- } else {
- setBackgroundDrawable(null);
- mSelector = mSelectorNormal;
- mSelectorY = mSelectorDefaultY;
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
- final int y = (int) event.getY();
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- requestFocus();
- mDownY = y;
- isDraggingSelector = (y >= mSelectorY) && (y <= (mSelectorY + mSelector.getIntrinsicHeight()));
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (isDraggingSelector) {
- int top = mSelectorDefaultY + (y - mDownY);
- if (top <= mSelectorMinY && canScrollDown()) {
- mSelectorY = mSelectorMinY;
- mStopAnimation = false;
- if (mScrollMode != SCROLL_MODE_DOWN) {
- mScrollMode = SCROLL_MODE_DOWN;
- scroll();
- }
- } else if (top >= mSelectorMaxY && canScrollUp()) {
- mSelectorY = mSelectorMaxY;
- mStopAnimation = false;
- if (mScrollMode != SCROLL_MODE_UP) {
- mScrollMode = SCROLL_MODE_UP;
- scroll();
- }
- } else {
- mSelectorY = top;
- mStopAnimation = true;
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- default:
- mSelectorY = mSelectorDefaultY;
- mStopAnimation = true;
- invalidate();
- break;
- }
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
-
- /* The bounds of the selector */
- final int selectorLeft = 0;
- final int selectorTop = mSelectorY;
- final int selectorRight = mMeasuredWidth;
- final int selectorBottom = mSelectorY + mSelectorHeight;
-
- /* Draw the selector */
- mSelector.setBounds(selectorLeft, selectorTop, selectorRight, selectorBottom);
- mSelector.draw(canvas);
-
- if (mTextList == null) {
-
- /* We're not setup with values so don't draw anything else */
- return;
- }
-
- final TextPaint textPaintDark = mTextPaintDark;
- if (hasFocus()) {
-
- /* The bounds of the top area where the text should be light */
- final int topLeft = 0;
- final int topTop = 0;
- final int topRight = selectorRight;
- final int topBottom = selectorTop + SELECTOR_ARROW_HEIGHT;
-
- /* Assign a bunch of local finals for performance */
- final String text1 = mText1;
- final String text2 = mText2;
- final String text3 = mText3;
- final String text4 = mText4;
- final String text5 = mText5;
- final TextPaint textPaintLight = mTextPaintLight;
-
- /*
- * Draw the 1st, 2nd and 3rd item in light only, clip it so it only
- * draws in the area above the selector
- */
- canvas.save();
- canvas.clipRect(topLeft, topTop, topRight, topBottom);
- drawText(canvas, text1, TEXT1_Y
- + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text2, TEXT2_Y
- + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
- canvas.restore();
-
- /*
- * Draw the 2nd, 3rd and 4th clipped to the selector bounds in dark
- * paint
- */
- canvas.save();
- canvas.clipRect(selectorLeft, selectorTop + SELECTOR_ARROW_HEIGHT,
- selectorRight, selectorBottom - SELECTOR_ARROW_HEIGHT);
- drawText(canvas, text2, TEXT2_Y
- + mTotalAnimatedDistance, textPaintDark);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintDark);
- drawText(canvas, text4,
- TEXT4_Y + mTotalAnimatedDistance, textPaintDark);
- canvas.restore();
-
- /* The bounds of the bottom area where the text should be light */
- final int bottomLeft = 0;
- final int bottomTop = selectorBottom - SELECTOR_ARROW_HEIGHT;
- final int bottomRight = selectorRight;
- final int bottomBottom = mMeasuredHeight;
-
- /*
- * Draw the 3rd, 4th and 5th in white text, clip it so it only draws
- * in the area below the selector.
- */
- canvas.save();
- canvas.clipRect(bottomLeft, bottomTop, bottomRight, bottomBottom);
- drawText(canvas, text3,
- TEXT3_Y + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text4,
- TEXT4_Y + mTotalAnimatedDistance, textPaintLight);
- drawText(canvas, text5,
- TEXT5_Y + mTotalAnimatedDistance, textPaintLight);
- canvas.restore();
-
- } else {
- drawText(canvas, mText3, TEXT3_Y, textPaintDark);
- }
- if (mIsAnimationRunning) {
- if ((Math.abs(mTotalAnimatedDistance) + mDistanceOfEachAnimation) > SCROLL_DISTANCE) {
- mTotalAnimatedDistance = 0;
- if (mScrollMode == SCROLL_MODE_UP) {
- int oldPos = mCurrentSelectedPos;
- int newPos = getNewIndex(1);
- if (newPos >= 0) {
- mCurrentSelectedPos = newPos;
- if (mListener != null) {
- mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
- }
- }
- if (newPos < 0 || ((newPos >= mTextList.length - 1) && !mWrapAround)) {
- mStopAnimation = true;
- }
- calculateTextPositions();
- } else if (mScrollMode == SCROLL_MODE_DOWN) {
- int oldPos = mCurrentSelectedPos;
- int newPos = getNewIndex(-1);
- if (newPos >= 0) {
- mCurrentSelectedPos = newPos;
- if (mListener != null) {
- mListener.onChanged(this, oldPos, mCurrentSelectedPos, mTextList);
- }
- }
- if (newPos < 0 || (newPos == 0 && !mWrapAround)) {
- mStopAnimation = true;
- }
- calculateTextPositions();
- }
- if (mStopAnimation) {
- final int previousScrollMode = mScrollMode;
-
- /* No longer scrolling, we wait till the current animation
- * completes then we stop.
- */
- mIsAnimationRunning = false;
- mStopAnimation = false;
- mScrollMode = SCROLL_MODE_NONE;
-
- /* If the current selected item is an empty string
- * scroll past it.
- */
- if ("".equals(mTextList[mCurrentSelectedPos])) {
- mScrollMode = previousScrollMode;
- scroll();
- mStopAnimation = true;
- }
- }
- } else {
- if (mScrollMode == SCROLL_MODE_UP) {
- mTotalAnimatedDistance -= mDistanceOfEachAnimation;
- } else if (mScrollMode == SCROLL_MODE_DOWN) {
- mTotalAnimatedDistance += mDistanceOfEachAnimation;
- }
- }
- if (mDelayBetweenAnimations > 0) {
- postInvalidateDelayed(mDelayBetweenAnimations);
- } else {
- invalidate();
- }
- }
- }
-
- /**
- * Called every time the text items or current position
- * changes. We calculate store we don't have to calculate
- * onDraw.
- */
- private void calculateTextPositions() {
- mText1 = getTextToDraw(-2);
- mText2 = getTextToDraw(-1);
- mText3 = getTextToDraw(0);
- mText4 = getTextToDraw(1);
- mText5 = getTextToDraw(2);
- }
-
- private String getTextToDraw(int offset) {
- int index = getNewIndex(offset);
- if (index < 0) {
- return "";
- }
- return mTextList[index];
- }
-
- private int getNewIndex(int offset) {
- int index = mCurrentSelectedPos + offset;
- if (index < 0) {
- if (mWrapAround) {
- index += mTextList.length;
- } else {
- return -1;
- }
- } else if (index >= mTextList.length) {
- if (mWrapAround) {
- index -= mTextList.length;
- } else {
- return -1;
- }
- }
- return index;
- }
-
- private void scroll() {
- if (mIsAnimationRunning) {
- return;
- }
- mTotalAnimatedDistance = 0;
- mIsAnimationRunning = true;
- invalidate();
- }
-
- private void calculateAnimationValues() {
- mNumberOfAnimations = (int) mScrollInterval / SCROLL_DISTANCE;
- if (mNumberOfAnimations < MIN_ANIMATIONS) {
- mNumberOfAnimations = MIN_ANIMATIONS;
- mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
- mDelayBetweenAnimations = 0;
- } else {
- mDistanceOfEachAnimation = SCROLL_DISTANCE / mNumberOfAnimations;
- mDelayBetweenAnimations = mScrollInterval / mNumberOfAnimations;
- }
- }
-
- private void drawText(Canvas canvas, String text, int y, TextPaint paint) {
- int width = (int) paint.measureText(text);
- int x = getMeasuredWidth() - width - TEXT_MARGIN_RIGHT;
- canvas.drawText(text, x, y, paint);
- }
-
- public int getCurrentSelectedPos() {
- return mCurrentSelectedPos;
- }
-}
diff --git a/core/java/com/android/internal/widget/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java
new file mode 100644
index 0000000..3d09f08
--- /dev/null
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.widget.LinearLayout;
+
+import static android.view.View.MeasureSpec.*;
+import static com.android.internal.R.*;
+
+/**
+ * A special layout when measured in AT_MOST will take up a given percentage of
+ * the available space.
+ */
+public class WeightedLinearLayout extends LinearLayout {
+ private float mMajorWeight;
+ private float mMinorWeight;
+
+ public WeightedLinearLayout(Context context) {
+ super(context);
+ }
+
+ public WeightedLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, styleable.WeightedLinearLayout);
+
+ mMajorWeight = a.getFloat(styleable.WeightedLinearLayout_majorWeight, 0.0f);
+ mMinorWeight = a.getFloat(styleable.WeightedLinearLayout_minorWeight, 0.0f);
+
+ a.recycle();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final int screenWidth = metrics.widthPixels;
+ final boolean isPortrait = screenWidth < metrics.heightPixels;
+
+ final int widthMode = getMode(widthMeasureSpec);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getMeasuredWidth();
+ int height = getMeasuredHeight();
+ boolean measure = false;
+
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, EXACTLY);
+
+ final float widthWeight = isPortrait ? mMinorWeight : mMajorWeight;
+ if (widthMode == AT_MOST && widthWeight > 0.0f) {
+ if (width < (screenWidth * widthWeight)) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (screenWidth * widthWeight),
+ EXACTLY);
+ measure = true;
+ }
+ }
+
+ // TODO: Support height?
+
+ if (measure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+}
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
index f5be0ec..fbfbe50 100644
--- a/core/java/com/google/android/collect/Sets.java
+++ b/core/java/com/google/android/collect/Sets.java
@@ -44,41 +44,50 @@ public class Sets {
return new HashSet<K>();
}
- /**
- * Creates a {@code HashSet} instance containing the given elements.
- *
- * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
- * following:
- *
- * <p>{@code Set<Base> set = Sets.newHashSet(sub1, sub2);}
- *
- * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code
- * Base}, not of {@code Base} itself. To get around this, you must use:
- *
- * <p>{@code Set<Base> set = Sets.<Base>newHashSet(sub1, sub2);}
- *
- * @param elements the elements that the set should contain
- * @return a newly-created {@code HashSet} containing those elements (minus
- * duplicates)
- */
- public static <E> HashSet<E> newHashSet(E... elements) {
- int capacity = elements.length * 4 / 3 + 1;
- HashSet<E> set = new HashSet<E>(capacity);
- Collections.addAll(set, elements);
- return set;
- }
+ /**
+ * Creates a {@code HashSet} instance containing the given elements.
+ *
+ * <p><b>Note:</b> due to a bug in javac 1.5.0_06, we cannot support the
+ * following:
+ *
+ * <p>{@code Set<Base> set = Sets.newHashSet(sub1, sub2);}
+ *
+ * <p>where {@code sub1} and {@code sub2} are references to subtypes of {@code
+ * Base}, not of {@code Base} itself. To get around this, you must use:
+ *
+ * <p>{@code Set<Base> set = Sets.<Base>newHashSet(sub1, sub2);}
+ *
+ * @param elements the elements that the set should contain
+ * @return a newly-created {@code HashSet} containing those elements (minus
+ * duplicates)
+ */
+ public static <E> HashSet<E> newHashSet(E... elements) {
+ int capacity = elements.length * 4 / 3 + 1;
+ HashSet<E> set = new HashSet<E>(capacity);
+ Collections.addAll(set, elements);
+ return set;
+ }
- /**
- * Creates a {@code SortedSet} instance containing the given elements.
- *
- * @param elements the elements that the set should contain
- * @return a newly-created {@code SortedSet} containing those elements (minus
- * duplicates)
- */
- public static <E> SortedSet<E> newSortedSet(E... elements) {
- SortedSet<E> set = new TreeSet<E>();
- Collections.addAll(set, elements);
- return set;
- }
+ /**
+ * Creates an empty {@code SortedSet} instance.
+ *
+ * @return a newly-created, initially-empty {@code SortedSet}.
+ */
+ public static <E> SortedSet<E> newSortedSet() {
+ return new TreeSet<E>();
+ }
+
+ /**
+ * Creates a {@code SortedSet} instance containing the given elements.
+ *
+ * @param elements the elements that the set should contain
+ * @return a newly-created {@code SortedSet} containing those elements (minus
+ * duplicates)
+ */
+ public static <E> SortedSet<E> newSortedSet(E... elements) {
+ SortedSet<E> set = new TreeSet<E>();
+ Collections.addAll(set, elements);
+ return set;
+ }
}
diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java
deleted file mode 100644
index 9a2a51d..0000000
--- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright 2007 The Android Open Source Project
-
-package com.google.android.gdata.client;
-
-import com.google.android.net.GoogleHttpClient;
-import com.google.wireless.gdata.client.GDataClient;
-import com.google.wireless.gdata.client.HttpException;
-import com.google.wireless.gdata.client.QueryParams;
-import com.google.wireless.gdata.data.StringUtils;
-import com.google.wireless.gdata.parser.ParseException;
-import com.google.wireless.gdata.serializer.GDataSerializer;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.os.SystemProperties;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.io.BufferedInputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-
-/**
- * Implementation of a GDataClient using GoogleHttpClient to make HTTP
- * requests. Always issues GETs and POSTs, using the X-HTTP-Method-Override
- * header when a PUT or DELETE is desired, to avoid issues with firewalls, etc.,
- * that do not allow methods other than GET or POST.
- */
-public class AndroidGDataClient implements GDataClient {
-
- private static final String TAG = "GDataClient";
- private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
-
- private static final String X_HTTP_METHOD_OVERRIDE =
- "X-HTTP-Method-Override";
-
- private static final String DEFAULT_USER_AGENT_APP_VERSION = "Android-GData/1.1";
-
- private static final int MAX_REDIRECTS = 10;
-
- // boolean system property that can be used to control whether or not
- // requests/responses are gzip'd.
- private static final String NO_GZIP_SYSTEM_PROPERTY = "sync.nogzip";
-
- private final GoogleHttpClient mHttpClient;
- private ContentResolver mResolver;
-
- /**
- * Interface for creating HTTP requests. Used by
- * {@link AndroidGDataClient#createAndExecuteMethod}, since HttpUriRequest does not allow for
- * changing the URI after creation, e.g., when you want to follow a redirect.
- */
- private interface HttpRequestCreator {
- HttpUriRequest createRequest(URI uri);
- }
-
- private static class GetRequestCreator implements HttpRequestCreator {
- public GetRequestCreator() {
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpGet get = new HttpGet(uri);
- return get;
- }
- }
-
- private static class PostRequestCreator implements HttpRequestCreator {
- private final String mMethodOverride;
- private final HttpEntity mEntity;
- public PostRequestCreator(String methodOverride, HttpEntity entity) {
- mMethodOverride = methodOverride;
- mEntity = entity;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- if (mMethodOverride != null) {
- post.addHeader(X_HTTP_METHOD_OVERRIDE, mMethodOverride);
- }
- post.setEntity(mEntity);
- return post;
- }
- }
-
- // MAJOR TODO: make this work across redirects (if we can reset the InputStream).
- // OR, read the bits into a local buffer (yuck, the media could be large).
- private static class MediaPutRequestCreator implements HttpRequestCreator {
- private final InputStream mMediaInputStream;
- private final String mContentType;
- public MediaPutRequestCreator(InputStream mediaInputStream, String contentType) {
- mMediaInputStream = mediaInputStream;
- mContentType = contentType;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- post.addHeader(X_HTTP_METHOD_OVERRIDE, "PUT");
- // mMediaInputStream.reset();
- InputStreamEntity entity = new InputStreamEntity(mMediaInputStream,
- -1 /* read until EOF */);
- entity.setContentType(mContentType);
- post.setEntity(entity);
- return post;
- }
- }
-
- /**
- * @deprecated Use AndroidGDAtaClient(Context) instead.
- */
- public AndroidGDataClient(ContentResolver resolver) {
- mHttpClient = new GoogleHttpClient(resolver, DEFAULT_USER_AGENT_APP_VERSION,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = resolver;
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- */
- public AndroidGDataClient(Context context) {
- this(context, DEFAULT_USER_AGENT_APP_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- */
- public AndroidGDataClient(Context context, String appAndVersion) {
- mHttpClient = new GoogleHttpClient(context, appAndVersion,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = context.getContentResolver();
- }
-
- public void close() {
- mHttpClient.close();
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#encodeUri(java.lang.String)
- */
- public String encodeUri(String uri) {
- String encodedUri;
- try {
- encodedUri = URLEncoder.encode(uri, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.e("JakartaGDataClient",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedUri = URLEncoder.encode(uri);
- }
- return encodedUri;
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
- */
- public QueryParams createQueryParams() {
- return new QueryParamsImpl();
- }
-
- // follows redirects
- private InputStream createAndExecuteMethod(HttpRequestCreator creator,
- String uriString,
- String authToken)
- throws HttpException, IOException {
-
- HttpResponse response = null;
- int status = 500;
- int redirectsLeft = MAX_REDIRECTS;
-
- URI uri;
- try {
- uri = new URI(uriString);
- } catch (URISyntaxException use) {
- Log.w(TAG, "Unable to parse " + uriString + " as URI.", use);
- throw new IOException("Unable to parse " + uriString + " as URI: "
- + use.getMessage());
- }
-
- // we follow redirects ourselves, since we want to follow redirects even on POSTs, which
- // the HTTP library does not do. following redirects ourselves also allows us to log
- // the redirects using our own logging.
- while (redirectsLeft > 0) {
-
- HttpUriRequest request = creator.createRequest(uri);
-
- if (!SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- AndroidHttpClient.modifyRequestToAcceptGzipResponse(request);
- }
-
- // only add the auth token if not null (to allow for GData feeds that do not require
- // authentication.)
- if (!TextUtils.isEmpty(authToken)) {
- request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
- }
- if (LOCAL_LOGV) {
- for (Header h : request.getAllHeaders()) {
- Log.v(TAG, h.getName() + ": " + h.getValue());
- }
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Executing " + request.getRequestLine().toString());
- }
-
- response = null;
-
- try {
- response = mHttpClient.execute(request);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to execute HTTP request." + ioe);
- throw ioe;
- }
-
- StatusLine statusLine = response.getStatusLine();
- if (statusLine == null) {
- Log.w(TAG, "StatusLine is null.");
- throw new NullPointerException("StatusLine is null -- should not happen.");
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, response.getStatusLine().toString());
- for (Header h : response.getAllHeaders()) {
- Log.d(TAG, h.getName() + ": " + h.getValue());
- }
- }
- status = statusLine.getStatusCode();
-
- HttpEntity entity = response.getEntity();
-
- if ((status >= 200) && (status < 300) && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- in = logInputStreamContents(in);
- }
- return in;
- }
-
- // TODO: handle 301, 307?
- // TODO: let the http client handle the redirects, if we can be sure we'll never get a
- // redirect on POST.
- if (status == 302) {
- // consume the content, so the connection can be closed.
- entity.consumeContent();
- Header location = response.getFirstHeader("Location");
- if (location == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Redirect requested but no Location "
- + "specified.");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Following redirect to " + location.getValue());
- }
- try {
- uri = new URI(location.getValue());
- } catch (URISyntaxException use) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Unable to parse " + location.getValue() + " as URI.", use);
- throw new IOException("Unable to parse " + location.getValue()
- + " as URI.");
- }
- break;
- }
- --redirectsLeft;
- } else {
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Received " + status + " status code.");
- }
- String errorMessage = null;
- HttpEntity entity = response.getEntity();
- try {
- if (response != null && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buf = new byte[8192];
- int bytesRead = -1;
- while ((bytesRead = in.read(buf)) != -1) {
- baos.write(buf, 0, bytesRead);
- }
- // TODO: use appropriate encoding, picked up from Content-Type.
- errorMessage = new String(baos.toByteArray());
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMessage);
- }
- }
- } finally {
- if (entity != null) {
- entity.consumeContent();
- }
- }
- String exceptionMessage = "Received " + status + " status code";
- if (errorMessage != null) {
- exceptionMessage += (": " + errorMessage);
- }
- throw new HttpException(exceptionMessage, status, null /* InputStream */);
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
- */
- public InputStream getFeedAsStream(String feedUrl,
- String authToken)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), feedUrl, authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access feed.");
- }
-
- /**
- * Log the contents of the input stream.
- * The original input stream is consumed, so the caller must use the
- * BufferedInputStream that is returned.
- * @param in InputStream
- * @return replacement input stream for caller to use
- * @throws IOException
- */
- private InputStream logInputStreamContents(InputStream in) throws IOException {
- if (in == null) {
- return in;
- }
- // bufferSize is the (arbitrary) maximum amount to log.
- // The original InputStream is wrapped in a
- // BufferedInputStream with a 16K buffer. This lets
- // us read up to 16K, write it to the log, and then
- // reset the stream so the the original client can
- // then read the data. The BufferedInputStream
- // provides the mark and reset support, even when
- // the original InputStream does not.
- final int bufferSize = 16384;
- BufferedInputStream bin = new BufferedInputStream(in, bufferSize);
- bin.mark(bufferSize);
- int wanted = bufferSize;
- int totalReceived = 0;
- byte buf[] = new byte[wanted];
- while (wanted > 0) {
- int got = bin.read(buf, totalReceived, wanted);
- if (got <= 0) break; // EOF
- wanted -= got;
- totalReceived += got;
- }
- Log.d(TAG, new String(buf, 0, totalReceived, "UTF-8"));
- bin.reset();
- return bin;
- }
-
- public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), mediaEntryUrl, authToken);
-
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access media entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#createEntry
- */
- public InputStream createEntry(String feedUrl,
- String authToken,
- GDataSerializer entry)
- throws HttpException, IOException {
-
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_CREATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(null /* override */, entity),
- feedUrl,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to create entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#updateEntry
- */
- public InputStream updateEntry(String editUri,
- String authToken,
- GDataSerializer entry)
- throws HttpException, IOException {
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_UPDATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator("PUT", entity),
- editUri,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to update entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#deleteEntry
- */
- public void deleteEntry(String editUri, String authToken)
- throws HttpException, IOException {
- if (StringUtils.isEmpty(editUri)) {
- throw new IllegalArgumentException(
- "you must specify an non-empty edit url");
- }
- InputStream in =
- createAndExecuteMethod(
- new PostRequestCreator("DELETE", null /* entity */),
- editUri,
- authToken);
- if (in == null) {
- throw new IOException("Unable to delete entry.");
- }
- try {
- in.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
-
- public InputStream updateMediaEntry(String editUri, String authToken,
- InputStream mediaEntryInputStream, String contentType)
- throws HttpException, IOException {
- InputStream in = createAndExecuteMethod(
- new MediaPutRequestCreator(mediaEntryInputStream, contentType),
- editUri,
- authToken);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to write media entry.");
- }
-
- private HttpEntity createEntityForEntry(GDataSerializer entry, int format) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- entry.serialize(baos, format);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to serialize entry.", ioe);
- throw ioe;
- } catch (ParseException pe) {
- Log.e(TAG, "Unable to serialize entry.", pe);
- throw new IOException("Unable to serialize entry: " + pe.getMessage());
- }
-
- byte[] entryBytes = baos.toByteArray();
-
- if (entryBytes != null && Log.isLoggable(TAG, Log.DEBUG)) {
- try {
- Log.d(TAG, "Serialized entry: " + new String(entryBytes, "UTF-8"));
- } catch (UnsupportedEncodingException uee) {
- // should not happen
- throw new IllegalStateException("UTF-8 should be supported!",
- uee);
- }
- }
-
- AbstractHttpEntity entity;
- if (SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- entity = new ByteArrayEntity(entryBytes);
- } else {
- entity = AndroidHttpClient.getCompressedEntity(entryBytes, mResolver);
- }
- entity.setContentType(entry.getContentType());
- return entity;
- }
-}
diff --git a/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java b/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
deleted file mode 100644
index a308fc0..0000000
--- a/core/java/com/google/android/gdata/client/AndroidXmlParserFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.google.android.gdata.client;
-
-import com.google.wireless.gdata.parser.xml.XmlParserFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Xml;
-
-/**
- * XmlParserFactory for the Android platform.
- */
-public class AndroidXmlParserFactory implements XmlParserFactory {
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createParser
- */
- public XmlPullParser createParser() throws XmlPullParserException {
- return Xml.newPullParser();
- }
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createSerializer
- */
- public XmlSerializer createSerializer() throws XmlPullParserException {
- return Xml.newSerializer();
- }
-}
diff --git a/core/java/com/google/android/gdata/client/QueryParamsImpl.java b/core/java/com/google/android/gdata/client/QueryParamsImpl.java
deleted file mode 100644
index fbe0d22..0000000
--- a/core/java/com/google/android/gdata/client/QueryParamsImpl.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.google.android.gdata.client;
-
-import com.google.wireless.gdata.client.QueryParams;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Simple implementation of the QueryParams interface.
- */
-// TODO: deal with categories
-public class QueryParamsImpl extends QueryParams {
-
- private final Map<String,String> mParams = new HashMap<String,String>();
-
- /**
- * Creates a new empty QueryParamsImpl.
- */
- public QueryParamsImpl() {
- }
-
- @Override
- public void clear() {
- setEntryId(null);
- mParams.clear();
- }
-
- @Override
- public String generateQueryUrl(String feedUrl) {
-
- if (TextUtils.isEmpty(getEntryId()) &&
- mParams.isEmpty()) {
- // nothing to do
- return feedUrl;
- }
-
- // handle entry IDs
- if (!TextUtils.isEmpty(getEntryId())) {
- if (!mParams.isEmpty()) {
- throw new IllegalStateException("Cannot set both an entry ID "
- + "and other query paramters.");
- }
- return feedUrl + '/' + getEntryId();
- }
-
- // otherwise, append the querystring params.
- StringBuilder sb = new StringBuilder();
- sb.append(feedUrl);
- Set<String> params = mParams.keySet();
- boolean first = true;
- if (feedUrl.contains("?")) {
- first = false;
- } else {
- sb.append('?');
- }
- for (String param : params) {
- String value = mParams.get(param);
- if (value == null) continue;
- if (first) {
- first = false;
- } else {
- sb.append('&');
- }
- sb.append(param);
- sb.append('=');
-
- String encodedValue = null;
-
- try {
- encodedValue = URLEncoder.encode(value, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.w("QueryParamsImpl",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedValue = URLEncoder.encode(value);
- }
- sb.append(encodedValue);
- }
- return sb.toString();
- }
-
- @Override
- public String getParamValue(String param) {
- if (!(mParams.containsKey(param))) {
- return null;
- }
- return mParams.get(param);
- }
-
- @Override
- public void setParamValue(String param, String value) {
- mParams.put(param, value);
- }
-
-}
diff --git a/core/java/com/google/android/gdata2/client/AndroidGDataClient.java b/core/java/com/google/android/gdata2/client/AndroidGDataClient.java
deleted file mode 100644
index 3721fa4..0000000
--- a/core/java/com/google/android/gdata2/client/AndroidGDataClient.java
+++ /dev/null
@@ -1,603 +0,0 @@
-// Copyright 2007 The Android Open Source Project
-
-package com.google.android.gdata2.client;
-
-import com.google.android.net.GoogleHttpClient;
-import com.google.wireless.gdata2.client.GDataClient;
-import com.google.wireless.gdata2.client.HttpException;
-import com.google.wireless.gdata2.client.QueryParams;
-import com.google.wireless.gdata2.data.StringUtils;
-import com.google.wireless.gdata2.parser.ParseException;
-import com.google.wireless.gdata2.serializer.GDataSerializer;
-import com.google.android.gdata2.client.QueryParamsImpl;
-
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.StatusLine;
-import org.apache.http.params.HttpParams;
-import org.apache.http.params.BasicHttpParams;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.os.SystemProperties;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.io.BufferedInputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-
-/**
- * Implementation of a GDataClient using GoogleHttpClient to make HTTP
- * requests. Always issues GETs and POSTs, using the X-HTTP-Method-Override
- * header when a PUT or DELETE is desired, to avoid issues with firewalls, etc.,
- * that do not allow methods other than GET or POST.
- */
-public class AndroidGDataClient implements GDataClient {
-
- private static final String TAG = "GDataClient";
- private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
-
- private static final String X_HTTP_METHOD_OVERRIDE =
- "X-HTTP-Method-Override";
-
- private static final String DEFAULT_USER_AGENT_APP_VERSION = "Android-GData/1.2";
-
- private static final int MAX_REDIRECTS = 10;
- private static String DEFAULT_GDATA_VERSION = "2.0";
-
- // boolean system property that can be used to control whether or not
- // requests/responses are gzip'd.
- private static final String NO_GZIP_SYSTEM_PROPERTY = "sync.nogzip";
-
- private String mGDataVersion;
- private final GoogleHttpClient mHttpClient;
- private ContentResolver mResolver;
-
- /**
- * Interface for creating HTTP requests. Used by
- * {@link AndroidGDataClient#createAndExecuteMethod}, since HttpUriRequest does not allow for
- * changing the URI after creation, e.g., when you want to follow a redirect.
- */
- private interface HttpRequestCreator {
- HttpUriRequest createRequest(URI uri);
- }
-
- private static class GetRequestCreator implements HttpRequestCreator {
- public GetRequestCreator() {
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpGet get = new HttpGet(uri);
- return get;
- }
- }
-
- private static class PostRequestCreator implements HttpRequestCreator {
- private final String mMethodOverride;
- private final HttpEntity mEntity;
- public PostRequestCreator(String methodOverride, HttpEntity entity) {
- mMethodOverride = methodOverride;
- mEntity = entity;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- if (mMethodOverride != null) {
- post.addHeader(X_HTTP_METHOD_OVERRIDE, mMethodOverride);
- }
- post.setEntity(mEntity);
- return post;
- }
- }
-
- // MAJOR TODO: make this work across redirects (if we can reset the InputStream).
- // OR, read the bits into a local buffer (yuck, the media could be large).
- private static class MediaPutRequestCreator implements HttpRequestCreator {
- private final InputStream mMediaInputStream;
- private final String mContentType;
- public MediaPutRequestCreator(InputStream mediaInputStream, String contentType) {
- mMediaInputStream = mediaInputStream;
- mContentType = contentType;
- }
-
- public HttpUriRequest createRequest(URI uri) {
- HttpPost post = new HttpPost(uri);
- post.addHeader(X_HTTP_METHOD_OVERRIDE, "PUT");
- // mMediaInputStream.reset();
- InputStreamEntity entity = new InputStreamEntity(mMediaInputStream,
- -1 /* read until EOF */);
- entity.setContentType(mContentType);
- post.setEntity(entity);
- return post;
- }
- }
-
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- */
- public AndroidGDataClient(Context context) {
- this(context, DEFAULT_USER_AGENT_APP_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- */
- public AndroidGDataClient(Context context, String appAndVersion) {
- this(context, appAndVersion, DEFAULT_GDATA_VERSION);
- }
-
- /**
- * Creates a new AndroidGDataClient.
- *
- * @param context The ContentResolver to get URL rewriting rules from
- * through the Android proxy server, using null to indicate not using proxy.
- * The context will also be used by GoogleHttpClient for configuration of
- * SSL session persistence.
- * @param appAndVersion The application name and version to be used as the basis of the
- * User-Agent. e.g., Android-GData/1.5.0.
- * @param gdataVersion The gdata service version that should be
- * used, e.g. "2.0"
- *
- */
- public AndroidGDataClient(Context context, String appAndVersion, String gdataVersion) {
- mHttpClient = new GoogleHttpClient(context, appAndVersion,
- true /* gzip capable */);
- mHttpClient.enableCurlLogging(TAG, Log.VERBOSE);
- mResolver = context.getContentResolver();
- mGDataVersion = gdataVersion;
- }
-
-
- public void close() {
- mHttpClient.close();
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#encodeUri(java.lang.String)
- */
- public String encodeUri(String uri) {
- String encodedUri;
- try {
- encodedUri = URLEncoder.encode(uri, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.e("JakartaGDataClient",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedUri = URLEncoder.encode(uri);
- }
- return encodedUri;
- }
-
- /*
- * (non-Javadoc)
- * @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
- */
- public QueryParams createQueryParams() {
- return new QueryParamsImpl();
- }
-
- // follows redirects
- private InputStream createAndExecuteMethod(HttpRequestCreator creator,
- String uriString,
- String authToken,
- String eTag,
- String protocolVersion)
- throws HttpException, IOException {
-
- HttpResponse response = null;
- int status = 500;
- int redirectsLeft = MAX_REDIRECTS;
-
- URI uri;
- try {
- uri = new URI(uriString);
- } catch (URISyntaxException use) {
- Log.w(TAG, "Unable to parse " + uriString + " as URI.", use);
- throw new IOException("Unable to parse " + uriString + " as URI: "
- + use.getMessage());
- }
-
- // we follow redirects ourselves, since we want to follow redirects even on POSTs, which
- // the HTTP library does not do. following redirects ourselves also allows us to log
- // the redirects using our own logging.
- while (redirectsLeft > 0) {
-
- HttpUriRequest request = creator.createRequest(uri);
-
- if (!SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- AndroidHttpClient.modifyRequestToAcceptGzipResponse(request);
- }
-
- // only add the auth token if not null (to allow for GData feeds that do not require
- // authentication.)
- if (!TextUtils.isEmpty(authToken)) {
- request.addHeader("Authorization", "GoogleLogin auth=" + authToken);
- }
-
- // while by default we have a 2.0 in this variable, it is possible to construct
- // a client that has an empty version field, to work with 1.0 services.
- if (!TextUtils.isEmpty(mGDataVersion)) {
- request.addHeader("GDataVersion", mGDataVersion);
- }
-
- // if we have a passed down eTag value, we need to add several headers
- if (!TextUtils.isEmpty(eTag)) {
- String method = request.getMethod();
- Header overrideMethodHeader = request.getFirstHeader(X_HTTP_METHOD_OVERRIDE);
- if (overrideMethodHeader != null) {
- method = overrideMethodHeader.getValue();
- }
- if ("GET".equals(method)) {
- // add the none match header, if the resource is not changed
- // this request will result in a 304 now.
- request.addHeader("If-None-Match", eTag);
- } else if ("DELETE".equals(method)
- || "PUT".equals(method)) {
- // now we send an if-match, but only if the passed in eTag is a strong eTag
- // as this only makes sense for a strong eTag
- if (!eTag.startsWith("W/")) {
- request.addHeader("If-Match", eTag);
- }
- }
- }
-
- if (LOCAL_LOGV) {
- for (Header h : request.getAllHeaders()) {
- Log.v(TAG, h.getName() + ": " + h.getValue());
- }
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Executing " + request.getRequestLine().toString());
- }
-
- response = null;
-
- try {
- response = mHttpClient.execute(request);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to execute HTTP request." + ioe);
- throw ioe;
- }
-
- StatusLine statusLine = response.getStatusLine();
- if (statusLine == null) {
- Log.w(TAG, "StatusLine is null.");
- throw new NullPointerException("StatusLine is null -- should not happen.");
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, response.getStatusLine().toString());
- for (Header h : response.getAllHeaders()) {
- Log.d(TAG, h.getName() + ": " + h.getValue());
- }
- }
- status = statusLine.getStatusCode();
-
- HttpEntity entity = response.getEntity();
-
- if ((status >= 200) && (status < 300) && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- in = logInputStreamContents(in);
- }
- return in;
- }
-
- // TODO: handle 301, 307?
- // TODO: let the http client handle the redirects, if we can be sure we'll never get a
- // redirect on POST.
- if (status == 302) {
- // consume the content, so the connection can be closed.
- entity.consumeContent();
- Header location = response.getFirstHeader("Location");
- if (location == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Redirect requested but no Location "
- + "specified.");
- }
- break;
- }
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Following redirect to " + location.getValue());
- }
- try {
- uri = new URI(location.getValue());
- } catch (URISyntaxException use) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Unable to parse " + location.getValue() + " as URI.", use);
- throw new IOException("Unable to parse " + location.getValue()
- + " as URI.");
- }
- break;
- }
- --redirectsLeft;
- } else {
- break;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Received " + status + " status code.");
- }
- String errorMessage = null;
- HttpEntity entity = response.getEntity();
- try {
- if (response != null && entity != null) {
- InputStream in = AndroidHttpClient.getUngzippedContent(entity);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buf = new byte[8192];
- int bytesRead = -1;
- while ((bytesRead = in.read(buf)) != -1) {
- baos.write(buf, 0, bytesRead);
- }
- // TODO: use appropriate encoding, picked up from Content-Type.
- errorMessage = new String(baos.toByteArray());
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, errorMessage);
- }
- }
- } finally {
- if (entity != null) {
- entity.consumeContent();
- }
- }
- String exceptionMessage = "Received " + status + " status code";
- if (errorMessage != null) {
- exceptionMessage += (": " + errorMessage);
- }
- throw new HttpException(exceptionMessage, status, null /* InputStream */);
- }
-
- /*
- * (non-Javadoc)
- * @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
- */
- public InputStream getFeedAsStream(String feedUrl,
- String authToken,
- String eTag,
- String protocolVersion)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), feedUrl, authToken, eTag, protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access feed.");
- }
-
- /**
- * Log the contents of the input stream.
- * The original input stream is consumed, so the caller must use the
- * BufferedInputStream that is returned.
- * @param in InputStream
- * @return replacement input stream for caller to use
- * @throws IOException
- */
- private InputStream logInputStreamContents(InputStream in) throws IOException {
- if (in == null) {
- return in;
- }
- // bufferSize is the (arbitrary) maximum amount to log.
- // The original InputStream is wrapped in a
- // BufferedInputStream with a 16K buffer. This lets
- // us read up to 16K, write it to the log, and then
- // reset the stream so the the original client can
- // then read the data. The BufferedInputStream
- // provides the mark and reset support, even when
- // the original InputStream does not.
- final int bufferSize = 16384;
- BufferedInputStream bin = new BufferedInputStream(in, bufferSize);
- bin.mark(bufferSize);
- int wanted = bufferSize;
- int totalReceived = 0;
- byte buf[] = new byte[wanted];
- while (wanted > 0) {
- int got = bin.read(buf, totalReceived, wanted);
- if (got <= 0) break; // EOF
- wanted -= got;
- totalReceived += got;
- }
- Log.d(TAG, new String(buf, 0, totalReceived, "UTF-8"));
- bin.reset();
- return bin;
- }
-
- public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken, String eTag, String protocolVersion)
- throws HttpException, IOException {
-
- InputStream in = createAndExecuteMethod(new GetRequestCreator(), mediaEntryUrl, authToken, eTag, protocolVersion);
-
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to access media entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#createEntry
- */
- public InputStream createEntry(String feedUrl,
- String authToken,
- String protocolVersion,
- GDataSerializer entry)
- throws HttpException, IOException {
-
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_CREATE);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(null /* override */, entity),
- feedUrl,
- authToken,
- null,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to create entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#updateEntry
- */
- public InputStream updateEntry(String editUri,
- String authToken,
- String eTag,
- String protocolVersion,
- GDataSerializer entry)
- throws HttpException, IOException {
- HttpEntity entity = createEntityForEntry(entry, GDataSerializer.FORMAT_UPDATE);
- final String method = entry.getSupportsPartial() ? "PATCH" : "PUT";
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator(method, entity),
- editUri,
- authToken,
- eTag,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to update entry.");
- }
-
- /* (non-Javadoc)
- * @see GDataClient#deleteEntry
- */
- public void deleteEntry(String editUri, String authToken, String eTag)
- throws HttpException, IOException {
- if (StringUtils.isEmpty(editUri)) {
- throw new IllegalArgumentException(
- "you must specify an non-empty edit url");
- }
- InputStream in =
- createAndExecuteMethod(
- new PostRequestCreator("DELETE", null /* entity */),
- editUri,
- authToken,
- eTag,
- null /* protocolVersion, not required for a delete */);
- if (in == null) {
- throw new IOException("Unable to delete entry.");
- }
- try {
- in.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
-
- public InputStream updateMediaEntry(String editUri, String authToken, String eTag,
- String protocolVersion, InputStream mediaEntryInputStream, String contentType)
- throws HttpException, IOException {
- InputStream in = createAndExecuteMethod(
- new MediaPutRequestCreator(mediaEntryInputStream, contentType),
- editUri,
- authToken,
- eTag,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to write media entry.");
- }
-
- private HttpEntity createEntityForEntry(GDataSerializer entry, int format) throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- entry.serialize(baos, format);
- } catch (IOException ioe) {
- Log.e(TAG, "Unable to serialize entry.", ioe);
- throw ioe;
- } catch (ParseException pe) {
- Log.e(TAG, "Unable to serialize entry.", pe);
- throw new IOException("Unable to serialize entry: " + pe.getMessage());
- }
-
- byte[] entryBytes = baos.toByteArray();
-
- if (entryBytes != null && Log.isLoggable(TAG, Log.DEBUG)) {
- try {
- Log.d(TAG, "Serialized entry: " + new String(entryBytes, "UTF-8"));
- } catch (UnsupportedEncodingException uee) {
- // should not happen
- throw new IllegalStateException("UTF-8 should be supported!",
- uee);
- }
- }
-
- AbstractHttpEntity entity;
- if (SystemProperties.getBoolean(NO_GZIP_SYSTEM_PROPERTY, false)) {
- entity = new ByteArrayEntity(entryBytes);
- } else {
- entity = AndroidHttpClient.getCompressedEntity(entryBytes, mResolver);
- }
-
- entity.setContentType(entry.getContentType());
- return entity;
- }
-
- /**
- * Connects to a GData server (specified by the batchUrl) and submits a
- * batch for processing. The response from the server is returned as an
- * {@link InputStream}. The caller is responsible for calling
- * {@link InputStream#close()} on the returned {@link InputStream}.
- *
- * @param batchUrl The batch url to which the batch is submitted.
- * @param authToken the authentication token that should be used when
- * submitting the batch.
- * @param protocolVersion The version of the protocol that
- * should be used for this request.
- * @param batch The batch of entries to submit.
- * @throws IOException Thrown if an io error occurs while communicating with
- * the service.
- * @throws HttpException if the service returns an error response.
- */
- public InputStream submitBatch(String batchUrl,
- String authToken,
- String protocolVersion,
- GDataSerializer batch)
- throws HttpException, IOException
- {
- HttpEntity entity = createEntityForEntry(batch, GDataSerializer.FORMAT_BATCH);
- InputStream in = createAndExecuteMethod(
- new PostRequestCreator("POST", entity),
- batchUrl,
- authToken,
- null,
- protocolVersion);
- if (in != null) {
- return in;
- }
- throw new IOException("Unable to process batch request.");
- }
-}
diff --git a/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java b/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java
deleted file mode 100644
index f097706..0000000
--- a/core/java/com/google/android/gdata2/client/AndroidXmlParserFactory.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.google.android.gdata2.client;
-
-import com.google.wireless.gdata2.parser.xml.XmlParserFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.util.Xml;
-
-/**
- * XmlParserFactory for the Android platform.
- */
-public class AndroidXmlParserFactory implements XmlParserFactory {
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createParser
- */
- public XmlPullParser createParser() throws XmlPullParserException {
- return Xml.newPullParser();
- }
-
- /*
- * (non-javadoc)
- * @see XmlParserFactory#createSerializer
- */
- public XmlSerializer createSerializer() throws XmlPullParserException {
- return Xml.newSerializer();
- }
-}
diff --git a/core/java/com/google/android/gdata2/client/QueryParamsImpl.java b/core/java/com/google/android/gdata2/client/QueryParamsImpl.java
deleted file mode 100644
index a26f4ce..0000000
--- a/core/java/com/google/android/gdata2/client/QueryParamsImpl.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.google.android.gdata2.client;
-import com.google.wireless.gdata2.client.QueryParams;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Simple implementation of the QueryParams interface.
- */
-// TODO: deal with categories
-public class QueryParamsImpl extends QueryParams {
-
- private final Map<String,String> mParams = new HashMap<String,String>();
-
- /**
- * Creates a new empty QueryParamsImpl.
- */
- public QueryParamsImpl() {
- }
-
- @Override
- public void clear() {
- setEntryId(null);
- mParams.clear();
- }
-
- @Override
- public String generateQueryUrl(String feedUrl) {
-
- if (TextUtils.isEmpty(getEntryId()) &&
- mParams.isEmpty()) {
- // nothing to do
- return feedUrl;
- }
-
- // handle entry IDs
- if (!TextUtils.isEmpty(getEntryId())) {
- if (!mParams.isEmpty()) {
- throw new IllegalStateException("Cannot set both an entry ID "
- + "and other query paramters.");
- }
- return feedUrl + '/' + getEntryId();
- }
-
- // otherwise, append the querystring params.
- StringBuilder sb = new StringBuilder();
- sb.append(feedUrl);
- Set<String> params = mParams.keySet();
- boolean first = true;
- if (feedUrl.contains("?")) {
- first = false;
- } else {
- sb.append('?');
- }
- for (String param : params) {
- if (first) {
- first = false;
- } else {
- sb.append('&');
- }
- sb.append(param);
- sb.append('=');
- String value = mParams.get(param);
- String encodedValue = null;
-
- try {
- encodedValue = URLEncoder.encode(value, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- // should not happen.
- Log.w("QueryParamsImpl",
- "UTF-8 not supported -- should not happen. "
- + "Using default encoding.", uee);
- encodedValue = URLEncoder.encode(value);
- }
- sb.append(encodedValue);
- }
- return sb.toString();
- }
-
- @Override
- public String getParamValue(String param) {
- if (!(mParams.containsKey(param))) {
- return null;
- }
- return mParams.get(param);
- }
-
- @Override
- public void setParamValue(String param, String value) {
- mParams.put(param, value);
- }
-
-}
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index 2a1f23a..d4ac24a 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -424,7 +424,8 @@ public class PduPersister {
// faster.
if ("text/plain".equals(type) || "application/smil".equals(type)) {
String text = c.getString(PART_COLUMN_TEXT);
- byte [] blob = new EncodedStringValue(text).getTextString();
+ byte [] blob = new EncodedStringValue(text != null ? text : "")
+ .getTextString();
baos.write(blob, 0, blob.length);
} else {
@@ -858,7 +859,7 @@ public class PduPersister {
} else {
values.put(Mms.SUBJECT, "");
}
-
+
long messageSize = sendReq.getMessageSize();
if (messageSize > 0) {
values.put(Mms.MESSAGE_SIZE, messageSize);
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
deleted file mode 100644
index 8a1298f..0000000
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.net;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.http.AndroidHttpClient;
-import android.os.Build;
-import android.os.NetStat;
-import android.os.SystemClock;
-import android.provider.Checkin;
-import android.util.Config;
-import android.util.Log;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.ProtocolException;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.LayeredSocketFactory;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.scheme.SchemeRegistry;
-import org.apache.http.conn.scheme.SocketFactory;
-import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-/**
- * {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs
- * and otherwise tweak HTTP requests.
- */
-public class GoogleHttpClient implements HttpClient {
- private static final String TAG = "GoogleHttpClient";
- private static final boolean LOCAL_LOGV = Config.LOGV || false;
-
- /** Exception thrown when a request is blocked by the URL rules. */
- public static class BlockedRequestException extends IOException {
- private final UrlRules.Rule mRule;
- BlockedRequestException(UrlRules.Rule rule) {
- super("Blocked by rule: " + rule.mName);
- mRule = rule;
- }
- }
-
- private final AndroidHttpClient mClient;
- private final ContentResolver mResolver;
- private final String mAppName, mUserAgent;
- private final ThreadLocal<Boolean> mConnectionAllocated = new ThreadLocal<Boolean>();
-
- /**
- * Create an HTTP client without SSL session persistence.
- * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
- */
- public GoogleHttpClient(ContentResolver resolver, String userAgent) {
- mClient = AndroidHttpClient.newInstance(userAgent);
- mResolver = resolver;
- mUserAgent = mAppName = userAgent;
- }
-
- /**
- * Create an HTTP client without SSL session persistence.
- * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
- */
- public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
- boolean gzipCapable) {
- this(resolver, null /* cache */, appAndVersion, gzipCapable);
- }
-
- /**
- * Create an HTTP client. Normaly this client is shared throughout an app.
- * The HTTP client will construct its User-Agent as follows:
- *
- * <appAndVersion> (<build device> <build id>)
- * or
- * <appAndVersion> (<build device> <build id>); gzip
- * (if gzip capable)
- *
- * The context has settings for URL rewriting rules and is used to enable
- * SSL session persistence.
- *
- * @param context application context.
- * @param appAndVersion Base app and version to use in the User-Agent.
- * e.g., "MyApp/1.0"
- * @param gzipCapable Whether or not this client is able to consume gzip'd
- * responses. Only used to modify the User-Agent, not other request
- * headers. Needed because Google servers require gzip in the User-Agent
- * in order to return gzip'd content.
- */
- public GoogleHttpClient(Context context, String appAndVersion, boolean gzipCapable) {
- this(context.getContentResolver(),
- SSLClientSessionCacheFactory.getCache(context),
- appAndVersion, gzipCapable);
- }
-
- private GoogleHttpClient(ContentResolver resolver,
- SSLClientSessionCache cache,
- String appAndVersion, boolean gzipCapable) {
- String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")";
- if (gzipCapable) {
- userAgent = userAgent + "; gzip";
- }
-
- mClient = AndroidHttpClient.newInstance(userAgent, cache);
- mResolver = resolver;
- mAppName = appAndVersion;
- mUserAgent = userAgent;
-
- // Wrap all the socket factories with the appropriate wrapper. (Apache
- // HTTP, curse its black and stupid heart, inspects the SocketFactory to
- // see if it's a LayeredSocketFactory, so we need two wrapper classes.)
- SchemeRegistry registry = getConnectionManager().getSchemeRegistry();
- for (String name : registry.getSchemeNames()) {
- Scheme scheme = registry.unregister(name);
- SocketFactory sf = scheme.getSocketFactory();
- if (sf instanceof LayeredSocketFactory) {
- sf = new WrappedLayeredSocketFactory((LayeredSocketFactory) sf);
- } else {
- sf = new WrappedSocketFactory(sf);
- }
- registry.register(new Scheme(name, sf, scheme.getDefaultPort()));
- }
- }
-
- /**
- * Delegating wrapper for SocketFactory records when sockets are connected.
- * We use this to know whether a connection was created vs reused, to
- * gather per-app statistics about connection reuse rates.
- * (Note, we record only *connection*, not *creation* of sockets --
- * what we care about is the network overhead of an actual TCP connect.)
- */
- private class WrappedSocketFactory implements SocketFactory {
- private SocketFactory mDelegate;
- private WrappedSocketFactory(SocketFactory delegate) { mDelegate = delegate; }
- public final Socket createSocket() throws IOException { return mDelegate.createSocket(); }
- public final boolean isSecure(Socket s) { return mDelegate.isSecure(s); }
-
- public final Socket connectSocket(
- Socket s, String h, int p,
- InetAddress la, int lp, HttpParams params) throws IOException {
- mConnectionAllocated.set(Boolean.TRUE);
- return mDelegate.connectSocket(s, h, p, la, lp, params);
- }
- }
-
- /** Like WrappedSocketFactory, but for the LayeredSocketFactory subclass. */
- private class WrappedLayeredSocketFactory
- extends WrappedSocketFactory implements LayeredSocketFactory {
- private LayeredSocketFactory mDelegate;
- private WrappedLayeredSocketFactory(LayeredSocketFactory sf) { super(sf); mDelegate = sf; }
-
- public final Socket createSocket(Socket s, String host, int port, boolean autoClose)
- throws IOException {
- return mDelegate.createSocket(s, host, port, autoClose);
- }
- }
-
- /**
- * Release resources associated with this client. You must call this,
- * or significant resources (sockets and memory) may be leaked.
- */
- public void close() {
- mClient.close();
- }
-
- /** Execute a request without applying and rewrite rules. */
- public HttpResponse executeWithoutRewriting(
- HttpUriRequest request, HttpContext context)
- throws IOException {
- int code = -1;
- long start = SystemClock.elapsedRealtime();
- try {
- HttpResponse response;
- mConnectionAllocated.set(null);
-
- if (NetworkStatsEntity.shouldLogNetworkStats()) {
- // TODO: if we're logging network stats, and if the apache library is configured
- // to follow redirects, count each redirect as an additional round trip.
-
- int uid = android.os.Process.myUid();
- long startTx = NetStat.getUidTxBytes(uid);
- long startRx = NetStat.getUidRxBytes(uid);
-
- response = mClient.execute(request, context);
- HttpEntity origEntity = response == null ? null : response.getEntity();
- if (origEntity != null) {
- // yeah, we compute the same thing below. we do need to compute this here
- // so we can wrap the HttpEntity in the response.
- long now = SystemClock.elapsedRealtime();
- long elapsed = now - start;
- NetworkStatsEntity entity = new NetworkStatsEntity(origEntity,
- mAppName, uid, startTx, startRx,
- elapsed /* response latency */, now /* processing start time */);
- response.setEntity(entity);
- }
- } else {
- response = mClient.execute(request, context);
- }
-
- code = response.getStatusLine().getStatusCode();
- return response;
- } finally {
- // Record some statistics to the checkin service about the outcome.
- // Note that this is only describing execute(), not body download.
- // We assume the database writes are much faster than network I/O,
- // and not worth running in a background thread or anything.
- try {
- long elapsed = SystemClock.elapsedRealtime() - start;
- ContentValues values = new ContentValues();
- values.put(Checkin.Stats.COUNT, 1);
- values.put(Checkin.Stats.SUM, elapsed / 1000.0);
-
- values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REQUEST + ":" + mAppName);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
-
- // No sockets and no exceptions means we successfully reused a connection
- if (mConnectionAllocated.get() == null && code >= 0) {
- values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REUSED + ":" + mAppName);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
- }
-
- String status = code < 0 ? "IOException" : Integer.toString(code);
- values.put(Checkin.Stats.TAG,
- Checkin.Stats.Tag.HTTP_STATUS + ":" + mAppName + ":" + status);
- mResolver.insert(Checkin.Stats.CONTENT_URI, values);
- } catch (Exception e) {
- Log.e(TAG, "Error recording stats", e);
- }
- }
- }
-
- public String rewriteURI(String original) {
- UrlRules rules = UrlRules.getRules(mResolver);
- UrlRules.Rule rule = rules.matchRule(original);
- return rule.apply(original);
- }
-
- public HttpResponse execute(HttpUriRequest request, HttpContext context)
- throws IOException {
- // Rewrite the supplied URL...
- URI uri = request.getURI();
- String original = uri.toString();
- UrlRules rules = UrlRules.getRules(mResolver);
- UrlRules.Rule rule = rules.matchRule(original);
- String rewritten = rule.apply(original);
-
- if (rewritten == null) {
- Log.w(TAG, "Blocked by " + rule.mName + ": " + original);
- throw new BlockedRequestException(rule);
- } else if (rewritten == original) {
- return executeWithoutRewriting(request, context); // Pass through
- }
-
- try {
- uri = new URI(rewritten);
- } catch (URISyntaxException e) {
- throw new RuntimeException("Bad URL from rule: " + rule.mName, e);
- }
-
- // Wrap request so we can replace the URI.
- RequestWrapper wrapper = wrapRequest(request);
- wrapper.setURI(uri);
- request = wrapper;
-
- if (LOCAL_LOGV) Log.v(TAG, "Rule " + rule.mName + ": " + original + " -> " + rewritten);
- return executeWithoutRewriting(request, context);
- }
-
- /**
- * Wraps the request making it mutable.
- */
- private static RequestWrapper wrapRequest(HttpUriRequest request)
- throws IOException {
- try {
- // We have to wrap it with the right type. Some code performs
- // instanceof checks.
- RequestWrapper wrapped;
- if (request instanceof HttpEntityEnclosingRequest) {
- wrapped = new EntityEnclosingRequestWrapper(
- (HttpEntityEnclosingRequest) request);
- } else {
- wrapped = new RequestWrapper(request);
- }
-
- // Copy the headers from the original request into the wrapper.
- wrapped.resetHeaders();
-
- return wrapped;
- } catch (ProtocolException e) {
- throw new ClientProtocolException(e);
- }
- }
-
- /**
- * Mark a user agent as one Google will trust to handle gzipped content.
- * {@link AndroidHttpClient#modifyRequestToAcceptGzipResponse} is (also)
- * necessary but not sufficient -- many browsers claim to accept gzip but
- * have broken handling, so Google checks the user agent as well.
- *
- * @param originalUserAgent to modify (however you identify yourself)
- * @return user agent with a "yes, I really can handle gzip" token added.
- * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)}
- */
- public static String getGzipCapableUserAgent(String originalUserAgent) {
- return originalUserAgent + "; gzip";
- }
-
- // HttpClient wrapper methods.
-
- public HttpParams getParams() {
- return mClient.getParams();
- }
-
- public ClientConnectionManager getConnectionManager() {
- return mClient.getConnectionManager();
- }
-
- public HttpResponse execute(HttpUriRequest request) throws IOException {
- return execute(request, (HttpContext) null);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request)
- throws IOException {
- return mClient.execute(target, request);
- }
-
- public HttpResponse execute(HttpHost target, HttpRequest request,
- HttpContext context) throws IOException {
- return mClient.execute(target, request, context);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler)
- throws IOException, ClientProtocolException {
- return mClient.execute(request, responseHandler);
- }
-
- public <T> T execute(HttpUriRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return mClient.execute(request, responseHandler, context);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler) throws IOException,
- ClientProtocolException {
- return mClient.execute(target, request, responseHandler);
- }
-
- public <T> T execute(HttpHost target, HttpRequest request,
- ResponseHandler<? extends T> responseHandler, HttpContext context)
- throws IOException, ClientProtocolException {
- return mClient.execute(target, request, responseHandler, context);
- }
-
- /**
- * Enables cURL request logging for this client.
- *
- * @param name to log messages with
- * @param level at which to log messages (see {@link android.util.Log})
- */
- public void enableCurlLogging(String name, int level) {
- mClient.enableCurlLogging(name, level);
- }
-
- /**
- * Disables cURL logging for this client.
- */
- public void disableCurlLogging() {
- mClient.disableCurlLogging();
- }
-}
diff --git a/core/java/com/google/android/net/NetworkStatsEntity.java b/core/java/com/google/android/net/NetworkStatsEntity.java
deleted file mode 100644
index f5d2349..0000000
--- a/core/java/com/google/android/net/NetworkStatsEntity.java
+++ /dev/null
@@ -1,85 +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 com.google.android.net;
-
-import android.os.NetStat;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.util.EventLog;
-
-import org.apache.http.HttpEntity;
-import org.apache.http.entity.HttpEntityWrapper;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-
-public class NetworkStatsEntity extends HttpEntityWrapper {
-
- private static final int HTTP_STATS_EVENT = 52001;
-
- private class NetworkStatsInputStream extends FilterInputStream {
-
- public NetworkStatsInputStream(InputStream wrapped) {
- super(wrapped);
- }
-
- @Override
- public void close() throws IOException {
- try {
- super.close();
- } finally {
- long processingTime = SystemClock.elapsedRealtime() - mProcessingStartTime;
- long tx = NetStat.getUidTxBytes(mUid);
- long rx = NetStat.getUidRxBytes(mUid);
-
- EventLog.writeEvent(HTTP_STATS_EVENT, mUa, mResponseLatency, processingTime,
- tx - mStartTx, rx - mStartRx);
- }
- }
- }
-
- private final String mUa;
- private final int mUid;
- private final long mStartTx;
- private final long mStartRx;
- private final long mResponseLatency;
- private final long mProcessingStartTime;
-
- public NetworkStatsEntity(HttpEntity orig, String ua,
- int uid, long startTx, long startRx, long responseLatency,
- long processingStartTime) {
- super(orig);
- this.mUa = ua;
- this.mUid = uid;
- this.mStartTx = startTx;
- this.mStartRx = startRx;
- this.mResponseLatency = responseLatency;
- this.mProcessingStartTime = processingStartTime;
- }
-
- public static boolean shouldLogNetworkStats() {
- return "1".equals(SystemProperties.get("googlehttpclient.logstats"));
- }
-
- @Override
- public InputStream getContent() throws IOException {
- InputStream orig = super.getContent();
- return new NetworkStatsInputStream(orig);
- }
-}
diff --git a/core/java/com/google/android/net/ParentalControl.java b/core/java/com/google/android/net/ParentalControl.java
deleted file mode 100644
index 71a3958..0000000
--- a/core/java/com/google/android/net/ParentalControl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.net;
-
-import android.os.ICheckinService;
-import android.os.IParentalControlCallback;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-
-public class ParentalControl {
- /**
- * Strings to identify your app. To enable parental control checking for
- * new apps, please add it here, and configure GServices accordingly.
- */
- public static final String VENDING = "vending";
- public static final String YOUTUBE = "youtube";
-
- /**
- * This interface is supplied to getParentalControlState and is callback upon with
- * the state of parental control.
- */
- public interface Callback {
- /**
- * This method will be called when the state of parental control is known. If state is
- * null, then the state of parental control is unknown.
- * @param state The state of parental control.
- */
- void onResult(ParentalControlState state);
- }
-
- private static class RemoteCallback extends IParentalControlCallback.Stub {
- private Callback mCallback;
-
- public RemoteCallback(Callback callback) {
- mCallback = callback;
- }
-
- public void onResult(ParentalControlState state) {
- if (mCallback != null) {
- mCallback.onResult(state);
- }
- }
- };
-
- public static void getParentalControlState(Callback callback,
- String requestingApp) {
- ICheckinService service =
- ICheckinService.Stub.asInterface(ServiceManager.getService("checkin"));
-
- RemoteCallback remoteCallback = new RemoteCallback(callback);
- try {
- service.getParentalControlState(remoteCallback, requestingApp);
- } catch (RemoteException e) {
- // This should never happen.
- Log.e("ParentalControl", "Failed to talk to the checkin service.");
- }
- }
-}
diff --git a/core/java/com/google/android/net/ParentalControlState.java b/core/java/com/google/android/net/ParentalControlState.java
deleted file mode 100644
index 162a1f6..0000000
--- a/core/java/com/google/android/net/ParentalControlState.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.net;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class ParentalControlState implements Parcelable {
- public boolean isEnabled;
- public String redirectUrl;
-
- /**
- * Used to read a ParentalControlStatus from a Parcel.
- */
- public static final Parcelable.Creator<ParentalControlState> CREATOR =
- new Parcelable.Creator<ParentalControlState>() {
- public ParentalControlState createFromParcel(Parcel source) {
- ParentalControlState status = new ParentalControlState();
- status.isEnabled = (source.readInt() == 1);
- status.redirectUrl = source.readString();
- return status;
- }
-
- public ParentalControlState[] newArray(int size) {
- return new ParentalControlState[size];
- }
- };
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(isEnabled ? 1 : 0);
- dest.writeString(redirectUrl);
- }
-
- @Override
- public String toString() {
- return isEnabled + ", " + redirectUrl;
- }
-};
diff --git a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java b/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
deleted file mode 100644
index 6570a9bd..0000000
--- a/core/java/com/google/android/net/SSLClientSessionCacheFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.google.android.net;
-
-import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
-import android.content.Context;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-
-import com.android.internal.net.DbSSLSessionCache;
-
-/**
- * Factory that returns the appropriate implementation of a {@link SSLClientSessionCache} based
- * on gservices.
- *
- * @hide
- */
-// TODO: return a proxied implementation that is updated as the gservices value changes.
-public final class SSLClientSessionCacheFactory {
-
- private static final String TAG = "SSLClientSessionCacheFactory";
-
- public static final String DB = "db";
- public static final String FILE = "file";
-
- // utility class
- private SSLClientSessionCacheFactory() {}
-
- /**
- * Returns a new {@link SSLClientSessionCache} based on the persistent cache that's specified,
- * if any, in gservices. If no cache is specified, returns null.
- * @param context The application context used for the per-process persistent cache.
- * @return A new {@link SSLClientSessionCache}, or null if no persistent cache is configured.
- */
- public static SSLClientSessionCache getCache(Context context) {
- String type = Settings.Gservices.getString(context.getContentResolver(),
- Settings.Gservices.SSL_SESSION_CACHE);
-
- if (type != null) {
- if (DB.equals(type)) {
- return DbSSLSessionCache.getInstanceForPackage(context);
- } else if (FILE.equals(type)) {
- File dir = context.getFilesDir();
- File cacheDir = new File(dir, "sslcache");
- if (!cacheDir.exists()) {
- cacheDir.mkdir();
- }
- try {
- return FileClientSessionCache.usingDirectory(cacheDir);
- } catch (IOException ioe) {
- Log.w(TAG, "Unable to create FileClientSessionCache in " + cacheDir.getName(), ioe);
- return null;
- }
- } else {
- Log.w(TAG, "Ignoring unrecognized type: '" + type + "'");
- }
- }
- return null;
- }
-}
diff --git a/core/java/com/google/android/net/UrlRules.java b/core/java/com/google/android/net/UrlRules.java
deleted file mode 100644
index 54d139d..0000000
--- a/core/java/com/google/android/net/UrlRules.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.net;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.provider.Checkin;
-import android.provider.Settings;
-import android.util.Config;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A set of rules rewriting and blocking URLs. Used to offer a point of
- * control for redirecting HTTP requests, often to the Android proxy server.
- *
- * <p>Each rule has the following format:
- *
- * <pre><em>url-prefix</em> [REWRITE <em>new-prefix</em>] [BLOCK]</pre>
- *
- * <p>Any URL which starts with <em>url-prefix</em> will trigger the rule.
- * If BLOCK is specified, requests to that URL will be blocked and fail.
- * If REWRITE is specified, the matching prefix will be removed and replaced
- * with <em>new-prefix</em>. (If both are specified, BLOCK wins.) Case is
- * insensitive for the REWRITE and BLOCK keywords, but sensitive for URLs.
- *
- * <p>In Gservices, the value of any key that starts with "url:" will be
- * interpreted as a rule. The full name of the key is unimportant (but can
- * be used to document the intent of the rule, and must be unique).
- * Example gservices keys:
- *
- * <pre>
- * url:use_proxy_for_calendar = "http://www.google.com/calendar/ REWRITE http://android.clients.google.com/proxy/calendar/"
- * url:stop_crash_reports = "http://android.clients.google.com/crash/ BLOCK"
- * url:use_ssl_for_contacts = "http://www.google.com/m8/ REWRITE https://www.google.com/m8/"
- * </pre>
- */
-public class UrlRules {
- public static final String TAG = "UrlRules";
- public static final boolean LOCAL_LOGV = Config.LOGV || false;
-
- /** Thrown when the rewrite rules can't be parsed. */
- public static class RuleFormatException extends Exception {
- public RuleFormatException(String msg) { super(msg); }
- }
-
- /** A single rule specifying actions for URLs matching a certain prefix. */
- public static class Rule implements Comparable {
- /** Name assigned to the rule (for logging and debugging). */
- public final String mName;
-
- /** Prefix required to match this rule. */
- public final String mPrefix;
-
- /** Text to replace mPrefix with (null to leave alone). */
- public final String mRewrite;
-
- /** True if matching URLs should be blocked. */
- public final boolean mBlock;
-
- /** Default rule that does nothing. */
- public static final Rule DEFAULT = new Rule();
-
- /** Parse a rewrite rule as given in a Gservices value. */
- public Rule(String name, String rule) throws RuleFormatException {
- mName = name;
- String[] words = PATTERN_SPACE_PLUS.split(rule);
- if (words.length == 0) throw new RuleFormatException("Empty rule");
-
- mPrefix = words[0];
- String rewrite = null;
- boolean block = false;
- for (int pos = 1; pos < words.length; ) {
- String word = words[pos].toLowerCase();
- if (word.equals("rewrite") && pos + 1 < words.length) {
- rewrite = words[pos + 1];
- pos += 2;
- } else if (word.equals("block")) {
- block = true;
- pos += 1;
- } else {
- throw new RuleFormatException("Illegal rule: " + rule);
- }
- // TODO: Parse timeout specifications, etc.
- }
-
- mRewrite = rewrite;
- mBlock = block;
- }
-
- /** Create the default Rule. */
- private Rule() {
- mName = "DEFAULT";
- mPrefix = "";
- mRewrite = null;
- mBlock = false;
- }
-
- /**
- * Apply the rule to a particular URL (assumed to match the rule).
- * @param url to rewrite or modify.
- * @return modified URL, or null if the URL is blocked.
- */
- public String apply(String url) {
- if (mBlock) {
- return null;
- } else if (mRewrite != null) {
- return mRewrite + url.substring(mPrefix.length());
- } else {
- return url;
- }
- }
-
- /** More generic rules are greater than more specific rules. */
- public int compareTo(Object o) {
- return ((Rule) o).mPrefix.compareTo(mPrefix);
- }
- }
-
- /** Cached rule set from Gservices. */
- private static UrlRules sCachedRules = new UrlRules(new Rule[] {});
-
- private static final Pattern PATTERN_SPACE_PLUS = Pattern.compile(" +");
- private static final Pattern RULE_PATTERN = Pattern.compile("\\W");
-
- /** Gservices digest when sCachedRules was cached. */
- private static String sCachedDigest = null;
-
- /** Currently active set of Rules. */
- private final Rule[] mRules;
-
- /** Regular expression with one capturing group for each Rule. */
- private final Pattern mPattern;
-
- /**
- * Create a rewriter from an array of Rules. Normally used only for
- * testing. Instead, use {@link #getRules} to get rules from Gservices.
- * @param rules to use.
- */
- public UrlRules(Rule[] rules) {
- // Sort the rules to put the most specific rules first.
- Arrays.sort(rules);
-
- // Construct a regular expression, escaping all the prefix strings.
- StringBuilder pattern = new StringBuilder("(");
- for (int i = 0; i < rules.length; ++i) {
- if (i > 0) pattern.append(")|(");
- pattern.append(RULE_PATTERN.matcher(rules[i].mPrefix).replaceAll("\\\\$0"));
- }
- mPattern = Pattern.compile(pattern.append(")").toString());
- mRules = rules;
- }
-
- /**
- * Match a string against every Rule and find one that matches.
- * @param uri to match against the Rules in the rewriter.
- * @return the most specific matching Rule, or Rule.DEFAULT if none match.
- */
- public Rule matchRule(String url) {
- Matcher matcher = mPattern.matcher(url);
- if (matcher.lookingAt()) {
- for (int i = 0; i < mRules.length; ++i) {
- if (matcher.group(i + 1) != null) {
- return mRules[i]; // Rules are sorted most specific first.
- }
- }
- }
- return Rule.DEFAULT;
- }
-
- /**
- * Get the (possibly cached) UrlRules based on the rules in Gservices.
- * @param resolver to use for accessing the Gservices database.
- * @return an updated UrlRules instance
- */
- public static synchronized UrlRules getRules(ContentResolver resolver) {
- String digest = Settings.Gservices.getString(resolver,
- Settings.Gservices.PROVISIONING_DIGEST);
- if (sCachedDigest != null && sCachedDigest.equals(digest)) {
- // The digest is the same, so the rules are the same.
- if (LOCAL_LOGV) Log.v(TAG, "Using cached rules for digest: " + digest);
- return sCachedRules;
- }
-
- if (LOCAL_LOGV) Log.v(TAG, "Scanning for Gservices \"url:*\" rules");
- Cursor cursor = resolver.query(Settings.Gservices.CONTENT_URI,
- new String[] {
- Settings.Gservices.NAME,
- Settings.Gservices.VALUE
- },
- Settings.Gservices.NAME + " like \"url:%\"", null,
- Settings.Gservices.NAME);
- try {
- ArrayList<Rule> rules = new ArrayList<Rule>();
- while (cursor.moveToNext()) {
- try {
- String name = cursor.getString(0).substring(4); // "url:X"
- String value = cursor.getString(1);
- if (value == null || value.length() == 0) continue;
- if (LOCAL_LOGV) Log.v(TAG, " Rule " + name + ": " + value);
- rules.add(new Rule(name, value));
- } catch (RuleFormatException e) {
- // Oops, Gservices has an invalid rule! Skip it.
- Log.e(TAG, "Invalid rule from Gservices", e);
- Checkin.logEvent(resolver,
- Checkin.Events.Tag.GSERVICES_ERROR, e.toString());
- }
- }
- sCachedRules = new UrlRules(rules.toArray(new Rule[rules.size()]));
- sCachedDigest = digest;
- if (LOCAL_LOGV) Log.v(TAG, "New rules stored for digest: " + digest);
- } finally {
- cursor.close();
- }
-
- return sCachedRules;
- }
-}
diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java
index 25f6b33..1871682 100644
--- a/core/java/com/google/android/util/AbstractMessageParser.java
+++ b/core/java/com/google/android/util/AbstractMessageParser.java
@@ -1,5 +1,18 @@
-// Copyright 2007 The Android Open Source Project
-// All Rights Reserved.
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.google.android.util;
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
deleted file mode 100644
index 8291e29..0000000
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.http.SslError;
-import android.os.Message;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.HttpAuthHandler;
-import android.webkit.SslErrorHandler;
-import android.webkit.WebSettings;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-import android.widget.TextView;
-
-import java.util.Locale;
-
-/**
- * Helper to display Google web content, and fallback on a static message if the
- * web content is unreachable. For example, this can be used to display
- * "Legal terms".
- * <p>
- * The typical usage pattern is to have two Gservices settings defined:
- * <ul>
- * <li>A secure URL that will be displayed on the device. This should be HTTPS
- * so hotspots won't intercept it giving us a false positive that the page
- * loaded successfully.
- * <li>A pretty human-readable URL that will be displayed to the user in case we
- * cannot reach the above URL.
- * </ul>
- * <p>
- * The typical call sequence is {@link #setUrlsFromGservices(String, String)},
- * {@link #setUnsuccessfulMessage(String)}, and {@link #loadUrl()}. At some
- * point, you'll want to display the layout via {@link #getLayout()}.
- */
-public class GoogleWebContentHelper {
-
- private Context mContext;
-
- private String mSecureUrl;
- private String mPrettyUrl;
-
- private String mUnsuccessfulMessage;
-
- private ViewGroup mLayout;
- private WebView mWebView;
- private View mProgressBar;
- private TextView mTextView;
-
- private boolean mReceivedResponse;
-
- public GoogleWebContentHelper(Context context) {
- mContext = context;
- }
-
- /**
- * Fetches the URLs from Gservices.
- *
- * @param secureSetting The setting key whose value contains the HTTPS URL.
- * @param prettySetting The setting key whose value contains the pretty URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUrlsFromGservices(String secureSetting, String prettySetting) {
- ContentResolver contentResolver = mContext.getContentResolver();
- mSecureUrl = fillUrl(Settings.Gservices.getString(contentResolver, secureSetting),
- mContext);
- mPrettyUrl = fillUrl(Settings.Gservices.getString(contentResolver, prettySetting),
- mContext);
- return this;
- }
-
- /**
- * Fetch directly from provided urls.
- *
- * @param secureUrl The HTTPS URL.
- * @param prettyUrl The pretty URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUrls(String secureUrl, String prettyUrl) {
- mSecureUrl = fillUrl(secureUrl, mContext);
- mPrettyUrl = fillUrl(prettyUrl, mContext);
- return this;
- }
-
-
- /**
- * Sets the message that will be shown if we are unable to load the page.
- * <p>
- * This should be called after {@link #setUrlsFromGservices(String, String)}
- * .
- *
- * @param message The message to load. The first argument, according to
- * {@link java.util.Formatter}, will be substituted with the pretty
- * URL.
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper setUnsuccessfulMessage(String message) {
- Locale locale = mContext.getResources().getConfiguration().locale;
- mUnsuccessfulMessage = String.format(locale, message, mPrettyUrl);
- return this;
- }
-
- /**
- * Begins loading the secure URL.
- *
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper loadUrl() {
- ensureViews();
- mWebView.loadUrl(mSecureUrl);
- return this;
- }
-
- /**
- * Loads data into the webview and also provides a failback url
- * @return This {@link GoogleWebContentHelper} so methods can be chained.
- */
- public GoogleWebContentHelper loadDataWithFailUrl(String base, String data,
- String mimeType, String encoding, String failUrl) {
- ensureViews();
- mWebView.loadDataWithBaseURL(base, data, mimeType, encoding, failUrl);
- return this;
- }
-
- /**
- * Helper to handle the back key. Returns true if the back key was handled,
- * otherwise returns false.
- * @param event the key event sent to {@link Activity#dispatchKeyEvent()}
- */
- public boolean handleKey(KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
- && event.getAction() == KeyEvent.ACTION_DOWN) {
- if (mWebView.canGoBack()) {
- mWebView.goBack();
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the layout containing the web view, progress bar, and text view.
- * This class takes care of setting each one's visibility based on current
- * state.
- *
- * @return The layout you should display.
- */
- public ViewGroup getLayout() {
- ensureViews();
- return mLayout;
- }
-
- private synchronized void ensureViews() {
- if (mLayout == null) {
- initializeViews();
- }
- }
-
- /**
- * Fills the URL with the locale.
- *
- * @param url The URL in Formatter style for the extra info to be filled in.
- * @return The filled URL.
- */
- private static String fillUrl(String url, Context context) {
-
- if (TextUtils.isEmpty(url)) {
- return "";
- }
-
- /* We add another layer of indirection here to allow mcc's to fill
- * in Locales for TOS. TODO - REMOVE when needed locales supported
- * natively (when not shipping devices to country X without support
- * for their locale).
- */
- String localeReplacement = context.
- getString(com.android.internal.R.string.locale_replacement);
- if (localeReplacement != null && localeReplacement.length() != 0) {
- url = String.format(url, localeReplacement);
- }
-
- Locale locale = Locale.getDefault();
- String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase();
- return String.format(url, tmp);
- }
-
- private void initializeViews() {
-
- LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- mLayout = (ViewGroup) inflater.inflate(
- com.android.internal.R.layout.google_web_content_helper_layout, null);
-
- mWebView = (WebView) mLayout.findViewById(com.android.internal.R.id.web);
- mWebView.setWebViewClient(new MyWebViewClient());
- WebSettings settings = mWebView.getSettings();
- settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
-
- mProgressBar = mLayout.findViewById(com.android.internal.R.id.progressContainer);
- TextView message = (TextView) mProgressBar.findViewById(com.android.internal.R.id.message);
- message.setText(com.android.internal.R.string.googlewebcontenthelper_loading);
-
- mTextView = (TextView) mLayout.findViewById(com.android.internal.R.id.text);
- mTextView.setText(mUnsuccessfulMessage);
- }
-
- private synchronized void handleWebViewCompletion(boolean success) {
-
- if (mReceivedResponse) {
- return;
- } else {
- mReceivedResponse = true;
- }
-
- // In both cases, remove the progress bar
- ((ViewGroup) mProgressBar.getParent()).removeView(mProgressBar);
-
- // Remove the view that isn't relevant
- View goneView = success ? mTextView : mWebView;
- ((ViewGroup) goneView.getParent()).removeView(goneView);
-
- // Show the next view, which depends on success
- View visibleView = success ? mWebView : mTextView;
- visibleView.setVisibility(View.VISIBLE);
- }
-
- private class MyWebViewClient extends WebViewClient {
-
- @Override
- public void onPageFinished(WebView view, String url) {
- handleWebViewCompletion(true);
- }
-
- @Override
- public void onReceivedError(WebView view, int errorCode,
- String description, String failingUrl) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onReceivedHttpAuthRequest(WebView view,
- HttpAuthHandler handler, String host, String realm) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onReceivedSslError(WebView view, SslErrorHandler handler,
- SslError error) {
- handleWebViewCompletion(false);
- }
-
- @Override
- public void onTooManyRedirects(WebView view, Message cancelMsg,
- Message continueMsg) {
- handleWebViewCompletion(false);
- }
-
- }
-
-}
diff --git a/core/java/com/google/android/util/SimplePullParser.java b/core/java/com/google/android/util/SimplePullParser.java
deleted file mode 100644
index 031790b..0000000
--- a/core/java/com/google/android/util/SimplePullParser.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.util;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.io.Reader;
-import java.io.Closeable;
-
-import android.util.Xml;
-import android.util.Log;
-
-/**
- * This is an abstraction of a pull parser that provides several benefits:<ul>
- * <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which
- * might have children)</li>
- * <li>it makes the handling of text (cdata) blocks more convenient</li>
- * <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception
- * if it is missing) or an optional attribute (and using a default value if it is missing)
- * </ul>
- */
-public class SimplePullParser {
- public static final String TEXT_TAG = "![CDATA[";
-
- private String mLogTag = null;
- private final XmlPullParser mParser;
- private Closeable source;
- private String mCurrentStartTag;
-
- /**
- * Constructs a new SimplePullParser to parse the stream
- * @param stream stream to parse
- * @param encoding the encoding to use
- */
- public SimplePullParser(InputStream stream, String encoding)
- throws ParseException, IOException {
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(stream, encoding);
- moveToStartDocument(parser);
- mParser = parser;
- mCurrentStartTag = null;
- source = stream;
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param parser the underlying parser to use
- */
- public SimplePullParser(XmlPullParser parser) {
- mParser = parser;
- mCurrentStartTag = null;
- source = null;
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param xml the xml to parse
- */
- public SimplePullParser(String xml) throws IOException, ParseException {
- this(new StringReader(xml));
- }
-
- /**
- * Constructs a new SimplePullParser to parse the xml
- * @param reader a reader containing the xml
- */
- public SimplePullParser(Reader reader) throws IOException, ParseException {
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(reader);
- moveToStartDocument(parser);
- mParser = parser;
- mCurrentStartTag = null;
- source = reader;
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- }
-
- private static void moveToStartDocument(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int eventType;
- eventType = parser.getEventType();
- if (eventType != XmlPullParser.START_DOCUMENT) {
- throw new XmlPullParserException("Not at start of response");
- }
- }
-
- /**
- * Enables logging to the provided log tag. A basic representation of the xml will be logged as
- * the xml is parsed. No logging is done unless this is called.
- *
- * @param logTag the log tag to use when logging
- */
- public void setLogTag(String logTag) {
- mLogTag = logTag;
- }
-
- /**
- * Returns the tag of the next element whose depth is parentDepth plus one
- * or null if there are no more such elements before the next start tag. When this returns,
- * getDepth() and all methods relating to attributes will refer to the element whose tag is
- * returned.
- *
- * @param parentDepth the depth of the parrent of the item to be returned
- * @param textBuilder if null then text blocks will be ignored. If
- * non-null then text blocks will be added to the builder and TEXT_TAG
- * will be returned when one is found
- * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null
- * if there are no more child elements or DATA blocks
- * @throws IOException propogated from the underlying parser
- * @throws ParseException if there was an error parsing the xml.
- */
- public String nextTagOrText(int parentDepth, StringBuilder textBuilder)
- throws IOException, ParseException {
- while (true) {
- int eventType = 0;
- try {
- eventType = mParser.next();
- } catch (XmlPullParserException e) {
- throw new ParseException(e);
- }
- int depth = mParser.getDepth();
- mCurrentStartTag = null;
-
- if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) {
- mCurrentStartTag = mParser.getName();
- if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) sb.append(" ");
- sb.append("<").append(mParser.getName());
- int count = mParser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- sb.append(" ");
- sb.append(mParser.getAttributeName(i));
- sb.append("=\"");
- sb.append(mParser.getAttributeValue(i));
- sb.append("\"");
- }
- sb.append(">");
- Log.d(mLogTag, sb.toString());
- }
- return mParser.getName();
- }
-
- if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
- if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < depth; i++) sb.append(" ");
- sb.append("</>"); // Not quite valid xml but it gets the job done.
- Log.d(mLogTag, sb.toString());
- }
- return null;
- }
-
- if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
- // we could just rely on the caller calling close(), which it should, but try
- // to auto-close for clients that might have missed doing so.
- if (source != null) {
- source.close();
- source = null;
- }
- return null;
- }
-
- if (eventType == XmlPullParser.TEXT && depth == parentDepth) {
- if (textBuilder == null) {
- continue;
- }
- String text = mParser.getText();
- textBuilder.append(text);
- return TEXT_TAG;
- }
- }
- }
-
- /**
- * The same as nextTagOrTexxt(int, StringBuilder) but ignores text blocks.
- */
- public String nextTag(int parentDepth) throws IOException, ParseException {
- return nextTagOrText(parentDepth, null /* ignore text */);
- }
-
- /**
- * Returns the depth of the current element. The depth is 0 before the first
- * element has been returned, 1 after that, etc.
- *
- * @return the depth of the current element
- */
- public int getDepth() {
- return mParser.getDepth();
- }
-
- /**
- * Consumes the rest of the children, accumulating any text at this level into the builder.
- *
- * @param textBuilder the builder to contain any text
- * @throws IOException propogated from the XmlPullParser
- * @throws ParseException if there was an error parsing the xml.
- */
- public void readRemainingText(int parentDepth, StringBuilder textBuilder)
- throws IOException, ParseException {
- while (nextTagOrText(parentDepth, textBuilder) != null) {
- }
- }
-
- /**
- * Returns the number of attributes on the current element.
- *
- * @return the number of attributes on the current element
- */
- public int numAttributes() {
- return mParser.getAttributeCount();
- }
-
- /**
- * Returns the name of the nth attribute on the current element.
- *
- * @return the name of the nth attribute on the current element
- */
- public String getAttributeName(int i) {
- return mParser.getAttributeName(i);
- }
-
- /**
- * Returns the namespace of the nth attribute on the current element.
- *
- * @return the namespace of the nth attribute on the current element
- */
- public String getAttributeNamespace(int i) {
- return mParser.getAttributeNamespace(i);
- }
-
- /**
- * Returns the string value of the named attribute.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute
- * @param defaultValue the value to return if the attribute is not specified
- * @return the value of the attribute
- */
- public String getStringAttribute(
- String namespace, String name, String defaultValue) {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- return value;
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing
- */
- public String getStringAttribute(String namespace, String name) throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) {
- throw new ParseException(
- "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element");
- }
- return value;
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not a valid integer.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute
- * @param defaultValue the value to return if the attribute is not specified
- * @return the value of the attribute
- * @throws ParseException thrown if the attribute not a valid integer.
- */
- public int getIntAttribute(String namespace, String name, int defaultValue)
- throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as an integer");
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present or is not a valid integer.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing or not a valid integer.
- */
- public int getIntAttribute(String namespace, String name)
- throws ParseException {
- String value = getStringAttribute(namespace, name);
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as an integer");
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not a valid long.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is not a valid long.
- */
- public long getLongAttribute(String namespace, String name, long defaultValue)
- throws ParseException {
- String value = mParser.getAttributeValue(namespace, name);
- if (null == value) return defaultValue;
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as a long");
- }
- }
-
- /**
- * Close this SimplePullParser and any underlying resources (e.g., its InputStream or
- * Reader source) used by this SimplePullParser.
- */
- public void close() {
- if (source != null) {
- try {
- source.close();
- } catch (IOException ioe) {
- // ignore
- }
- }
- }
-
- /**
- * Returns the string value of the named attribute. An exception will
- * be thrown if the attribute is not present or is not a valid long.
- *
- * @param namespace the namespace of the attribute
- * @param name the name of the attribute @return the value of the attribute
- * @throws ParseException thrown if the attribute is missing or not a valid long.
- */
- public long getLongAttribute(String namespace, String name)
- throws ParseException {
- String value = getStringAttribute(namespace, name);
- try {
- return Long.parseLong(value);
- } catch (NumberFormatException e) {
- throw new ParseException("Cannot parse '" + value + "' as a long");
- }
- }
-
- public static final class ParseException extends Exception {
- public ParseException(String message) {
- super(message);
- }
-
- public ParseException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public ParseException(Throwable cause) {
- super(cause);
- }
- }
-}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 015268b..a39d06b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -32,7 +32,9 @@ LOCAL_SRC_FILES:= \
android_opengl_GLES10Ext.cpp \
android_opengl_GLES11.cpp \
android_opengl_GLES11Ext.cpp \
+ android_opengl_GLES20.cpp \
android_database_CursorWindow.cpp \
+ android_database_SQLiteCompiledSql.cpp \
android_database_SQLiteDebug.cpp \
android_database_SQLiteDatabase.cpp \
android_database_SQLiteProgram.cpp \
@@ -43,6 +45,7 @@ LOCAL_SRC_FILES:= \
android_view_Surface.cpp \
android_view_ViewRoot.cpp \
android_text_AndroidCharacter.cpp \
+ android_text_AndroidBidi.cpp \
android_text_KeyCharacterMap.cpp \
android_os_Debug.cpp \
android_os_FileUtils.cpp \
@@ -53,9 +56,9 @@ LOCAL_SRC_FILES:= \
android_os_SystemClock.cpp \
android_os_SystemProperties.cpp \
android_os_UEventObserver.cpp \
- android_os_Hardware.cpp \
android_net_LocalSocketImpl.cpp \
android_net_NetUtils.cpp \
+ android_net_TrafficStats.cpp \
android_net_wifi_Wifi.cpp \
android_nio_utils.cpp \
android_pim_EventRecurrence.cpp \
@@ -69,7 +72,6 @@ LOCAL_SRC_FILES:= \
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
- android_util_Base64.cpp \
android/graphics/Bitmap.cpp \
android/graphics/BitmapFactory.cpp \
android/graphics/Camera.cpp \
@@ -98,6 +100,7 @@ LOCAL_SRC_FILES:= \
android/graphics/Shader.cpp \
android/graphics/Typeface.cpp \
android/graphics/Xfermode.cpp \
+ android/graphics/YuvToJpegEncoder.cpp \
android_media_AudioRecord.cpp \
android_media_AudioSystem.cpp \
android_media_AudioTrack.cpp \
@@ -147,6 +150,7 @@ LOCAL_C_INCLUDES += \
external/tremor/Tremor \
external/icu4c/i18n \
external/icu4c/common \
+ external/jpeg \
frameworks/opt/emoji
LOCAL_SHARED_LIBRARIES := \
@@ -157,12 +161,16 @@ LOCAL_SHARED_LIBRARIES := \
libbinder \
libnetutils \
libui \
+ libsurfaceflinger_client \
+ libcamera_client \
libskiagl \
libskia \
libsqlite \
libdvm \
libEGL \
libGLESv1_CM \
+ libGLESv2 \
+ libETC1 \
libhardware \
libhardware_legacy \
libsonivox \
@@ -172,7 +180,8 @@ LOCAL_SHARED_LIBRARIES := \
libicui18n \
libicudata \
libmedia \
- libwpa_client
+ libwpa_client \
+ libjpeg
ifeq ($(BOARD_HAVE_BLUETOOTH),true)
LOCAL_C_INCLUDES += \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f61e247..9fbf171 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -64,6 +64,7 @@ extern int register_android_graphics_PathEffect(JNIEnv* env);
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_Shader(JNIEnv* env);
extern int register_android_graphics_Typeface(JNIEnv* env);
+extern int register_android_graphics_YuvImage(JNIEnv* env);
extern int register_com_google_android_gles_jni_EGLImpl(JNIEnv* env);
extern int register_com_google_android_gles_jni_GLImpl(JNIEnv* env);
@@ -71,6 +72,7 @@ extern int register_android_opengl_jni_GLES10(JNIEnv* env);
extern int register_android_opengl_jni_GLES10Ext(JNIEnv* env);
extern int register_android_opengl_jni_GLES11(JNIEnv* env);
extern int register_android_opengl_jni_GLES11Ext(JNIEnv* env);
+extern int register_android_opengl_jni_GLES20(JNIEnv* env);
extern int register_android_hardware_Camera(JNIEnv *env);
@@ -115,6 +117,7 @@ extern int register_android_view_Display(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_ViewRoot(JNIEnv* env);
extern int register_android_database_CursorWindow(JNIEnv* env);
+extern int register_android_database_SQLiteCompiledSql(JNIEnv* env);
extern int register_android_database_SQLiteDatabase(JNIEnv* env);
extern int register_android_database_SQLiteDebug(JNIEnv* env);
extern int register_android_database_SQLiteProgram(JNIEnv* env);
@@ -129,7 +132,6 @@ extern int register_android_os_ParcelFileDescriptor(JNIEnv *env);
extern int register_android_os_Power(JNIEnv *env);
extern int register_android_os_StatFs(JNIEnv *env);
extern int register_android_os_SystemProperties(JNIEnv *env);
-extern int register_android_os_Hardware(JNIEnv* env);
extern int register_android_os_SystemClock(JNIEnv* env);
extern int register_android_os_FileObserver(JNIEnv *env);
extern int register_android_os_FileUtils(JNIEnv *env);
@@ -137,9 +139,11 @@ extern int register_android_os_UEventObserver(JNIEnv* env);
extern int register_android_os_MemoryFile(JNIEnv* env);
extern int register_android_net_LocalSocketImpl(JNIEnv* env);
extern int register_android_net_NetworkUtils(JNIEnv* env);
+extern int register_android_net_TrafficStats(JNIEnv* env);
extern int register_android_net_wifi_WifiManager(JNIEnv* env);
extern int register_android_security_Md5MessageDigest(JNIEnv *env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
+extern int register_android_text_AndroidBidi(JNIEnv *env);
extern int register_android_text_KeyCharacterMap(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_bluetooth_HeadsetBase(JNIEnv* env);
@@ -151,7 +155,6 @@ extern int register_android_server_BluetoothEventLoop(JNIEnv *env);
extern int register_android_server_BluetoothA2dpService(JNIEnv* env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env);
-extern int register_android_util_Base64(JNIEnv* env);
extern int register_android_location_GpsLocationProvider(JNIEnv* env);
extern int register_android_backup_BackupDataInput(JNIEnv *env);
extern int register_android_backup_BackupDataOutput(JNIEnv *env);
@@ -654,6 +657,15 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
}
}
+ /* enable poisoning of memory of freed objects */
+ property_get("dalvik.vm.gc.overwritefree", propBuf, "false");
+ if (strcmp(propBuf, "true") == 0) {
+ opt.optionString = "-Xgc:overwritefree";
+ mOptions.add(opt);
+ } else if (strcmp(propBuf, "false") != 0) {
+ LOGW("dalvik.vm.gc.overwritefree should be 'true' or 'false'");
+ }
+
/* enable debugging; set suspend=y to pause during VM init */
#ifdef HAVE_ANDROID_OS
/* use android ADB transport */
@@ -691,12 +703,21 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
//mOptions.add(opt);
}
+ char lockProfThresholdBuf[sizeof("-Xlockprofthreshold:") + sizeof(propBuf)];
+ property_get("dalvik.vm.lockprof.threshold", propBuf, "");
+ if (strlen(propBuf) > 0) {
+ strcpy(lockProfThresholdBuf, "-Xlockprofthreshold:");
+ strcat(lockProfThresholdBuf, propBuf);
+ opt.optionString = lockProfThresholdBuf;
+ mOptions.add(opt);
+ }
+
#if defined(WITH_JIT)
/* Minimal profile threshold to trigger JIT compilation */
- char jitThresholdBuf[sizeof("-Xthreshold:") + PROPERTY_VALUE_MAX];
+ char jitThresholdBuf[sizeof("-Xjitthreshold:") + PROPERTY_VALUE_MAX];
property_get("dalvik.vm.jit.threshold", propBuf, "");
if (strlen(propBuf) > 0) {
- strcpy(jitThresholdBuf, "-Xthreshold:");
+ strcpy(jitThresholdBuf, "-Xjitthreshold:");
strcat(jitThresholdBuf, propBuf);
opt.optionString = jitThresholdBuf;
mOptions.add(opt);
@@ -750,6 +771,18 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
opt.optionString = "-Xjitprofile";
mOptions.add(opt);
}
+
+ /*
+ * Disable optimizations by setting the corresponding bit to 1.
+ */
+ char jitOptBuf[sizeof("-Xjitdisableopt:") + PROPERTY_VALUE_MAX];
+ property_get("dalvik.vm.jit.disableopt", propBuf, "");
+ if (strlen(propBuf) > 0) {
+ strcpy(jitOptBuf, "-Xjitdisableopt:");
+ strcat(jitOptBuf, propBuf);
+ opt.optionString = jitOptBuf;
+ mOptions.add(opt);
+ }
#endif
if (executionMode == kEMIntPortable) {
@@ -1013,7 +1046,7 @@ static int javaAttachThread(const char* threadName, JNIEnv** pEnv)
result = vm->AttachCurrentThread(pEnv, (void*) &args);
if (result != JNI_OK)
- LOGE("ERROR: thread attach failed\n");
+ LOGI("NOTE: attach of thread '%s' failed\n", threadName);
return result;
}
@@ -1162,10 +1195,10 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_emoji_EmojiFactory),
REG_JNI(register_android_security_Md5MessageDigest),
REG_JNI(register_android_text_AndroidCharacter),
+ REG_JNI(register_android_text_AndroidBidi),
REG_JNI(register_android_text_KeyCharacterMap),
REG_JNI(register_android_os_Process),
REG_JNI(register_android_os_Binder),
- REG_JNI(register_android_os_Hardware),
REG_JNI(register_android_view_Display),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_PixelFormat),
@@ -1178,6 +1211,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_opengl_jni_GLES10Ext),
REG_JNI(register_android_opengl_jni_GLES11),
REG_JNI(register_android_opengl_jni_GLES11Ext),
+ REG_JNI(register_android_opengl_jni_GLES20),
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
@@ -1202,9 +1236,11 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Shader),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
+ REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_com_android_internal_graphics_NativeUtils),
REG_JNI(register_android_database_CursorWindow),
+ REG_JNI(register_android_database_SQLiteCompiledSql),
REG_JNI(register_android_database_SQLiteDatabase),
REG_JNI(register_android_database_SQLiteDebug),
REG_JNI(register_android_database_SQLiteProgram),
@@ -1220,6 +1256,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
REG_JNI(register_android_net_NetworkUtils),
+ REG_JNI(register_android_net_TrafficStats),
REG_JNI(register_android_net_wifi_WifiManager),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_com_android_internal_os_ZygoteInit),
@@ -1241,7 +1278,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_server_BluetoothA2dpService),
REG_JNI(register_android_message_digest_sha1),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
- REG_JNI(register_android_util_Base64),
REG_JNI(register_android_location_GpsLocationProvider),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
diff --git a/core/jni/CursorWindow.cpp b/core/jni/CursorWindow.cpp
index 7864189..7877921 100644
--- a/core/jni/CursorWindow.cpp
+++ b/core/jni/CursorWindow.cpp
@@ -18,7 +18,8 @@
#define LOG_TAG "CursorWindow"
#include <utils/Log.h>
-#include <binder/MemoryDealer.h>
+#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryBase.h>
#include <assert.h>
#include <string.h>
@@ -37,7 +38,7 @@ CursorWindow::CursorWindow(size_t maxSize) :
{
}
-bool CursorWindow::setMemory(sp<IMemory> memory)
+bool CursorWindow::setMemory(const sp<IMemory>& memory)
{
mMemory = memory;
mData = (uint8_t *) memory->pointer();
@@ -47,7 +48,6 @@ bool CursorWindow::setMemory(sp<IMemory> memory)
mHeader = (window_header_t *) mData;
// Make the window read-only
- mHeap = NULL;
ssize_t size = memory->size();
mSize = size;
mMaxSize = size;
@@ -60,9 +60,10 @@ bool CursorWindow::initBuffer(bool localOnly)
{
//TODO Use a non-memory dealer mmap region for localOnly
- mHeap = new MemoryDealer(new SharedHeap(mMaxSize, 0, "CursorWindow"));
- if (mHeap != NULL) {
- mMemory = mHeap->allocate(mMaxSize);
+ sp<MemoryHeapBase> heap;
+ heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
+ if (heap != NULL) {
+ mMemory = new MemoryBase(heap, 0, mMaxSize);
if (mMemory != NULL) {
mData = (uint8_t *) mMemory->pointer();
if (mData) {
@@ -75,10 +76,10 @@ bool CursorWindow::initBuffer(bool localOnly)
return true;
}
}
- LOGE("memory dealer allocation failed");
+ LOGE("CursorWindow heap allocation failed");
return false;
} else {
- LOGE("failed to create the memory dealer");
+ LOGE("failed to create the CursorWindow heap");
return false;
}
}
diff --git a/core/jni/CursorWindow.h b/core/jni/CursorWindow.h
index e98b009..3fcb560 100644
--- a/core/jni/CursorWindow.h
+++ b/core/jni/CursorWindow.h
@@ -21,7 +21,7 @@
#include <stddef.h>
#include <stdint.h>
-#include <binder/MemoryDealer.h>
+#include <binder/IMemory.h>
#include <utils/RefBase.h>
#include <jni.h>
@@ -101,7 +101,7 @@ class CursorWindow
public:
CursorWindow(size_t maxSize);
CursorWindow(){}
- bool setMemory(sp<IMemory>);
+ bool setMemory(const sp<IMemory>&);
~CursorWindow();
bool initBuffer(bool localOnly);
@@ -189,7 +189,6 @@ private:
size_t mSize;
size_t mMaxSize;
window_header_t * mHeader;
- sp<MemoryDealer> mHeap;
sp<IMemory> mMemory;
/**
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 0f1b845..88bbafd 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -527,6 +527,53 @@ static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject,
}
}
+static bool Bitmap_sameAs(JNIEnv* env, jobject, const SkBitmap* bm0,
+ const SkBitmap* bm1) {
+ if (bm0->width() != bm1->width() ||
+ bm0->height() != bm1->height() ||
+ bm0->config() != bm1->config()) {
+ return false;
+ }
+
+ SkAutoLockPixels alp0(*bm0);
+ SkAutoLockPixels alp1(*bm1);
+
+ // if we can't load the pixels, return false
+ if (NULL == bm0->getPixels() || NULL == bm1->getPixels()) {
+ return false;
+ }
+
+ if (bm0->config() == SkBitmap::kIndex8_Config) {
+ SkColorTable* ct0 = bm0->getColorTable();
+ SkColorTable* ct1 = bm1->getColorTable();
+ if (NULL == ct0 || NULL == ct1) {
+ return false;
+ }
+ if (ct0->count() != ct1->count()) {
+ return false;
+ }
+
+ SkAutoLockColors alc0(ct0);
+ SkAutoLockColors alc1(ct1);
+ const size_t size = ct0->count() * sizeof(SkPMColor);
+ if (memcmp(alc0.colors(), alc1.colors(), size) != 0) {
+ return false;
+ }
+ }
+
+ // now compare each scanline. We can't do the entire buffer at once,
+ // since we don't care about the pixel values that might extend beyond
+ // the width (since the scanline might be larger than the logical width)
+ const int h = bm0->height();
+ const size_t size = bm0->width() * bm0->bytesPerPixel();
+ for (int y = 0; y < h; y++) {
+ if (memcmp(bm0->getAddr(0, y), bm1->getAddr(0, y), size) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
static void Bitmap_prepareToDraw(JNIEnv* env, jobject, SkBitmap* bitmap) {
bitmap->lockPixels();
bitmap->unlockPixels();
@@ -567,7 +614,8 @@ static JNINativeMethod gBitmapMethods[] = {
(void*)Bitmap_copyPixelsToBuffer },
{ "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V",
(void*)Bitmap_copyPixelsFromBuffer },
- { "nativePrepareToDraw", "(I)V", (void*)Bitmap_prepareToDraw }
+ { "nativeSameAs", "(II)Z", (void*)Bitmap_sameAs },
+ { "nativePrepareToDraw", "(I)V", (void*)Bitmap_prepareToDraw },
};
#define kClassPathName "android/graphics/Bitmap"
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 65f6845..b41bad0 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -164,8 +164,11 @@ void AutoDecoderCancel::Validate() {
using namespace android;
class NinePatchPeeker : public SkImageDecoder::Peeker {
+ SkImageDecoder* fHost;
public:
- NinePatchPeeker() {
+ NinePatchPeeker(SkImageDecoder* host) {
+ // the host lives longer than we do, so a raw ptr is safe
+ fHost = host;
fPatchIsValid = false;
}
@@ -197,6 +200,19 @@ public:
// fPatch.sizeLeft, fPatch.sizeTop,
// fPatch.sizeRight, fPatch.sizeBottom);
fPatchIsValid = true;
+
+ // now update our host to force index or 32bit config
+ // 'cause we don't want 565 predithered, since as a 9patch, we know
+ // we will be stretched, and therefore we want to dither afterwards.
+ static const SkBitmap::Config gNo565Pref[] = {
+ SkBitmap::kIndex8_Config,
+ SkBitmap::kIndex8_Config,
+ SkBitmap::kARGB_8888_Config,
+ SkBitmap::kARGB_8888_Config,
+ SkBitmap::kARGB_8888_Config,
+ SkBitmap::kARGB_8888_Config,
+ };
+ fHost->setPrefConfigTable(gNo565Pref);
} else {
fPatch = NULL;
}
@@ -331,12 +347,14 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
// i.e. dynamically allocated, since its lifetime may exceed the current stack
// frame.
static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
- jobject options, bool allowPurgeable) {
+ jobject options, bool allowPurgeable,
+ bool forcePurgeable = false) {
int sampleSize = 1;
SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
bool doDither = true;
- bool isPurgeable = allowPurgeable && optionsPurgeable(env, options);
+ bool isPurgeable = forcePurgeable ||
+ (allowPurgeable && optionsPurgeable(env, options));
bool reportSizeToVM = optionsReportSizeToVM(env, options);
if (NULL != options) {
@@ -362,7 +380,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
decoder->setSampleSize(sampleSize);
decoder->setDitherImage(doDither);
- NinePatchPeeker peeker;
+ NinePatchPeeker peeker(decoder);
JavaPixelAllocator javaAllocator(env, reportSizeToVM);
SkBitmap* bitmap = new SkBitmap;
Res_png_9patch dummy9Patch;
@@ -568,8 +586,10 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
jobject options) { // BitmapFactory$Options
SkStream* stream;
Asset* asset = reinterpret_cast<Asset*>(native_asset);
+ // assets can always be rebuilt, so force this
+ bool forcePurgeable = true;
- if (optionsPurgeable(env, options)) {
+ if (forcePurgeable || optionsPurgeable(env, options)) {
// if we could "ref/reopen" the asset, we may not need to copy it here
// and we could assume optionsShareable, since assets are always RO
stream = copyAssetToStream(asset);
@@ -582,7 +602,7 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
stream = new AssetStreamAdaptor(asset);
}
SkAutoUnref aur(stream);
- return doDecode(env, stream, padding, options, true);
+ return doDecode(env, stream, padding, options, true, forcePurgeable);
}
static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -645,6 +665,23 @@ static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObj
return chunkObject;
}
+static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
+ SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
+
+ // these are the only default configs that make sense for codecs right now
+ static const SkBitmap::Config gValidDefConfig[] = {
+ SkBitmap::kRGB_565_Config,
+ SkBitmap::kARGB_8888_Config,
+ };
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
+ if (config == gValidDefConfig[i]) {
+ SkImageDecoder::SetDeviceConfig(config);
+ break;
+ }
+ }
+}
+
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gMethods[] = {
@@ -671,8 +708,9 @@ static JNINativeMethod gMethods[] = {
{ "nativeScaleNinePatch",
"([BFLandroid/graphics/Rect;)[B",
(void*)nativeScaleNinePatch
- }
+ },
+ { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
};
static JNINativeMethod gOptionsMethods[] = {
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 7f526d1..e1e9536 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -864,8 +864,6 @@ public:
*matrix = canvas->getTotalMatrix();
}
};
-
-///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gCanvasMethods[] = {
{"finalizer", "(I)V", (void*) SkCanvasGlue::finalizer},
@@ -971,17 +969,17 @@ static JNINativeMethod gCanvasMethods[] = {
///////////////////////////////////////////////////////////////////////////////
static void BoundaryPatch_computeCubic(JNIEnv* env, jobject, jfloatArray jpts,
- int texW, int texH, int rows, int cols,
- jfloatArray jverts, jshortArray jidx) {
- AutoJavaFloatArray ptsArray(env, jpts, 24);
+ int texW, int texH, int rows, int cols,
+ jfloatArray jverts, jshortArray jidx) {
+ AutoJavaFloatArray ptsArray(env, jpts, 24, kRO_JNIAccess);
int vertCount = rows * cols;
- AutoJavaFloatArray vertsArray(env, jverts, vertCount * 4);
+ AutoJavaFloatArray vertsArray(env, jverts, vertCount * 4, kRW_JNIAccess);
SkPoint* verts = (SkPoint*)vertsArray.ptr();
SkPoint* texs = verts + vertCount;
int idxCount = (rows - 1) * (cols - 1) * 6;
- AutoJavaShortArray idxArray(env, jidx, idxCount);
+ AutoJavaShortArray idxArray(env, jidx, idxCount, kRW_JNIAccess);
uint16_t* idx = (uint16_t*)idxArray.ptr(); // cast from int16_t*
SkCubicBoundary cubic;
@@ -1016,7 +1014,7 @@ int register_android_graphics_Canvas(JNIEnv* env) {
REG(env, "android/graphics/Canvas", gCanvasMethods);
REG(env, "android/graphics/utils/BoundaryPatch", gBoundaryPatchMethods);
-
+
return result;
}
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 2e0caed..5659ba2 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -1,3 +1,5 @@
+#define LOG_TAG "GraphicsJNI"
+
#include "jni.h"
#include "GraphicsJNI.h"
#include "NIOBuffer.h"
@@ -56,7 +58,7 @@ bool GraphicsJNI::hasException(JNIEnv *env) {
///////////////////////////////////////////////////////////////////////////////
AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
- int minLength)
+ int minLength, JNIAccess access)
: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
SkASSERT(env);
if (array) {
@@ -66,11 +68,12 @@ AutoJavaFloatArray::AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
}
fPtr = env->GetFloatArrayElements(array, NULL);
}
+ fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
}
AutoJavaFloatArray::~AutoJavaFloatArray() {
if (fPtr) {
- fEnv->ReleaseFloatArrayElements(fArray, fPtr, 0);
+ fEnv->ReleaseFloatArrayElements(fArray, fPtr, fReleaseMode);
}
}
@@ -94,7 +97,7 @@ AutoJavaIntArray::~AutoJavaIntArray() {
}
AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
- int minLength)
+ int minLength, JNIAccess access)
: fEnv(env), fArray(array), fPtr(NULL), fLen(0) {
SkASSERT(env);
if (array) {
@@ -104,11 +107,12 @@ AutoJavaShortArray::AutoJavaShortArray(JNIEnv* env, jshortArray array,
}
fPtr = env->GetShortArrayElements(array, NULL);
}
+ fReleaseMode = (access == kRO_JNIAccess) ? JNI_ABORT : 0;
}
AutoJavaShortArray::~AutoJavaShortArray() {
if (fPtr) {
- fEnv->ReleaseShortArrayElements(fArray, fPtr, 0);
+ fEnv->ReleaseShortArrayElements(fArray, fPtr, fReleaseMode);
}
}
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 7adadbc..fe24b05 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -80,9 +80,15 @@ private:
bool fReportSizeToVM;
};
+enum JNIAccess {
+ kRO_JNIAccess,
+ kRW_JNIAccess
+};
+
class AutoJavaFloatArray {
public:
- AutoJavaFloatArray(JNIEnv* env, jfloatArray array, int minLength = 0);
+ AutoJavaFloatArray(JNIEnv* env, jfloatArray array,
+ int minLength = 0, JNIAccess = kRW_JNIAccess);
~AutoJavaFloatArray();
float* ptr() const { return fPtr; }
@@ -93,6 +99,7 @@ private:
jfloatArray fArray;
float* fPtr;
int fLen;
+ int fReleaseMode;
};
class AutoJavaIntArray {
@@ -112,7 +119,8 @@ private:
class AutoJavaShortArray {
public:
- AutoJavaShortArray(JNIEnv* env, jshortArray array, int minLength = 0);
+ AutoJavaShortArray(JNIEnv* env, jshortArray array,
+ int minLength = 0, JNIAccess = kRW_JNIAccess);
~AutoJavaShortArray();
jshort* ptr() const { return fPtr; }
@@ -123,6 +131,7 @@ private:
jshortArray fArray;
jshort* fPtr;
int fLen;
+ int fReleaseMode;
};
class AutoJavaByteArray {
diff --git a/core/jni/android/graphics/Path.cpp b/core/jni/android/graphics/Path.cpp
index 11c608c..b8b2a10 100644
--- a/core/jni/android/graphics/Path.cpp
+++ b/core/jni/android/graphics/Path.cpp
@@ -75,7 +75,7 @@ public:
return result;
}
- static void computeBounds(JNIEnv* env, jobject clazz, SkPath* obj, jobject bounds, int boundstype) {
+ static void computeBounds(JNIEnv* env, jobject clazz, SkPath* obj, jobject bounds) {
const SkRect& bounds_ = obj->getBounds();
GraphicsJNI::rect_to_jrectf(bounds_, env, bounds);
}
@@ -267,7 +267,7 @@ static JNINativeMethod methods[] = {
{"native_setFillType","(II)V", (void*) SkPathGlue::setFillType},
{"native_isEmpty","(I)Z", (void*) SkPathGlue::isEmpty},
{"native_isRect","(ILandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect},
- {"native_computeBounds","(ILandroid/graphics/RectF;I)V", (void*) SkPathGlue::computeBounds},
+ {"native_computeBounds","(ILandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds},
{"native_incReserve","(II)V", (void*) SkPathGlue::incReserve},
{"native_moveTo","(IFF)V", (void*) SkPathGlue::moveTo__FF},
{"native_rMoveTo","(IFF)V", (void*) SkPathGlue::rMoveTo},
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.cpp b/core/jni/android/graphics/YuvToJpegEncoder.cpp
new file mode 100644
index 0000000..0a0c5b3
--- /dev/null
+++ b/core/jni/android/graphics/YuvToJpegEncoder.cpp
@@ -0,0 +1,250 @@
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "SkJpegUtility.h"
+#include "YuvToJpegEncoder.h"
+#include <ui/PixelFormat.h>
+#include <hardware/hardware.h>
+
+#include <jni.h>
+
+YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
+ // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
+ // for now.
+ if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
+ return new Yuv420SpToJpegEncoder(strides);
+ } else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) {
+ return new Yuv422IToJpegEncoder(strides);
+ } else {
+ return NULL;
+ }
+}
+
+YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) {
+}
+
+bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
+ int height, int* offsets, int jpegQuality) {
+ jpeg_compress_struct cinfo;
+ skjpeg_error_mgr sk_err;
+ skjpeg_destination_mgr sk_wstream(stream);
+
+ cinfo.err = jpeg_std_error(&sk_err);
+ sk_err.error_exit = skjpeg_error_exit;
+ if (setjmp(sk_err.fJmpBuf)) {
+ return false;
+ }
+ jpeg_create_compress(&cinfo);
+
+ cinfo.dest = &sk_wstream;
+
+ setJpegCompressStruct(&cinfo, width, height, jpegQuality);
+
+ jpeg_start_compress(&cinfo, TRUE);
+
+ compress(&cinfo, (uint8_t*) inYuv, offsets);
+
+ jpeg_finish_compress(&cinfo);
+
+ return true;
+}
+
+void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo,
+ int width, int height, int quality) {
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+ cinfo->input_components = 3;
+ cinfo->in_color_space = JCS_YCbCr;
+ jpeg_set_defaults(cinfo);
+
+ jpeg_set_quality(cinfo, quality, TRUE);
+ jpeg_set_colorspace(cinfo, JCS_YCbCr);
+ cinfo->raw_data_in = TRUE;
+ cinfo->dct_method = JDCT_IFAST;
+ configSamplingFactors(cinfo);
+}
+
+///////////////////////////////////////////////////////////////////
+Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) :
+ YuvToJpegEncoder(strides) {
+ fNumPlanes = 2;
+}
+
+void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) {
+ SkDebugf("onFlyCompress");
+ JSAMPROW y[16];
+ JSAMPROW cb[8];
+ JSAMPROW cr[8];
+ JSAMPARRAY planes[3];
+ planes[0] = y;
+ planes[1] = cb;
+ planes[2] = cr;
+
+ int width = cinfo->image_width;
+ int height = cinfo->image_height;
+ uint8_t* yPlanar = yuv + offsets[0];
+ uint8_t* vuPlanar = yuv + offsets[1]; //width * height;
+ uint8_t* uRows = new uint8_t [8 * (width >> 1)];
+ uint8_t* vRows = new uint8_t [8 * (width >> 1)];
+
+
+ // process 16 lines of Y and 8 lines of U/V each time.
+ while (cinfo->next_scanline < cinfo->image_height) {
+ //deitnerleave u and v
+ deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width);
+
+ for (int i = 0; i < 16; i++) {
+ // y row
+ y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0];
+
+ // construct u row and v row
+ if ((i & 1) == 0) {
+ // height and width are both halved because of downsampling
+ int offset = (i >> 1) * (width >> 1);
+ cb[i/2] = uRows + offset;
+ cr[i/2] = vRows + offset;
+ }
+ }
+ jpeg_write_raw_data(cinfo, planes, 16);
+ }
+ delete [] uRows;
+ delete [] vRows;
+
+}
+
+void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width) {
+ for (int row = 0; row < 8; ++row) {
+ int offset = ((rowIndex >> 1) + row) * fStrides[1];
+ uint8_t* vu = vuPlanar + offset;
+ for (int i = 0; i < (width >> 1); ++i) {
+ int index = row * (width >> 1) + i;
+ uRows[index] = vu[1];
+ vRows[index] = vu[0];
+ vu += 2;
+ }
+ }
+}
+
+void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
+ // cb and cr are horizontally downsampled and vertically downsampled as well.
+ cinfo->comp_info[0].h_samp_factor = 2;
+ cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = 1;
+ cinfo->comp_info[1].v_samp_factor = 1;
+ cinfo->comp_info[2].h_samp_factor = 1;
+ cinfo->comp_info[2].v_samp_factor = 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) :
+ YuvToJpegEncoder(strides) {
+ fNumPlanes = 1;
+}
+
+void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) {
+ SkDebugf("onFlyCompress_422");
+ JSAMPROW y[16];
+ JSAMPROW cb[16];
+ JSAMPROW cr[16];
+ JSAMPARRAY planes[3];
+ planes[0] = y;
+ planes[1] = cb;
+ planes[2] = cr;
+
+ int width = cinfo->image_width;
+ int height = cinfo->image_height;
+ uint8_t* yRows = new uint8_t [16 * width];
+ uint8_t* uRows = new uint8_t [16 * (width >> 1)];
+ uint8_t* vRows = new uint8_t [16 * (width >> 1)];
+
+ uint8_t* yuvOffset = yuv + offsets[0];
+
+ // process 16 lines of Y and 16 lines of U/V each time.
+ while (cinfo->next_scanline < cinfo->image_height) {
+ deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height);
+
+ for (int i = 0; i < 16; i++) {
+ // y row
+ y[i] = yRows + i * width;
+
+ // construct u row and v row
+ // width is halved because of downsampling
+ int offset = i * (width >> 1);
+ cb[i] = uRows + offset;
+ cr[i] = vRows + offset;
+ }
+
+ jpeg_write_raw_data(cinfo, planes, 16);
+ }
+ delete [] yRows;
+ delete [] uRows;
+ delete [] vRows;
+}
+
+
+void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width, int height) {
+ for (int row = 0; row < 16; ++row) {
+ uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0];
+ for (int i = 0; i < (width >> 1); ++i) {
+ int indexY = row * width + (i << 1);
+ int indexU = row * (width >> 1) + i;
+ yRows[indexY] = yuvSeg[0];
+ yRows[indexY + 1] = yuvSeg[2];
+ uRows[indexU] = yuvSeg[1];
+ vRows[indexU] = yuvSeg[3];
+ yuvSeg += 4;
+ }
+ }
+}
+
+void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
+ // cb and cr are horizontally downsampled and vertically downsampled as well.
+ cinfo->comp_info[0].h_samp_factor = 2;
+ cinfo->comp_info[0].v_samp_factor = 2;
+ cinfo->comp_info[1].h_samp_factor = 1;
+ cinfo->comp_info[1].v_samp_factor = 2;
+ cinfo->comp_info[2].h_samp_factor = 1;
+ cinfo->comp_info[2].v_samp_factor = 2;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
+ int format, int width, int height, jintArray offsets,
+ jintArray strides, int jpegQuality, jobject jstream,
+ jbyteArray jstorage) {
+ jbyte* yuv = env->GetByteArrayElements(inYuv, NULL);
+ SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
+
+ jint* imgOffsets = env->GetIntArrayElements(offsets, NULL);
+ jint* imgStrides = env->GetIntArrayElements(strides, NULL);
+ YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides);
+ if (encoder == NULL) {
+ return false;
+ }
+ encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality);
+
+ delete encoder;
+ env->ReleaseByteArrayElements(inYuv, yuv, 0);
+ env->ReleaseIntArrayElements(offsets, imgOffsets, 0);
+ env->ReleaseIntArrayElements(strides, imgStrides, 0);
+ return true;
+}
+///////////////////////////////////////////////////////////////////////////////
+
+#include <android_runtime/AndroidRuntime.h>
+
+static JNINativeMethod gYuvImageMethods[] = {
+ { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
+ (void*)YuvImage_compressToJpeg }
+};
+
+#define kClassPathName "android/graphics/YuvImage"
+
+int register_android_graphics_YuvImage(JNIEnv* env);
+int register_android_graphics_YuvImage(JNIEnv* env)
+{
+ return android::AndroidRuntime::registerNativeMethods(env, kClassPathName,
+ gYuvImageMethods, SK_ARRAY_COUNT(gYuvImageMethods));
+}
diff --git a/core/jni/android/graphics/YuvToJpegEncoder.h b/core/jni/android/graphics/YuvToJpegEncoder.h
new file mode 100644
index 0000000..97106ce
--- /dev/null
+++ b/core/jni/android/graphics/YuvToJpegEncoder.h
@@ -0,0 +1,74 @@
+#ifndef YuvToJpegEncoder_DEFINED
+#define YuvToJpegEncoder_DEFINED
+
+#include "SkTypes.h"
+#include "SkStream.h"
+extern "C" {
+ #include "jpeglib.h"
+ #include "jerror.h"
+}
+
+class YuvToJpegEncoder {
+public:
+ /** Create an encoder based on the YUV format.
+ *
+ * @param pixelFormat The yuv pixel format as defined in ui/PixelFormat.h.
+ * @param strides The number of row bytes in each image plane.
+ * @return an encoder based on the pixelFormat.
+ */
+ static YuvToJpegEncoder* create(int pixelFormat, int* strides);
+
+ YuvToJpegEncoder(int* strides);
+
+ /** Encode YUV data to jpeg, which is output to a stream.
+ *
+ * @param stream The jpeg output stream.
+ * @param inYuv The input yuv data.
+ * @param width Width of the the Yuv data in terms of pixels.
+ * @param height Height of the Yuv data in terms of pixels.
+ * @param offsets The offsets in each image plane with respect to inYuv.
+ * @param jpegQuality Picture quality in [0, 100].
+ * @return true if successfully compressed the stream.
+ */
+ bool encode(SkWStream* stream, void* inYuv, int width,
+ int height, int* offsets, int jpegQuality);
+
+ virtual ~YuvToJpegEncoder() {}
+
+protected:
+ int fNumPlanes;
+ int* fStrides;
+ void setJpegCompressStruct(jpeg_compress_struct* cinfo, int width,
+ int height, int quality);
+ virtual void configSamplingFactors(jpeg_compress_struct* cinfo) = 0;
+ virtual void compress(jpeg_compress_struct* cinfo,
+ uint8_t* yuv, int* offsets) = 0;
+};
+
+class Yuv420SpToJpegEncoder : public YuvToJpegEncoder {
+public:
+ Yuv420SpToJpegEncoder(int* strides);
+ virtual ~Yuv420SpToJpegEncoder() {}
+
+private:
+ void configSamplingFactors(jpeg_compress_struct* cinfo);
+ void deinterleaveYuv(uint8_t* yuv, int width, int height,
+ uint8_t*& yPlanar, uint8_t*& uPlanar, uint8_t*& vPlanar);
+ void deinterleave(uint8_t* vuPlanar, uint8_t* uRows, uint8_t* vRows,
+ int rowIndex, int width);
+ void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets);
+};
+
+class Yuv422IToJpegEncoder : public YuvToJpegEncoder {
+public:
+ Yuv422IToJpegEncoder(int* strides);
+ virtual ~Yuv422IToJpegEncoder() {}
+
+private:
+ void configSamplingFactors(jpeg_compress_struct* cinfo);
+ void compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets);
+ void deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
+ uint8_t* vRows, int rowIndex, int width, int height);
+};
+
+#endif
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 4041346..589b255 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -23,6 +23,7 @@
#include <dlfcn.h>
#include <GLES/gl.h>
+#include <ETC1/etc1.h>
#include <core/SkBitmap.h>
@@ -39,6 +40,7 @@ namespace android {
static jclass gIAEClass;
static jclass gUOEClass;
+static jclass gAIOOBEClass;
static inline
void mx4transform(float x, float y, float z, float w, const float* pM, float* pDest) {
@@ -712,6 +714,297 @@ static jint util_texSubImage2D(JNIEnv *env, jclass clazz,
}
/*
+ * ETC1 methods.
+ */
+
+static jclass nioAccessClass;
+static jclass bufferClass;
+static jmethodID getBasePointerID;
+static jmethodID getBaseArrayID;
+static jmethodID getBaseArrayOffsetID;
+static jfieldID positionID;
+static jfieldID limitID;
+static jfieldID elementSizeShiftID;
+
+/* Cache method IDs each time the class is loaded. */
+
+static void
+nativeClassInitBuffer(JNIEnv *_env)
+{
+ jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess");
+ nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal);
+
+ jclass bufferClassLocal = _env->FindClass("java/nio/Buffer");
+ bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal);
+
+ getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
+ "getBasePointer", "(Ljava/nio/Buffer;)J");
+ getBaseArrayID = _env->GetStaticMethodID(nioAccessClass,
+ "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
+ getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass,
+ "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
+ positionID = _env->GetFieldID(bufferClass, "position", "I");
+ limitID = _env->GetFieldID(bufferClass, "limit", "I");
+ elementSizeShiftID =
+ _env->GetFieldID(bufferClass, "_elementSizeShift", "I");
+}
+
+static void *
+getPointer(JNIEnv *_env, jobject buffer, jint *remaining)
+{
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ jint offset;
+ void *data;
+
+ position = _env->GetIntField(buffer, positionID);
+ limit = _env->GetIntField(buffer, limitID);
+ elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+ *remaining = (limit - position) << elementSizeShift;
+ pointer = _env->CallStaticLongMethod(nioAccessClass,
+ getBasePointerID, buffer);
+ if (pointer != 0L) {
+ return (void *) (jint) pointer;
+ }
+ return NULL;
+}
+
+class BufferHelper {
+public:
+ BufferHelper(JNIEnv *env, jobject buffer) {
+ mEnv = env;
+ mBuffer = buffer;
+ mData = NULL;
+ mRemaining = 0;
+ }
+
+ bool checkPointer(const char* errorMessage) {
+ if (mBuffer) {
+ mData = getPointer(mEnv, mBuffer, &mRemaining);
+ if (mData == NULL) {
+ mEnv->ThrowNew(gIAEClass, errorMessage);
+ }
+ return mData != NULL;
+ } else {
+ mEnv->ThrowNew(gIAEClass, errorMessage);
+ return false;
+ }
+ }
+
+ inline void* getData() {
+ return mData;
+ }
+
+ inline jint remaining() {
+ return mRemaining;
+ }
+
+private:
+ JNIEnv* mEnv;
+ jobject mBuffer;
+ void* mData;
+ jint mRemaining;
+};
+
+/**
+ * Encode a block of pixels.
+ *
+ * @param in a pointer to a ETC1_DECODED_BLOCK_SIZE array of bytes that represent a
+ * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
+ * value of pixel (x, y).
+ *
+ * @param validPixelMask is a 16-bit mask where bit (1 << (x + y * 4)) indicates whether
+ * the corresponding (x,y) pixel is valid. Invalid pixel color values are ignored when compressing.
+ *
+ * @param out an ETC1 compressed version of the data.
+ *
+ */
+static void etc1_encodeBlock(JNIEnv *env, jclass clazz,
+ jobject in, jint validPixelMask, jobject out) {
+ if (validPixelMask < 0 || validPixelMask > 15) {
+ env->ThrowNew(gIAEClass, "validPixelMask");
+ return;
+ }
+ BufferHelper inB(env, in);
+ BufferHelper outB(env, out);
+ if (inB.checkPointer("in") && outB.checkPointer("out")) {
+ if (inB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
+ env->ThrowNew(gIAEClass, "in's remaining data < DECODED_BLOCK_SIZE");
+ } else if (outB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
+ env->ThrowNew(gIAEClass, "out's remaining data < ENCODED_BLOCK_SIZE");
+ } else {
+ etc1_encode_block((etc1_byte*) inB.getData(), validPixelMask,
+ (etc1_byte*) outB.getData());
+ }
+ }
+}
+
+/**
+ * Decode a block of pixels.
+ *
+ * @param in an ETC1 compressed version of the data.
+ *
+ * @param out a pointer to a ETC_DECODED_BLOCK_SIZE array of bytes that represent a
+ * 4 x 4 square of 3-byte pixels in form R, G, B. Byte (3 * (x + 4 * y) is the R
+ * value of pixel (x, y).
+ */
+static void etc1_decodeBlock(JNIEnv *env, jclass clazz,
+ jobject in, jobject out){
+ BufferHelper inB(env, in);
+ BufferHelper outB(env, out);
+ if (inB.checkPointer("in") && outB.checkPointer("out")) {
+ if (inB.remaining() < ETC1_ENCODED_BLOCK_SIZE) {
+ env->ThrowNew(gIAEClass, "in's remaining data < ENCODED_BLOCK_SIZE");
+ } else if (outB.remaining() < ETC1_DECODED_BLOCK_SIZE) {
+ env->ThrowNew(gIAEClass, "out's remaining data < DECODED_BLOCK_SIZE");
+ } else {
+ etc1_decode_block((etc1_byte*) inB.getData(),
+ (etc1_byte*) outB.getData());
+ }
+ }
+}
+
+/**
+ * Return the size of the encoded image data (does not include size of PKM header).
+ */
+static jint etc1_getEncodedDataSize(JNIEnv *env, jclass clazz,
+ jint width, jint height) {
+ return etc1_get_encoded_data_size(width, height);
+}
+
+/**
+ * Encode an entire image.
+ * @param in pointer to the image data. Formatted such that
+ * pixel (x,y) is at pIn + pixelSize * x + stride * y + redOffset;
+ * @param out pointer to encoded data. Must be large enough to store entire encoded image.
+ */
+static void etc1_encodeImage(JNIEnv *env, jclass clazz,
+ jobject in, jint width, jint height,
+ jint pixelSize, jint stride, jobject out) {
+ if (pixelSize < 2 || pixelSize > 3) {
+ env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3");
+ return;
+ }
+ BufferHelper inB(env, in);
+ BufferHelper outB(env, out);
+ if (inB.checkPointer("in") && outB.checkPointer("out")) {
+ jint imageSize = stride * height;
+ jint encodedImageSize = etc1_get_encoded_data_size(width, height);
+ if (inB.remaining() < imageSize) {
+ env->ThrowNew(gIAEClass, "in's remaining data < image size");
+ } else if (outB.remaining() < encodedImageSize) {
+ env->ThrowNew(gIAEClass, "out's remaining data < encoded image size");
+ } else {
+ int result = etc1_encode_image((etc1_byte*) inB.getData(),
+ width, height, pixelSize,
+ stride,
+ (etc1_byte*) outB.getData());
+ }
+ }
+}
+
+/**
+ * Decode an entire image.
+ * @param in the encoded data.
+ * @param out pointer to the image data. Will be written such that
+ * pixel (x,y) is at pIn + pixelSize * x + stride * y. Must be
+ * large enough to store entire image.
+ */
+static void etc1_decodeImage(JNIEnv *env, jclass clazz,
+ jobject in, jobject out,
+ jint width, jint height,
+ jint pixelSize, jint stride) {
+ if (pixelSize < 2 || pixelSize > 3) {
+ env->ThrowNew(gIAEClass, "pixelSize must be 2 or 3");
+ return;
+ }
+ BufferHelper inB(env, in);
+ BufferHelper outB(env, out);
+ if (inB.checkPointer("in") && outB.checkPointer("out")) {
+ jint imageSize = stride * height;
+ jint encodedImageSize = etc1_get_encoded_data_size(width, height);
+ if (inB.remaining() < encodedImageSize) {
+ env->ThrowNew(gIAEClass, "in's remaining data < encoded image size");
+ } else if (outB.remaining() < imageSize) {
+ env->ThrowNew(gIAEClass, "out's remaining data < image size");
+ } else {
+ int result = etc1_decode_image((etc1_byte*) inB.getData(),
+ (etc1_byte*) outB.getData(),
+ width, height, pixelSize,
+ stride);
+ }
+ }
+}
+
+/**
+ * Format a PKM header
+ */
+static void etc1_formatHeader(JNIEnv *env, jclass clazz,
+ jobject header, jint width, jint height) {
+ BufferHelper headerB(env, header);
+ if (headerB.checkPointer("header") ){
+ if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
+ env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
+ } else {
+ etc1_pkm_format_header((etc1_byte*) headerB.getData(), width, height);
+ }
+ }
+}
+
+/**
+ * Check if a PKM header is correctly formatted.
+ */
+static jboolean etc1_isValid(JNIEnv *env, jclass clazz,
+ jobject header) {
+ jboolean result = false;
+ BufferHelper headerB(env, header);
+ if (headerB.checkPointer("header") ){
+ if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
+ env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
+ } else {
+ result = etc1_pkm_is_valid((etc1_byte*) headerB.getData());
+ }
+ }
+ return result;
+}
+
+/**
+ * Read the image width from a PKM header
+ */
+static jint etc1_getWidth(JNIEnv *env, jclass clazz,
+ jobject header) {
+ jint result = 0;
+ BufferHelper headerB(env, header);
+ if (headerB.checkPointer("header") ){
+ if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
+ env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
+ } else {
+ result = etc1_pkm_get_width((etc1_byte*) headerB.getData());
+ }
+ }
+ return result;
+}
+
+/**
+ * Read the image height from a PKM header
+ */
+static int etc1_getHeight(JNIEnv *env, jclass clazz,
+ jobject header) {
+ jint result = 0;
+ BufferHelper headerB(env, header);
+ if (headerB.checkPointer("header") ){
+ if (headerB.remaining() < ETC_PKM_HEADER_SIZE) {
+ env->ThrowNew(gIAEClass, "header's remaining data < ETC_PKM_HEADER_SIZE");
+ } else {
+ result = etc1_pkm_get_height((etc1_byte*) headerB.getData());
+ }
+ }
+ return result;
+}
+
+/*
* JNI registration
*/
@@ -721,6 +1014,8 @@ lookupClasses(JNIEnv* env) {
env->FindClass("java/lang/IllegalArgumentException"));
gUOEClass = (jclass) env->NewGlobalRef(
env->FindClass("java/lang/UnsupportedOperationException"));
+ gAIOOBEClass = (jclass) env->NewGlobalRef(
+ env->FindClass("java/lang/ArrayIndexOutOfBoundsException"));
}
static JNINativeMethod gMatrixMethods[] = {
@@ -742,6 +1037,18 @@ static JNINativeMethod gUtilsMethods[] = {
{ "native_texSubImage2D", "(IIIILandroid/graphics/Bitmap;II)I", (void*)util_texSubImage2D },
};
+static JNINativeMethod gEtc1Methods[] = {
+ { "encodeBlock", "(Ljava/nio/Buffer;ILjava/nio/Buffer;)V", (void*) etc1_encodeBlock },
+ { "decodeBlock", "(Ljava/nio/Buffer;Ljava/nio/Buffer;)V", (void*) etc1_decodeBlock },
+ { "getEncodedDataSize", "(II)I", (void*) etc1_getEncodedDataSize },
+ { "encodeImage", "(Ljava/nio/Buffer;IIIILjava/nio/Buffer;)V", (void*) etc1_encodeImage },
+ { "decodeImage", "(Ljava/nio/Buffer;Ljava/nio/Buffer;IIII)V", (void*) etc1_decodeImage },
+ { "formatHeader", "(Ljava/nio/Buffer;II)V", (void*) etc1_formatHeader },
+ { "isValid", "(Ljava/nio/Buffer;)Z", (void*) etc1_isValid },
+ { "getWidth", "(Ljava/nio/Buffer;)I", (void*) etc1_getWidth },
+ { "getHeight", "(Ljava/nio/Buffer;)I", (void*) etc1_getHeight },
+};
+
typedef struct _ClassRegistrationInfo {
const char* classPath;
JNINativeMethod* methods;
@@ -752,11 +1059,13 @@ static ClassRegistrationInfo gClasses[] = {
{"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
{"android/opengl/Visibility", gVisiblityMethods, NELEM(gVisiblityMethods)},
{"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
+ {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
};
int register_android_opengl_classes(JNIEnv* env)
{
lookupClasses(env);
+ nativeClassInitBuffer(env);
int result = 0;
for (int i = 0; i < NELEM(gClasses); i++) {
ClassRegistrationInfo* cri = &gClasses[i];
diff --git a/core/jni/android_backup_BackupDataInput.cpp b/core/jni/android_backup_BackupDataInput.cpp
index cf8a8e8..b03dd16 100644
--- a/core/jni/android_backup_BackupDataInput.cpp
+++ b/core/jni/android_backup_BackupDataInput.cpp
@@ -28,7 +28,7 @@ namespace android
// java.io.FileDescriptor
static jfieldID s_descriptorField = 0;
-// android.backup.BackupDataInput$EntityHeader
+// android.app.backup.BackupDataInput$EntityHeader
static jfieldID s_keyField = 0;
static jfieldID s_dataSizeField = 0;
@@ -130,7 +130,7 @@ skipEntityData_native(JNIEnv* env, jobject clazz, int r)
static const JNINativeMethod g_methods[] = {
{ "ctor", "(Ljava/io/FileDescriptor;)I", (void*)ctor_native },
{ "dtor", "(I)V", (void*)dtor_native },
- { "readNextHeader_native", "(ILandroid/backup/BackupDataInput$EntityHeader;)I",
+ { "readNextHeader_native", "(ILandroid/app/backup/BackupDataInput$EntityHeader;)I",
(void*)readNextHeader_native },
{ "readEntityData_native", "(I[BII)I", (void*)readEntityData_native },
{ "skipEntityData_native", "(I)I", (void*)skipEntityData_native },
@@ -148,16 +148,16 @@ int register_android_backup_BackupDataInput(JNIEnv* env)
LOG_FATAL_IF(s_descriptorField == NULL,
"Unable to find descriptor field in java.io.FileDescriptor");
- clazz = env->FindClass("android/backup/BackupDataInput$EntityHeader");
- LOG_FATAL_IF(clazz == NULL, "Unable to find class android.backup.BackupDataInput.EntityHeader");
+ clazz = env->FindClass("android/app/backup/BackupDataInput$EntityHeader");
+ LOG_FATAL_IF(clazz == NULL, "Unable to find class android.app.backup.BackupDataInput.EntityHeader");
s_keyField = env->GetFieldID(clazz, "key", "Ljava/lang/String;");
LOG_FATAL_IF(s_keyField == NULL,
- "Unable to find key field in android.backup.BackupDataInput.EntityHeader");
+ "Unable to find key field in android.app.backup.BackupDataInput.EntityHeader");
s_dataSizeField = env->GetFieldID(clazz, "dataSize", "I");
LOG_FATAL_IF(s_dataSizeField == NULL,
- "Unable to find dataSize field in android.backup.BackupDataInput.EntityHeader");
+ "Unable to find dataSize field in android.app.backup.BackupDataInput.EntityHeader");
- return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupDataInput",
+ return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupDataInput",
g_methods, NELEM(g_methods));
}
diff --git a/core/jni/android_backup_BackupDataOutput.cpp b/core/jni/android_backup_BackupDataOutput.cpp
index ce30aaa..b895d11 100644
--- a/core/jni/android_backup_BackupDataOutput.cpp
+++ b/core/jni/android_backup_BackupDataOutput.cpp
@@ -121,7 +121,7 @@ int register_android_backup_BackupDataOutput(JNIEnv* env)
LOG_FATAL_IF(s_descriptorField == NULL,
"Unable to find descriptor field in java.io.FileDescriptor");
- return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupDataOutput",
+ return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupDataOutput",
g_methods, NELEM(g_methods));
}
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
index 2e3f0b9..26e7d66 100644
--- a/core/jni/android_backup_BackupHelperDispatcher.cpp
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -219,16 +219,16 @@ writeHeader_native(JNIEnv* env, jobject clazz, jobject headerObj, jobject fdObj,
static const JNINativeMethod g_methods[] = {
{ "readHeader_native",
- "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+ "(Landroid/app/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
(void*)readHeader_native },
{ "skipChunk_native",
"(Ljava/io/FileDescriptor;I)I",
(void*)skipChunk_native },
{ "allocateHeader_native",
- "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
+ "(Landroid/app/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;)I",
(void*)allocateHeader_native },
{ "writeHeader_native",
- "(Landroid/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;I)I",
+ "(Landroid/app/backup/BackupHelperDispatcher$Header;Ljava/io/FileDescriptor;I)I",
(void*)writeHeader_native },
};
@@ -242,17 +242,17 @@ int register_android_backup_BackupHelperDispatcher(JNIEnv* env)
LOG_FATAL_IF(s_descriptorField == NULL,
"Unable to find descriptor field in java.io.FileDescriptor");
- clazz = env->FindClass("android/backup/BackupHelperDispatcher$Header");
+ clazz = env->FindClass("android/app/backup/BackupHelperDispatcher$Header");
LOG_FATAL_IF(clazz == NULL,
- "Unable to find class android.backup.BackupHelperDispatcher.Header");
+ "Unable to find class android.app.backup.BackupHelperDispatcher.Header");
s_chunkSizeField = env->GetFieldID(clazz, "chunkSize", "I");
LOG_FATAL_IF(s_chunkSizeField == NULL,
- "Unable to find chunkSize field in android.backup.BackupHelperDispatcher.Header");
+ "Unable to find chunkSize field in android.app.backup.BackupHelperDispatcher.Header");
s_keyPrefixField = env->GetFieldID(clazz, "keyPrefix", "Ljava/lang/String;");
LOG_FATAL_IF(s_keyPrefixField == NULL,
- "Unable to find keyPrefix field in android.backup.BackupHelperDispatcher.Header");
+ "Unable to find keyPrefix field in android.app.backup.BackupHelperDispatcher.Header");
- return AndroidRuntime::registerNativeMethods(env, "android/backup/BackupHelperDispatcher",
+ return AndroidRuntime::registerNativeMethods(env, "android/app/backup/BackupHelperDispatcher",
g_methods, NELEM(g_methods));
}
diff --git a/core/jni/android_backup_FileBackupHelperBase.cpp b/core/jni/android_backup_FileBackupHelperBase.cpp
index 8225a36..0137a06 100644
--- a/core/jni/android_backup_FileBackupHelperBase.cpp
+++ b/core/jni/android_backup_FileBackupHelperBase.cpp
@@ -129,7 +129,7 @@ int register_android_backup_FileBackupHelperBase(JNIEnv* env)
LOG_FATAL_IF(s_descriptorField == NULL,
"Unable to find descriptor field in java.io.FileDescriptor");
- return AndroidRuntime::registerNativeMethods(env, "android/backup/FileBackupHelperBase",
+ return AndroidRuntime::registerNativeMethods(env, "android/app/backup/FileBackupHelperBase",
g_methods, NELEM(g_methods));
}
diff --git a/core/jni/android_bluetooth_HeadsetBase.cpp b/core/jni/android_bluetooth_HeadsetBase.cpp
index 71279b2..b0b0cb8 100644
--- a/core/jni/android_bluetooth_HeadsetBase.cpp
+++ b/core/jni/android_bluetooth_HeadsetBase.cpp
@@ -96,6 +96,13 @@ static int send_line(int fd, const char* line) {
return 0;
}
+static int is_ascii(char *line) {
+ for (;;line++) {
+ if (*line == 0) return 1;
+ if (*line >> 7) return 0;
+ }
+}
+
static const char* get_line(int fd, char *buf, int len, int timeout_ms,
int *err) {
char *bufit=buf;
@@ -125,7 +132,7 @@ again:
return NULL;
}
- while ((int)(bufit - buf) < len)
+ while ((int)(bufit - buf) < (len - 1))
{
errno = 0;
int rc = read(fd, bufit, 1);
@@ -155,8 +162,18 @@ again:
bufit++;
}
- *bufit = '\x0';
- LOG(LOG_INFO, "Bluetooth AT recv", buf);
+ *bufit = NULL;
+
+ // Simple validation. Must be all ASCII.
+ // (we sometimes send non-ASCII UTF-8 in address book, but should
+ // never receive non-ASCII UTF-8).
+ // This was added because of the BMW 2005 E46 which sends binary junk.
+ if (is_ascii(buf)) {
+ LOG(LOG_INFO, "Bluetooth AT recv", buf);
+ } else {
+ LOGW("Ignoring invalid AT command: %s", buf);
+ buf[0] = NULL;
+ }
return buf;
}
@@ -501,7 +518,7 @@ static jstring readNative(JNIEnv *env, jobject obj, jint timeout_ms) {
{
native_data_t *nat = get_native_data(env, obj);
if (nat->rfcomm_connected) {
- char buf[128];
+ char buf[256];
const char *ret = get_line(nat->rfcomm_sock,
buf, sizeof(buf),
timeout_ms,
diff --git a/core/jni/android_bluetooth_ScoSocket.cpp b/core/jni/android_bluetooth_ScoSocket.cpp
index 3afe5f5..94e4409 100644
--- a/core/jni/android_bluetooth_ScoSocket.cpp
+++ b/core/jni/android_bluetooth_ScoSocket.cpp
@@ -37,6 +37,23 @@
#ifdef HAVE_BLUETOOTH
#include <bluetooth/bluetooth.h>
#include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+
+#define MAX_LINE 255
+
+/*
+ * Defines the module strings used in the blacklist file.
+ * These are used by consumers of the blacklist file to see if the line is
+ * used by that module.
+ */
+#define SCO_BLACKLIST_MODULE_NAME "scoSocket"
+
+
+/* Define the type strings used in the blacklist file. */
+#define BLACKLIST_BY_NAME "name"
+#define BLACKLIST_BY_PARTIAL_NAME "partial_name"
+#define BLACKLIST_BY_OUI "vendor_oui"
+
#endif
/* Ideally, blocking I/O on a SCO socket would return when another thread
@@ -67,11 +84,28 @@ static jmethodID method_onClosed;
struct thread_data_t;
static void *work_thread(void *arg);
-static int connect_work(const char *address);
+static int connect_work(const char *address, uint16_t sco_pkt_type);
static int accept_work(int signal_sk);
static void wait_for_close(int sk, int signal_sk);
static void closeNative(JNIEnv *env, jobject object);
+static void parseBlacklist(void);
+static uint16_t getScoType(char *address, const char *name);
+
+#define COMPARE_STRING(key, s) (!strncmp(key, s, strlen(s)))
+
+/* Blacklist data */
+typedef struct scoBlacklist {
+ int fieldType;
+ char *value;
+ uint16_t scoType;
+ struct scoBlacklist *next;
+} scoBlacklist_t;
+
+#define BL_TYPE_NAME 1 // Field type is name string
+
+static scoBlacklist_t *blacklist = NULL;
+
/* shared native data - protected by mutex */
typedef struct {
pthread_mutex_t mutex;
@@ -87,11 +121,144 @@ struct thread_data_t {
bool is_accept; // accept (listening) or connect (outgoing) thread
int signal_sk; // socket for thread to listen for unblock signal
char address[BTADDR_SIZE]; // BT addres as string
+ uint16_t sco_pkt_type; // SCO packet types supported
};
static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
return (native_data_t *)(env->GetIntField(object, field_mNativeData));
}
+
+static uint16_t str2scoType (char *key) {
+ LOGV("%s: key = %s", __FUNCTION__, key);
+ if (COMPARE_STRING(key, "ESCO_HV1"))
+ return ESCO_HV1;
+ if (COMPARE_STRING(key, "ESCO_HV2"))
+ return ESCO_HV2;
+ if (COMPARE_STRING(key, "ESCO_HV3"))
+ return ESCO_HV3;
+ if (COMPARE_STRING(key, "ESCO_EV3"))
+ return ESCO_EV3;
+ if (COMPARE_STRING(key, "ESCO_EV4"))
+ return ESCO_EV4;
+ if (COMPARE_STRING(key, "ESCO_EV5"))
+ return ESCO_EV5;
+ if (COMPARE_STRING(key, "ESCO_2EV3"))
+ return ESCO_2EV3;
+ if (COMPARE_STRING(key, "ESCO_3EV3"))
+ return ESCO_3EV3;
+ if (COMPARE_STRING(key, "ESCO_2EV5"))
+ return ESCO_2EV5;
+ if (COMPARE_STRING(key, "ESCO_3EV5"))
+ return ESCO_3EV5;
+ if (COMPARE_STRING(key, "SCO_ESCO_MASK"))
+ return SCO_ESCO_MASK;
+ if (COMPARE_STRING(key, "EDR_ESCO_MASK"))
+ return EDR_ESCO_MASK;
+ if (COMPARE_STRING(key, "ALL_ESCO_MASK"))
+ return ALL_ESCO_MASK;
+ LOGE("Unknown SCO Type (%s) skipping",key);
+ return 0;
+}
+
+static void parseBlacklist(void) {
+ const char *filename = "/etc/bluetooth/blacklist.conf";
+ char line[MAX_LINE];
+ scoBlacklist_t *list = NULL;
+ scoBlacklist_t *newelem;
+
+ LOGV(__FUNCTION__);
+
+ /* Open file */
+ FILE *fp = fopen(filename, "r");
+ if(!fp) {
+ LOGE("Error(%s)opening blacklist file", strerror(errno));
+ return;
+ }
+
+ while (fgets(line, MAX_LINE, fp) != NULL) {
+ if ((COMPARE_STRING(line, "//")) || (!strcmp(line, "")))
+ continue;
+ char *module = strtok(line,":");
+ if (COMPARE_STRING(module, SCO_BLACKLIST_MODULE_NAME)) {
+ newelem = (scoBlacklist_t *)calloc(1, sizeof(scoBlacklist_t));
+ if (newelem == NULL) {
+ LOGE("%s: out of memory!", __FUNCTION__);
+ return;
+ }
+ // parse line
+ char *type = strtok(NULL, ",");
+ char *valueList = strtok(NULL, ",");
+ char *paramList = strtok(NULL, ",");
+ if (COMPARE_STRING(type, BLACKLIST_BY_NAME)) {
+ // Extract Name from Value list
+ newelem->fieldType = BL_TYPE_NAME;
+ newelem->value = (char *)calloc(1, strlen(valueList));
+ if (newelem->value == NULL) {
+ LOGE("%s: out of memory!", __FUNCTION__);
+ continue;
+ }
+ valueList++; // Skip open quote
+ strncpy(newelem->value, valueList, strlen(valueList) - 1);
+
+ // Get Sco Settings from Parameters
+ char *param = strtok(paramList, ";");
+ uint16_t scoTypes = 0;
+ while (param != NULL) {
+ uint16_t sco;
+ if (param[0] == '-') {
+ param++;
+ sco = str2scoType(param);
+ if (sco != 0)
+ scoTypes &= ~sco;
+ } else if (param[0] == '+') {
+ param++;
+ sco = str2scoType(param);
+ if (sco != 0)
+ scoTypes |= sco;
+ } else if (param[0] == '=') {
+ param++;
+ sco = str2scoType(param);
+ if (sco != 0)
+ scoTypes = sco;
+ } else {
+ LOGE("Invalid SCO type must be =, + or -");
+ }
+ param = strtok(NULL, ";");
+ }
+ newelem->scoType = scoTypes;
+ } else {
+ LOGE("Unknown SCO type entry in Blacklist file");
+ continue;
+ }
+ if (list) {
+ list->next = newelem;
+ list = newelem;
+ } else {
+ blacklist = list = newelem;
+ }
+ LOGI("Entry name = %s ScoTypes = 0x%x", newelem->value,
+ newelem->scoType);
+ }
+ }
+ fclose(fp);
+ return;
+}
+static uint16_t getScoType(char *address, const char *name) {
+ uint16_t ret = 0;
+ scoBlacklist_t *list = blacklist;
+
+ while (list != NULL) {
+ if (list->fieldType == BL_TYPE_NAME) {
+ if (COMPARE_STRING(name, list->value)) {
+ ret = list->scoType;
+ break;
+ }
+ }
+ list = list->next;
+ }
+ LOGI("%s %s - 0x%x", __FUNCTION__, name, ret);
+ return ret;
+}
#endif
static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -104,6 +271,9 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V");
method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V");
method_onClosed = env->GetMethodID(clazz, "onClosed", "()V");
+
+ /* Read the blacklist file in here */
+ parseBlacklist();
#endif
}
@@ -192,7 +362,9 @@ static jboolean acceptNative(JNIEnv *env, jobject object) {
return JNI_FALSE;
}
-static jboolean connectNative(JNIEnv *env, jobject object, jstring address) {
+static jboolean connectNative(JNIEnv *env, jobject object, jstring address,
+ jstring name) {
+
LOGV(__FUNCTION__);
#ifdef HAVE_BLUETOOTH
native_data_t *nat = get_native_data(env, object);
@@ -200,6 +372,7 @@ static jboolean connectNative(JNIEnv *env, jobject object, jstring address) {
pthread_t thread;
struct thread_data_t *data;
const char *c_address;
+ const char *c_name;
pthread_mutex_lock(&nat->mutex);
if (nat->signal_sk != -1) {
@@ -231,6 +404,15 @@ static jboolean connectNative(JNIEnv *env, jobject object, jstring address) {
env->ReleaseStringUTFChars(address, c_address);
data->is_accept = false;
+ if (name == NULL) {
+ LOGE("%s: Null pointer passed in for device name", __FUNCTION__);
+ data->sco_pkt_type = 0;
+ } else {
+ c_name = env->GetStringUTFChars(name, NULL);
+ /* See if this device is in the black list */
+ data->sco_pkt_type = getScoType(data->address, c_name);
+ env->ReleaseStringUTFChars(name, c_name);
+ }
if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
return JNI_FALSE;
@@ -282,7 +464,7 @@ static void *work_thread(void *arg) {
sk = accept_work(data->signal_sk);
LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object);
} else {
- sk = connect_work(data->address);
+ sk = connect_work(data->address, data->sco_pkt_type);
}
/* callback with connection result */
@@ -426,7 +608,7 @@ error:
return -1;
}
-static int connect_work(const char *address) {
+static int connect_work(const char *address, uint16_t sco_pkt_type) {
LOGV(__FUNCTION__);
struct sockaddr_sco addr;
int sk = -1;
@@ -449,6 +631,7 @@ static int connect_work(const char *address) {
memset(&addr, 0, sizeof(addr));
addr.sco_family = AF_BLUETOOTH;
get_bdaddr(address, &addr.sco_bdaddr);
+ addr.sco_pkt_type = sco_pkt_type;
LOGI("Connecting to socket");
while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
if (errno != EINTR) {
@@ -493,7 +676,7 @@ static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "()V", (void *)initNative},
{"destroyNative", "()V", (void *)destroyNative},
- {"connectNative", "(Ljava/lang/String;)Z", (void *)connectNative},
+ {"connectNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)connectNative},
{"acceptNative", "()Z", (void *)acceptNative},
{"closeNative", "()V", (void *)closeNative},
};
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index ef9b66b..378bb6f 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -88,6 +88,8 @@ struct event_loop_native_data_t {
int envVer;
/* reference to our java self */
jobject me;
+ /* flag to indicate if the event loop thread is running */
+ bool running;
};
struct _Properties {
diff --git a/core/jni/android_database_SQLiteCompiledSql.cpp b/core/jni/android_database_SQLiteCompiledSql.cpp
new file mode 100644
index 0000000..8d1c39e
--- /dev/null
+++ b/core/jni/android_database_SQLiteCompiledSql.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2006-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Cursor"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sqlite3_exception.h"
+
+
+namespace android {
+
+static jfieldID gHandleField;
+static jfieldID gStatementField;
+
+
+#define GET_STATEMENT(env, object) \
+ (sqlite3_stmt *)env->GetIntField(object, gStatementField)
+#define GET_HANDLE(env, object) \
+ (sqlite3 *)env->GetIntField(object, gHandleField)
+
+
+sqlite3_stmt * compile(JNIEnv* env, jobject object,
+ sqlite3 * handle, jstring sqlString)
+{
+ int err;
+ jchar const * sql;
+ jsize sqlLen;
+ sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+ // Make sure not to leak the statement if it already exists
+ if (statement != NULL) {
+ sqlite3_finalize(statement);
+ env->SetIntField(object, gStatementField, 0);
+ }
+
+ // Compile the SQL
+ sql = env->GetStringChars(sqlString, NULL);
+ sqlLen = env->GetStringLength(sqlString);
+ err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
+ env->ReleaseStringChars(sqlString, sql);
+
+ if (err == SQLITE_OK) {
+ // Store the statement in the Java object for future calls
+ LOGV("Prepared statement %p on %p", statement, handle);
+ env->SetIntField(object, gStatementField, (int)statement);
+ return statement;
+ } else {
+ // Error messages like 'near ")": syntax error' are not
+ // always helpful enough, so construct an error string that
+ // includes the query itself.
+ const char *query = env->GetStringUTFChars(sqlString, NULL);
+ char *message = (char*) malloc(strlen(query) + 50);
+ if (message) {
+ strcpy(message, ", while compiling: "); // less than 50 chars
+ strcat(message, query);
+ }
+ env->ReleaseStringUTFChars(sqlString, query);
+ throw_sqlite3_exception(env, handle, message);
+ free(message);
+ return NULL;
+ }
+}
+
+static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
+{
+ compile(env, object, GET_HANDLE(env, object), sqlString);
+}
+
+static void native_finalize(JNIEnv* env, jobject object)
+{
+ int err;
+ sqlite3_stmt * statement = GET_STATEMENT(env, object);
+
+ if (statement != NULL) {
+ sqlite3_finalize(statement);
+ env->SetIntField(object, gStatementField, 0);
+ }
+}
+
+static JNINativeMethod sMethods[] =
+{
+ /* name, signature, funcPtr */
+ {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
+ {"native_finalize", "()V", (void *)native_finalize},
+};
+
+int register_android_database_SQLiteCompiledSql(JNIEnv * env)
+{
+ jclass clazz;
+
+ clazz = env->FindClass("android/database/sqlite/SQLiteCompiledSql");
+ if (clazz == NULL) {
+ LOGE("Can't find android/database/sqlite/SQLiteCompiledSql");
+ return -1;
+ }
+
+ gHandleField = env->GetFieldID(clazz, "nHandle", "I");
+ gStatementField = env->GetFieldID(clazz, "nStatement", "I");
+
+ if (gHandleField == NULL || gStatementField == NULL) {
+ LOGE("Error locating fields");
+ return -1;
+ }
+
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/database/sqlite/SQLiteCompiledSql", sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index f20dadb..36234a9 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -63,6 +63,36 @@ enum {
static jfieldID offset_db_handle;
+static char *createStr(const char *path) {
+ int len = strlen(path);
+ char *str = (char *)malloc(len + 1);
+ strncpy(str, path, len);
+ str[len] = NULL;
+ return str;
+}
+
+static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
+ // skip printing this message if it is due to certain types of errors
+ if (iErrCode == SQLITE_CONSTRAINT) return;
+ LOGI("sqlite returned: error code = %d, msg = %s\n", iErrCode, zMsg);
+}
+
+// register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
+static void registerLoggingFunc(const char *path) {
+ static bool loggingFuncSet = false;
+ if (loggingFuncSet) {
+ return;
+ }
+
+ LOGV("Registering sqlite logging func \n");
+ int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path));
+ if (err != SQLITE_OK) {
+ LOGE("sqlite_config failed error_code = %d. THIS SHOULD NEVER occur.\n", err);
+ return;
+ }
+ loggingFuncSet = true;
+}
+
/* public native void dbopen(String path, int flags, String locale); */
static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags)
{
@@ -72,6 +102,9 @@ static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags)
char const * path8 = env->GetStringUTFChars(pathString, NULL);
int sqliteFlags;
+ // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
+ registerLoggingFunc(path8);
+
// convert our flags into the sqlite flags
if (flags & CREATE_IF_NECESSARY) {
sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
@@ -143,12 +176,57 @@ done:
if (handle != NULL) sqlite3_close(handle);
}
+static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName) {
+ char const *path = env->GetStringUTFChars(databaseName, NULL);
+ if (path == NULL) {
+ LOGE("Failure in getDatabaseName(). VM ran out of memory?\n");
+ return NULL; // VM would have thrown OutOfMemoryError
+ }
+ char *dbNameStr = createStr(path);
+ env->ReleaseStringUTFChars(databaseName, path);
+ return dbNameStr;
+}
+
+static void sqlTrace(void *databaseName, const char *sql) {
+ LOGI("sql_statement|%s|%s\n", (char *)databaseName, sql);
+}
+
+/* public native void enableSqlTracing(); */
+static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName));
+}
+
+static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
+ double d = tm/1000000.0;
+ LOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql);
+}
+
+/* public native void enableSqlProfiling(); */
+static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName));
+}
+
+
/* public native void close(); */
static void dbclose(JNIEnv* env, jobject object)
{
sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
if (handle != NULL) {
+ // release the memory associated with the traceFuncArg in enableSqlTracing function
+ void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL);
+ if (traceFuncArg != NULL) {
+ free(traceFuncArg);
+ }
+ // release the memory associated with the traceFuncArg in enableSqlProfiling function
+ traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL);
+ if (traceFuncArg != NULL) {
+ free(traceFuncArg);
+ }
LOGV("Closing database: handle=%p\n", handle);
int result = sqlite3_close(handle);
if (result == SQLITE_OK) {
@@ -229,6 +307,16 @@ static jint lastChangeCount(JNIEnv* env, jobject object)
return sqlite3_changes(handle);
}
+/* native int native_getDbLookaside(); */
+static jint native_getDbLookaside(JNIEnv* env, jobject object)
+{
+ sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+ int pCur = -1;
+ int unused;
+ sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0);
+ return pCur;
+}
+
/* set locale in the android_metadata table, install localized collators, and rebuild indexes */
static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
{
@@ -291,7 +379,7 @@ static void native_setLocale(JNIEnv* env, jobject object, jstring localeString,
if (err != SQLITE_OK) {
LOGE("register_localized_collators() failed setting locale\n");
throw_sqlite3_exception(env, handle);
- goto done;
+ goto rollback;
}
err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
@@ -339,7 +427,9 @@ static void native_setLocale(JNIEnv* env, jobject object, jstring localeString,
}
rollback:
- sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+ if (err != SQLITE_OK) {
+ sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+ }
done:
if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8);
@@ -358,10 +448,13 @@ static JNINativeMethod sMethods[] =
/* name, signature, funcPtr */
{"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
{"dbclose", "()V", (void *)dbclose},
+ {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing},
+ {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling},
{"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL},
{"lastInsertRow", "()J", (void *)lastInsertRow},
{"lastChangeCount", "()I", (void *)lastChangeCount},
{"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
+ {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
{"releaseMemory", "()I", (void *)native_releaseMemory},
};
@@ -413,7 +506,7 @@ void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* messa
if (errcode == SQLITE_DONE) {
throw_sqlite3_exception(env, errcode, NULL, message);
} else {
- char temp[20];
+ char temp[21];
sprintf(temp, "error code %d", errcode);
throw_sqlite3_exception(env, errcode, temp, message);
}
diff --git a/core/jni/android_database_SQLiteDebug.cpp b/core/jni/android_database_SQLiteDebug.cpp
index 916df35..873b2a1 100644
--- a/core/jni/android_database_SQLiteDebug.cpp
+++ b/core/jni/android_database_SQLiteDebug.cpp
@@ -29,36 +29,28 @@
// From mem_mspace.c in libsqlite
extern "C" mspace sqlite3_get_mspace();
-// From sqlite.c, hacked in for Android
-extern "C" void sqlite3_get_pager_stats(sqlite3_int64 * totalBytesOut,
- sqlite3_int64 * referencedBytesOut,
- sqlite3_int64 * dbBytesOut,
- int * numPagersOut);
-
namespace android {
-static jfieldID gTotalBytesField;
-static jfieldID gReferencedBytesField;
-static jfieldID gDbBytesField;
-static jfieldID gNumPagersField;
+static jfieldID gMemoryUsedField;
+static jfieldID gPageCacheOverfloField;
+static jfieldID gLargestMemAllocField;
#define USE_MSPACE 0
static void getPagerStats(JNIEnv *env, jobject clazz, jobject statsObj)
{
- sqlite3_int64 totalBytes;
- sqlite3_int64 referencedBytes;
- sqlite3_int64 dbBytes;
- int numPagers;
-
- sqlite3_get_pager_stats(&totalBytes, &referencedBytes, &dbBytes,
- &numPagers);
-
- env->SetLongField(statsObj, gTotalBytesField, totalBytes);
- env->SetLongField(statsObj, gReferencedBytesField, referencedBytes);
- env->SetLongField(statsObj, gDbBytesField, dbBytes);
- env->SetIntField(statsObj, gNumPagersField, numPagers);
+ int memoryUsed;
+ int pageCacheOverflo;
+ int largestMemAlloc;
+ int unused;
+
+ sqlite3_status(SQLITE_STATUS_MEMORY_USED, &memoryUsed, &unused, 0);
+ sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &unused, &largestMemAlloc, 0);
+ sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheOverflo, &unused, 0);
+ env->SetIntField(statsObj, gMemoryUsedField, memoryUsed);
+ env->SetIntField(statsObj, gPageCacheOverfloField, pageCacheOverflo);
+ env->SetIntField(statsObj, gLargestMemAllocField, largestMemAlloc);
}
static jlong getHeapSize(JNIEnv *env, jobject clazz)
@@ -213,27 +205,21 @@ int register_android_database_SQLiteDebug(JNIEnv *env)
return -1;
}
- gTotalBytesField = env->GetFieldID(clazz, "totalBytes", "J");
- if (gTotalBytesField == NULL) {
- LOGE("Can't find totalBytes");
- return -1;
- }
-
- gReferencedBytesField = env->GetFieldID(clazz, "referencedBytes", "J");
- if (gReferencedBytesField == NULL) {
- LOGE("Can't find referencedBytes");
+ gMemoryUsedField = env->GetFieldID(clazz, "memoryUsed", "I");
+ if (gMemoryUsedField == NULL) {
+ LOGE("Can't find memoryUsed");
return -1;
}
- gDbBytesField = env->GetFieldID(clazz, "databaseBytes", "J");
- if (gDbBytesField == NULL) {
- LOGE("Can't find databaseBytes");
+ gLargestMemAllocField = env->GetFieldID(clazz, "largestMemAlloc", "I");
+ if (gLargestMemAllocField == NULL) {
+ LOGE("Can't find largestMemAlloc");
return -1;
}
- gNumPagersField = env->GetFieldID(clazz, "numPagers", "I");
- if (gNumPagersField == NULL) {
- LOGE("Can't find numPagers");
+ gPageCacheOverfloField = env->GetFieldID(clazz, "pageCacheOverflo", "I");
+ if (gPageCacheOverfloField == NULL) {
+ LOGE("Can't find pageCacheOverflo");
return -1;
}
diff --git a/core/jni/android_database_SQLiteProgram.cpp b/core/jni/android_database_SQLiteProgram.cpp
index 7bda004..c247bbd 100644
--- a/core/jni/android_database_SQLiteProgram.cpp
+++ b/core/jni/android_database_SQLiteProgram.cpp
@@ -43,52 +43,12 @@ static jfieldID gStatementField;
#define GET_HANDLE(env, object) \
(sqlite3 *)env->GetIntField(object, gHandleField)
-
-sqlite3_stmt * compile(JNIEnv* env, jobject object,
- sqlite3 * handle, jstring sqlString)
-{
- int err;
- jchar const * sql;
- jsize sqlLen;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- // Make sure not to leak the statement if it already exists
- if (statement != NULL) {
- sqlite3_finalize(statement);
- env->SetIntField(object, gStatementField, 0);
- }
-
- // Compile the SQL
- sql = env->GetStringChars(sqlString, NULL);
- sqlLen = env->GetStringLength(sqlString);
- err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
- env->ReleaseStringChars(sqlString, sql);
-
- if (err == SQLITE_OK) {
- // Store the statement in the Java object for future calls
- LOGV("Prepared statement %p on %p", statement, handle);
- env->SetIntField(object, gStatementField, (int)statement);
- return statement;
- } else {
- // Error messages like 'near ")": syntax error' are not
- // always helpful enough, so construct an error string that
- // includes the query itself.
- const char *query = env->GetStringUTFChars(sqlString, NULL);
- char *message = (char*) malloc(strlen(query) + 50);
- if (message) {
- strcpy(message, ", while compiling: "); // less than 50 chars
- strcat(message, query);
- }
- env->ReleaseStringUTFChars(sqlString, query);
- throw_sqlite3_exception(env, handle, message);
- free(message);
- return NULL;
- }
-}
-
static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
{
- compile(env, object, GET_HANDLE(env, object), sqlString);
+ char buf[65];
+ strcpy(buf, "android_database_SQLiteProgram->native_compile() not implemented");
+ throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+ return;
}
static void native_bind_null(JNIEnv* env, jobject object,
@@ -164,7 +124,7 @@ static void native_bind_blob(JNIEnv* env, jobject object,
jsize sqlLen;
sqlite3_stmt * statement= GET_STATEMENT(env, object);
- jint len = env->GetArrayLength(value);
+ jint len = env->GetArrayLength(value);
jbyte * bytes = env->GetByteArrayElements(value, NULL);
err = sqlite3_bind_blob(statement, index, bytes, len, SQLITE_TRANSIENT);
@@ -192,27 +152,22 @@ static void native_clear_bindings(JNIEnv* env, jobject object)
static void native_finalize(JNIEnv* env, jobject object)
{
- int err;
- sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
- if (statement != NULL) {
- sqlite3_finalize(statement);
- env->SetIntField(object, gStatementField, 0);
- }
+ char buf[66];
+ strcpy(buf, "android_database_SQLiteProgram->native_finalize() not implemented");
+ throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
+ return;
}
static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
- {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
{"native_bind_null", "(I)V", (void *)native_bind_null},
{"native_bind_long", "(IJ)V", (void *)native_bind_long},
{"native_bind_double", "(ID)V", (void *)native_bind_double},
{"native_bind_string", "(ILjava/lang/String;)V", (void *)native_bind_string},
- {"native_bind_blob", "(I[B)V", (void *)native_bind_blob},
+ {"native_bind_blob", "(I[B)V", (void *)native_bind_blob},
{"native_clear_bindings", "()V", (void *)native_clear_bindings},
- {"native_finalize", "()V", (void *)native_finalize},
};
int register_android_database_SQLiteProgram(JNIEnv * env)
diff --git a/core/jni/android_graphics_PixelFormat.cpp b/core/jni/android_graphics_PixelFormat.cpp
index 0643622..5b8363c 100644
--- a/core/jni/android_graphics_PixelFormat.cpp
+++ b/core/jni/android_graphics_PixelFormat.cpp
@@ -48,11 +48,35 @@ static void android_graphics_getPixelFormatInfo(
JNIEnv* env, jobject clazz, jint format, jobject pixelFormatObject)
{
PixelFormatInfo info;
- status_t err = getPixelFormatInfo(format, &info);
+ status_t err;
+
+ // we need this for backward compatibility with PixelFormat's
+ // deprecated constants
+ switch (format) {
+ case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 16;
+ goto done;
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 12;
+ goto done;
+ case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ // defined as the bytes per pixel of the Y plane
+ info.bytesPerPixel = 1;
+ info.bitsPerPixel = 16;
+ goto done;
+ }
+
+ err = getPixelFormatInfo(format, &info);
if (err < 0) {
doThrow(env, "java/lang/IllegalArgumentException");
return;
}
+
+done:
env->SetIntField(pixelFormatObject, offsets.bytesPerPixel, info.bytesPerPixel);
env->SetIntField(pixelFormatObject, offsets.bitsPerPixel, info.bitsPerPixel);
}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index d57e526..b85466b 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -25,8 +25,8 @@
#include <utils/Vector.h>
-#include <ui/Surface.h>
-#include <ui/Camera.h>
+#include <surfaceflinger/Surface.h>
+#include <camera/Camera.h>
#include <binder/IMemory.h>
using namespace android;
@@ -168,7 +168,7 @@ void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int
}
if(mCallbackBuffers.isEmpty()) {
- LOGW("Out of buffers, clearing callback!");
+ LOGV("Out of buffers, clearing callback!");
mCamera->setPreviewCallbackFlags(FRAME_CALLBACK_FLAG_NOOP);
mManualCameraCallbackSet = false;
@@ -220,7 +220,7 @@ void JNICameraContext::postData(int32_t msgType, const sp<IMemory>& dataPtr)
break;
default:
// TODO: Change to LOGV
- LOGD("dataCallback(%d, %p)", msgType, dataPtr.get());
+ LOGV("dataCallback(%d, %p)", msgType, dataPtr.get());
copyAndPost(env, dataPtr, msgType);
break;
}
@@ -328,7 +328,7 @@ static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj
static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
{
// TODO: Change to LOGV
- LOGD("release camera");
+ LOGV("release camera");
JNICameraContext* context = NULL;
sp<Camera> camera;
{
@@ -526,18 +526,23 @@ static void android_hardware_Camera_unlock(JNIEnv *env, jobject thiz)
static void android_hardware_Camera_startSmoothZoom(JNIEnv *env, jobject thiz, jint value)
{
- LOGD("startSmoothZoom");
+ LOGV("startSmoothZoom");
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return;
- if (camera->sendCommand(CAMERA_CMD_START_SMOOTH_ZOOM, value, 0) != NO_ERROR) {
+ status_t rc = camera->sendCommand(CAMERA_CMD_START_SMOOTH_ZOOM, value, 0);
+ if (rc == BAD_VALUE) {
+ char msg[64];
+ sprintf(msg, "invalid zoom value=%d", value);
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ } else if (rc != NO_ERROR) {
jniThrowException(env, "java/lang/RuntimeException", "start smooth zoom failed");
}
}
static void android_hardware_Camera_stopSmoothZoom(JNIEnv *env, jobject thiz)
{
- LOGD("stopSmoothZoom");
+ LOGV("stopSmoothZoom");
sp<Camera> camera = get_native_camera(env, thiz, NULL);
if (camera == 0) return;
@@ -546,6 +551,18 @@ static void android_hardware_Camera_stopSmoothZoom(JNIEnv *env, jobject thiz)
}
}
+static void android_hardware_Camera_setDisplayOrientation(JNIEnv *env, jobject thiz,
+ jint value)
+{
+ LOGV("setDisplayOrientation");
+ sp<Camera> camera = get_native_camera(env, thiz, NULL);
+ if (camera == 0) return;
+
+ if (camera->sendCommand(CAMERA_CMD_SET_DISPLAY_ORIENTATION, value, 0) != NO_ERROR) {
+ jniThrowException(env, "java/lang/RuntimeException", "set display orientation failed");
+ }
+}
+
//-------------------------------------------------
static JNINativeMethod camMethods[] = {
@@ -603,6 +620,9 @@ static JNINativeMethod camMethods[] = {
{ "stopSmoothZoom",
"()V",
(void *)android_hardware_Camera_stopSmoothZoom },
+ { "setDisplayOrientation",
+ "(I)V",
+ (void *)android_hardware_Camera_setDisplayOrientation },
};
struct field {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 3e27978..9a90b72 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -16,7 +16,6 @@
#define LOG_TAG "SensorManager"
-#define LOG_NDEBUG 0
#include "utils/Log.h"
#include <hardware/sensors.h>
diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/core/jni/android_location_GpsLocationProvider.cpp
index f845878..f60fe6d 100755
--- a/core/jni/android_location_GpsLocationProvider.cpp
+++ b/core/jni/android_location_GpsLocationProvider.cpp
@@ -41,7 +41,9 @@ static jmethodID method_reportNiNotification;
static const GpsInterface* sGpsInterface = NULL;
static const GpsXtraInterface* sGpsXtraInterface = NULL;
static const AGpsInterface* sAGpsInterface = NULL;
+static const GpsPrivacyInterface* sGpsPrivacyInterface = NULL;
static const GpsNiInterface* sGpsNiInterface = NULL;
+static const GpsDebugInterface* sGpsDebugInterface = NULL;
// data written to by GPS callbacks
static GpsLocation sGpsLocation;
@@ -57,7 +59,7 @@ struct NmeaSentence {
GpsUtcTime timestamp;
char nmea[NMEA_SENTENCE_LENGTH];
};
-static NmeaSentence sNmeaBuffer[NMEA_SENTENCE_LENGTH];
+static NmeaSentence sNmeaBuffer[NMEA_SENTENCE_COUNT];
static int mNmeaSentenceCount = 0;
// a copy of the data shared by android_location_GpsLocationProvider_wait_for_event
@@ -66,7 +68,7 @@ static GpsLocation sGpsLocationCopy;
static GpsStatus sGpsStatusCopy;
static GpsSvStatus sGpsSvStatusCopy;
static AGpsStatus sAGpsStatusCopy;
-static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_LENGTH];
+static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_COUNT];
static GpsNiNotification sGpsNiNotificationCopy;
enum CallbackType {
@@ -222,15 +224,30 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
sAGpsInterface->init(&sAGpsCallbacks);
if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
+ sGpsNiInterface->init(&sGpsNiCallbacks);
+
+ // Clear privacy lock while enabled
+ if (!sGpsPrivacyInterface)
+ sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE);
+ if (sGpsPrivacyInterface)
+ sGpsPrivacyInterface->set_privacy_lock(0);
+
+ if (!sGpsDebugInterface)
+ sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
return true;
}
static void android_location_GpsLocationProvider_disable(JNIEnv* env, jobject obj)
{
+ // Enable privacy lock while disabled
+ if (!sGpsPrivacyInterface)
+ sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE);
+ if (sGpsPrivacyInterface)
+ sGpsPrivacyInterface->set_privacy_lock(1);
+
pthread_mutex_lock(&sEventMutex);
sPendingCallbacks |= kDisableRequest;
pthread_cond_signal(&sEventCond);
@@ -472,11 +489,24 @@ static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jo
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- if (sGpsNiInterface) {
- sGpsNiInterface->respond(notifId, response);
- }
+ if (!sGpsNiInterface)
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ if (sGpsNiInterface)
+ sGpsNiInterface->respond(notifId, response);
+}
+
+static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
+{
+ jstring result = NULL;
+ if (sGpsDebugInterface) {
+ const size_t maxLength = 2047;
+ char buffer[maxLength+1];
+ size_t length = sGpsDebugInterface->get_internal_state(buffer, maxLength);
+ if (length > maxLength) length = maxLength;
+ buffer[length] = 0;
+ result = env->NewStringUTF(buffer);
+ }
+ return result;
}
static JNINativeMethod sMethods[] = {
@@ -501,6 +531,7 @@ static JNINativeMethod sMethods[] = {
{"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
{"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},
{"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
+ {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
};
int register_android_location_GpsLocationProvider(JNIEnv* env)
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index d7485ae..17f5daf 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -55,6 +55,8 @@ struct audiorecord_callback_cookie {
jobject audioRecord_ref;
};
+Mutex sLock;
+
// ----------------------------------------------------------------------------
#define AUDIORECORD_SUCCESS 0
@@ -255,12 +257,21 @@ android_media_AudioRecord_stop(JNIEnv *env, jobject thiz)
// ----------------------------------------------------------------------------
-static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) {
-
- // delete the AudioRecord object
+static void android_media_AudioRecord_release(JNIEnv *env, jobject thiz) {
+
+ // serialize access. Ugly, but functional.
+ Mutex::Autolock lock(&sLock);
AudioRecord *lpRecorder =
(AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
+ audiorecord_callback_cookie *lpCookie = (audiorecord_callback_cookie *)env->GetIntField(
+ thiz, javaAudioRecordFields.nativeCallbackCookie);
+ // reset the native resources in the Java object so any attempt to access
+ // them after a call to release fails.
+ env->SetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, 0);
+ env->SetIntField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
+
+ // delete the AudioRecord object
if (lpRecorder) {
LOGV("About to delete lpRecorder: %x\n", (int)lpRecorder);
lpRecorder->stop();
@@ -268,27 +279,18 @@ static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) {
}
// delete the callback information
- audiorecord_callback_cookie *lpCookie = (audiorecord_callback_cookie *)env->GetIntField(
- thiz, javaAudioRecordFields.nativeCallbackCookie);
if (lpCookie) {
LOGV("deleting lpCookie: %x\n", (int)lpCookie);
env->DeleteGlobalRef(lpCookie->audioRecord_class);
env->DeleteGlobalRef(lpCookie->audioRecord_ref);
delete lpCookie;
}
-
}
// ----------------------------------------------------------------------------
-static void android_media_AudioRecord_release(JNIEnv *env, jobject thiz) {
-
- // do everything a call to finalize would
- android_media_AudioRecord_finalize(env, thiz);
- // + reset the native resources in the Java object so any attempt to access
- // them after a call to release fails.
- env->SetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj, 0);
- env->SetIntField(thiz, javaAudioRecordFields.nativeCallbackCookie, 0);
+static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) {
+ android_media_AudioRecord_release(env, thiz);
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3d8d296..3995026 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -64,10 +64,10 @@ android_media_AudioSystem_isMicrophoneMuted(JNIEnv *env, jobject thiz)
}
static jboolean
-android_media_AudioSystem_isMusicActive(JNIEnv *env, jobject thiz)
+android_media_AudioSystem_isStreamActive(JNIEnv *env, jobject thiz, jint stream)
{
bool state = false;
- AudioSystem::isMusicActive(&state);
+ AudioSystem::isStreamActive(stream, &state);
return state;
}
@@ -195,7 +195,7 @@ static JNINativeMethod gMethods[] = {
{"getParameters", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_AudioSystem_getParameters},
{"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone},
{"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted},
- {"isMusicActive", "()Z", (void *)android_media_AudioSystem_isMusicActive},
+ {"isStreamActive", "(I)Z", (void *)android_media_AudioSystem_isStreamActive},
{"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
{"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState},
{"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp
index f14b9fa..e58794b 100644
--- a/core/jni/android_net_LocalSocketImpl.cpp
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -35,8 +35,6 @@
#include <cutils/sockets.h>
#include <netinet/tcp.h>
-#include <cutils/properties.h>
-#include <cutils/adb_networking.h>
namespace android {
diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp
new file mode 100644
index 0000000..ff46bdd
--- /dev/null
+++ b/core/jni/android_net_TrafficStats.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TrafficStats"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/logger.h>
+#include <jni.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+
+namespace android {
+
+// Returns an ASCII decimal number read from the specified file, -1 on error.
+static jlong readNumber(char const* filename) {
+#ifdef HAVE_ANDROID_OS
+ char buf[80];
+ int fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ if (errno != ENOENT) LOGE("Can't open %s: %s", filename, strerror(errno));
+ return -1;
+ }
+
+ int len = read(fd, buf, sizeof(buf) - 1);
+ if (len < 0) {
+ LOGE("Can't read %s: %s", filename, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ buf[len] = '\0';
+ return atoll(buf);
+#else // Simulator
+ return -1;
+#endif
+}
+
+// Return the number from the first file which exists and contains data
+static jlong tryBoth(char const* a, char const* b) {
+ jlong num = readNumber(a);
+ return num >= 0 ? num : readNumber(b);
+}
+
+// Returns the sum of numbers from the specified path under /sys/class/net/*,
+// -1 if no such file exists.
+static jlong readTotal(char const* suffix) {
+#ifdef HAVE_ANDROID_OS
+ char filename[PATH_MAX] = "/sys/class/net/";
+ DIR *dir = opendir(filename);
+ if (dir == NULL) {
+ LOGE("Can't list %s: %s", filename, strerror(errno));
+ return -1;
+ }
+
+ int len = strlen(filename);
+ jlong total = -1;
+ while (struct dirent *entry = readdir(dir)) {
+ // Skip ., .., and localhost interfaces.
+ if (entry->d_name[0] != '.' && strncmp(entry->d_name, "lo", 2) != 0) {
+ strlcpy(filename + len, entry->d_name, sizeof(filename) - len);
+ strlcat(filename, suffix, sizeof(filename));
+ jlong num = readNumber(filename);
+ if (num >= 0) total = total < 0 ? num : total + num;
+ }
+ }
+
+ closedir(dir);
+ return total;
+#else // Simulator
+ return -1;
+#endif
+}
+
+// Mobile stats get accessed a lot more often than total stats.
+// Note the individual files can come and go at runtime, so we check
+// each file every time (rather than caching which ones exist).
+
+static jlong getMobileTxPackets(JNIEnv* env, jobject clazz) {
+ return tryBoth(
+ "/sys/class/net/rmnet0/statistics/tx_packets",
+ "/sys/class/net/ppp0/statistics/tx_packets");
+}
+
+static jlong getMobileRxPackets(JNIEnv* env, jobject clazz) {
+ return tryBoth(
+ "/sys/class/net/rmnet0/statistics/rx_packets",
+ "/sys/class/net/ppp0/statistics/rx_packets");
+}
+
+static jlong getMobileTxBytes(JNIEnv* env, jobject clazz) {
+ return tryBoth(
+ "/sys/class/net/rmnet0/statistics/tx_bytes",
+ "/sys/class/net/ppp0/statistics/tx_bytes");
+}
+
+static jlong getMobileRxBytes(JNIEnv* env, jobject clazz) {
+ return tryBoth(
+ "/sys/class/net/rmnet0/statistics/rx_bytes",
+ "/sys/class/net/ppp0/statistics/rx_bytes");
+}
+
+// Total stats are read less often, so we're willing to put up
+// with listing the directory and concatenating filenames.
+
+static jlong getTotalTxPackets(JNIEnv* env, jobject clazz) {
+ return readTotal("/statistics/tx_packets");
+}
+
+static jlong getTotalRxPackets(JNIEnv* env, jobject clazz) {
+ return readTotal("/statistics/rx_packets");
+}
+
+static jlong getTotalTxBytes(JNIEnv* env, jobject clazz) {
+ return readTotal("/statistics/tx_bytes");
+}
+
+static jlong getTotalRxBytes(JNIEnv* env, jobject clazz) {
+ return readTotal("/statistics/rx_bytes");
+}
+
+// Per-UID stats require reading from a constructed filename.
+
+static jlong getUidRxBytes(JNIEnv* env, jobject clazz, jint uid) {
+ char filename[80];
+ sprintf(filename, "/proc/uid_stat/%d/tcp_rcv", uid);
+ return readNumber(filename);
+}
+
+static jlong getUidTxBytes(JNIEnv* env, jobject clazz, jint uid) {
+ char filename[80];
+ sprintf(filename, "/proc/uid_stat/%d/tcp_snd", uid);
+ return readNumber(filename);
+}
+
+static JNINativeMethod gMethods[] = {
+ {"getMobileTxPackets", "()J", (void*) getMobileTxPackets},
+ {"getMobileRxPackets", "()J", (void*) getMobileRxPackets},
+ {"getMobileTxBytes", "()J", (void*) getMobileTxBytes},
+ {"getMobileRxBytes", "()J", (void*) getMobileRxBytes},
+ {"getTotalTxPackets", "()J", (void*) getTotalTxPackets},
+ {"getTotalRxPackets", "()J", (void*) getTotalRxPackets},
+ {"getTotalTxBytes", "()J", (void*) getTotalTxBytes},
+ {"getTotalRxBytes", "()J", (void*) getTotalRxBytes},
+ {"getUidTxBytes", "(I)J", (void*) getUidTxBytes},
+ {"getUidRxBytes", "(I)J", (void*) getUidRxBytes},
+};
+
+int register_android_net_TrafficStats(JNIEnv* env) {
+ return AndroidRuntime::registerNativeMethods(env, "android/net/TrafficStats",
+ gMethods, NELEM(gMethods));
+}
+
+}
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 38f3fda..46000c9 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -20,6 +20,7 @@
#include <utils/misc.h>
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
+#include <utils/String16.h>
#include "wifi.h"
@@ -92,7 +93,8 @@ static jstring doStringCommand(JNIEnv *env, const char *cmd)
if (doCommand(cmd, reply, sizeof(reply)) != 0) {
return env->NewStringUTF(NULL);
} else {
- return env->NewStringUTF(reply);
+ String16 str((char *)reply);
+ return env->NewString((const jchar *)str.string(), str.size());
}
}
diff --git a/core/jni/android_opengl_GLES11.cpp b/core/jni/android_opengl_GLES11.cpp
index 44213ed..0f71b9f 100644
--- a/core/jni/android_opengl_GLES11.cpp
+++ b/core/jni/android_opengl_GLES11.cpp
@@ -24,6 +24,13 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
+/* special calls implemented in Android's GLES wrapper used to more
+ * efficiently bound-check passed arrays */
+extern "C" {
+GL_API void GL_APIENTRY glPointSizePointerOESBounds(GLenum type, GLsizei stride,
+ const GLvoid *ptr, GLsizei count);
+}
+
static int initialized = 0;
static jclass nioAccessClass;
@@ -122,6 +129,19 @@ releasePointer(JNIEnv *_env, jarray array, void *data, jboolean commit)
commit ? 0 : JNI_ABORT);
}
+static void *
+getDirectBufferPointer(JNIEnv *_env, jobject buffer) {
+ char* buf = (char*) _env->GetDirectBufferAddress(buffer);
+ if (buf) {
+ jint position = _env->GetIntField(buffer, positionID);
+ jint elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+ buf += position << elementSizeShift;
+ } else {
+ _env->ThrowNew(IAEClass, "Must use a native order direct Buffer");
+ }
+ return (void*) buf;
+}
+
// --------------------------------------------------------------------------
/* void glBindBuffer ( GLenum target, GLuint buffer ) */
@@ -2035,21 +2055,24 @@ exit:
/* void glPointSizePointerOES ( GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glPointSizePointerOES__IILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf) {
+android_glPointSizePointerOESBounds__IILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf, jint remaining) {
jarray _array = (jarray) 0;
jint _remaining;
GLvoid *pointer = (GLvoid *) 0;
- pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
- glPointSizePointerOES(
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glPointSizePointerOESBounds(
(GLenum)type,
(GLsizei)stride,
- (GLvoid *)pointer
+ (GLvoid *)pointer,
+ (GLsizei)remaining
);
- if (_array) {
- releasePointer(_env, _array, pointer, JNI_FALSE);
- }
}
/* void glTexCoordPointer ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
@@ -2454,7 +2477,7 @@ static JNINativeMethod methods[] = {
{"glPointParameterx", "(II)V", (void *) android_glPointParameterx__II },
{"glPointParameterxv", "(I[II)V", (void *) android_glPointParameterxv__I_3II },
{"glPointParameterxv", "(ILjava/nio/IntBuffer;)V", (void *) android_glPointParameterxv__ILjava_nio_IntBuffer_2 },
-{"glPointSizePointerOES", "(IILjava/nio/Buffer;)V", (void *) android_glPointSizePointerOES__IILjava_nio_Buffer_2 },
+{"glPointSizePointerOESBounds", "(IILjava/nio/Buffer;I)V", (void *) android_glPointSizePointerOESBounds__IILjava_nio_Buffer_2I },
{"glTexCoordPointer", "(IIII)V", (void *) android_glTexCoordPointer__IIII },
{"glTexEnvi", "(III)V", (void *) android_glTexEnvi__III },
{"glTexEnviv", "(II[II)V", (void *) android_glTexEnviv__II_3II },
diff --git a/core/jni/android_opengl_GLES11Ext.cpp b/core/jni/android_opengl_GLES11Ext.cpp
index 6f3495c..942a0d9 100644
--- a/core/jni/android_opengl_GLES11Ext.cpp
+++ b/core/jni/android_opengl_GLES11Ext.cpp
@@ -24,6 +24,15 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
+/* special calls implemented in Android's GLES wrapper used to more
+ * efficiently bound-check passed arrays */
+extern "C" {
+GL_API void GL_APIENTRY glMatrixIndexPointerOESBounds(GLint size, GLenum type, GLsizei stride,
+ const GLvoid *ptr, GLsizei count);
+GL_API void GL_APIENTRY glWeightPointerOESBounds(GLint size, GLenum type, GLsizei stride,
+ const GLvoid *ptr, GLsizei count);
+}
+
static int initialized = 0;
static jclass nioAccessClass;
@@ -122,6 +131,18 @@ releasePointer(JNIEnv *_env, jarray array, void *data, jboolean commit)
commit ? 0 : JNI_ABORT);
}
+static void *
+getDirectBufferPointer(JNIEnv *_env, jobject buffer) {
+ char* buf = (char*) _env->GetDirectBufferAddress(buffer);
+ if (buf) {
+ jint position = _env->GetIntField(buffer, positionID);
+ jint elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+ buf += position << elementSizeShift;
+ } else {
+ _env->ThrowNew(IAEClass, "Must use a native order direct Buffer");
+ }
+ return (void*) buf;
+}
// --------------------------------------------------------------------------
/* void glBlendEquationSeparateOES ( GLenum modeRGB, GLenum modeAlpha ) */
@@ -1771,32 +1792,62 @@ android_glGenerateMipmapOES__I
static void
android_glCurrentPaletteMatrixOES__I
(JNIEnv *_env, jobject _this, jint matrixpaletteindex) {
- _env->ThrowNew(UOEClass,
- "glCurrentPaletteMatrixOES");
+ glCurrentPaletteMatrixOES(
+ (GLuint)matrixpaletteindex
+ );
}
/* void glLoadPaletteFromModelViewMatrixOES ( void ) */
static void
android_glLoadPaletteFromModelViewMatrixOES__
(JNIEnv *_env, jobject _this) {
- _env->ThrowNew(UOEClass,
- "glLoadPaletteFromModelViewMatrixOES");
+ glLoadPaletteFromModelViewMatrixOES();
}
/* void glMatrixIndexPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
- _env->ThrowNew(UOEClass,
- "glMatrixIndexPointerOES");
+android_glMatrixIndexPointerOESBounds__IIILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pointer = (GLvoid *) 0;
+
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glMatrixIndexPointerOESBounds(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (GLvoid *)pointer,
+ (GLsizei)remaining
+ );
}
/* void glWeightPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glWeightPointerOES__IIILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
- _env->ThrowNew(UOEClass,
- "glWeightPointerOES");
+android_glWeightPointerOESBounds__IIILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pointer = (GLvoid *) 0;
+
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glWeightPointerOESBounds(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (GLvoid *)pointer,
+ (GLsizei)remaining
+ );
}
/* void glDepthRangefOES ( GLclampf zNear, GLclampf zFar ) */
@@ -2426,8 +2477,8 @@ static JNINativeMethod methods[] = {
{"glGenerateMipmapOES", "(I)V", (void *) android_glGenerateMipmapOES__I },
{"glCurrentPaletteMatrixOES", "(I)V", (void *) android_glCurrentPaletteMatrixOES__I },
{"glLoadPaletteFromModelViewMatrixOES", "()V", (void *) android_glLoadPaletteFromModelViewMatrixOES__ },
-{"glMatrixIndexPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2 },
-{"glWeightPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glWeightPointerOES__IIILjava_nio_Buffer_2 },
+{"glMatrixIndexPointerOESBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glMatrixIndexPointerOESBounds__IIILjava_nio_Buffer_2I },
+{"glWeightPointerOESBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glWeightPointerOESBounds__IIILjava_nio_Buffer_2I },
{"glDepthRangefOES", "(FF)V", (void *) android_glDepthRangefOES__FF },
{"glFrustumfOES", "(FFFFFF)V", (void *) android_glFrustumfOES__FFFFFF },
{"glOrthofOES", "(FFFFFF)V", (void *) android_glOrthofOES__FFFFFF },
diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp
new file mode 100644
index 0000000..fd1c8fd
--- /dev/null
+++ b/core/jni/android_opengl_GLES20.cpp
@@ -0,0 +1,5023 @@
+/*
+**
+** Copyright 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.
+*/
+
+// This source file is automatically generated
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/misc.h>
+
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+static int initialized = 0;
+
+static jclass nioAccessClass;
+static jclass bufferClass;
+static jclass OOMEClass;
+static jclass UOEClass;
+static jclass IAEClass;
+static jclass AIOOBEClass;
+static jmethodID getBasePointerID;
+static jmethodID getBaseArrayID;
+static jmethodID getBaseArrayOffsetID;
+static jfieldID positionID;
+static jfieldID limitID;
+static jfieldID elementSizeShiftID;
+
+/* Cache method IDs each time the class is loaded. */
+
+static void
+nativeClassInitBuffer(JNIEnv *_env)
+{
+ jclass nioAccessClassLocal = _env->FindClass("java/nio/NIOAccess");
+ nioAccessClass = (jclass) _env->NewGlobalRef(nioAccessClassLocal);
+
+ jclass bufferClassLocal = _env->FindClass("java/nio/Buffer");
+ bufferClass = (jclass) _env->NewGlobalRef(bufferClassLocal);
+
+ getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
+ "getBasePointer", "(Ljava/nio/Buffer;)J");
+ getBaseArrayID = _env->GetStaticMethodID(nioAccessClass,
+ "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;");
+ getBaseArrayOffsetID = _env->GetStaticMethodID(nioAccessClass,
+ "getBaseArrayOffset", "(Ljava/nio/Buffer;)I");
+
+ positionID = _env->GetFieldID(bufferClass, "position", "I");
+ limitID = _env->GetFieldID(bufferClass, "limit", "I");
+ elementSizeShiftID =
+ _env->GetFieldID(bufferClass, "_elementSizeShift", "I");
+}
+
+
+static void
+nativeClassInit(JNIEnv *_env, jclass glImplClass)
+{
+ nativeClassInitBuffer(_env);
+
+ jclass IAEClassLocal =
+ _env->FindClass("java/lang/IllegalArgumentException");
+ jclass OOMEClassLocal =
+ _env->FindClass("java/lang/OutOfMemoryError");
+ jclass UOEClassLocal =
+ _env->FindClass("java/lang/UnsupportedOperationException");
+ jclass AIOOBEClassLocal =
+ _env->FindClass("java/lang/ArrayIndexOutOfBoundsException");
+
+ IAEClass = (jclass) _env->NewGlobalRef(IAEClassLocal);
+ OOMEClass = (jclass) _env->NewGlobalRef(OOMEClassLocal);
+ UOEClass = (jclass) _env->NewGlobalRef(UOEClassLocal);
+ AIOOBEClass = (jclass) _env->NewGlobalRef(AIOOBEClassLocal);
+}
+
+static void *
+getPointer(JNIEnv *_env, jobject buffer, jarray *array, jint *remaining)
+{
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ jint offset;
+ void *data;
+
+ position = _env->GetIntField(buffer, positionID);
+ limit = _env->GetIntField(buffer, limitID);
+ elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+ *remaining = (limit - position) << elementSizeShift;
+ pointer = _env->CallStaticLongMethod(nioAccessClass,
+ getBasePointerID, buffer);
+ if (pointer != 0L) {
+ *array = NULL;
+ return (void *) (jint) pointer;
+ }
+
+ *array = (jarray) _env->CallStaticObjectMethod(nioAccessClass,
+ getBaseArrayID, buffer);
+ offset = _env->CallStaticIntMethod(nioAccessClass,
+ getBaseArrayOffsetID, buffer);
+ data = _env->GetPrimitiveArrayCritical(*array, (jboolean *) 0);
+
+ return (void *) ((char *) data + offset);
+}
+
+
+static void
+releasePointer(JNIEnv *_env, jarray array, void *data, jboolean commit)
+{
+ _env->ReleasePrimitiveArrayCritical(array, data,
+ commit ? 0 : JNI_ABORT);
+}
+
+static void *
+getDirectBufferPointer(JNIEnv *_env, jobject buffer) {
+ char* buf = (char*) _env->GetDirectBufferAddress(buffer);
+ if (buf) {
+ jint position = _env->GetIntField(buffer, positionID);
+ jint elementSizeShift = _env->GetIntField(buffer, elementSizeShiftID);
+ buf += position << elementSizeShift;
+ } else {
+ _env->ThrowNew(IAEClass, "Must use a native order direct Buffer");
+ }
+ return (void*) buf;
+}
+
+static int
+getNumCompressedTextureFormats() {
+ int numCompressedTextureFormats = 0;
+ glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numCompressedTextureFormats);
+ return numCompressedTextureFormats;
+}
+
+static void glVertexAttribPointerBounds(GLuint indx, GLint size, GLenum type,
+ GLboolean normalized, GLsizei stride, const GLvoid *pointer, GLsizei count) {
+ glVertexAttribPointer(indx, size, type, normalized, stride, pointer);
+}
+
+// --------------------------------------------------------------------------
+
+/* void glActiveTexture ( GLenum texture ) */
+static void
+android_glActiveTexture__I
+ (JNIEnv *_env, jobject _this, jint texture) {
+ glActiveTexture(
+ (GLenum)texture
+ );
+}
+
+/* void glAttachShader ( GLuint program, GLuint shader ) */
+static void
+android_glAttachShader__II
+ (JNIEnv *_env, jobject _this, jint program, jint shader) {
+ glAttachShader(
+ (GLuint)program,
+ (GLuint)shader
+ );
+}
+
+/* void glBindAttribLocation ( GLuint program, GLuint index, const char *name ) */
+static void
+android_glBindAttribLocation__IILjava_lang_String_2
+ (JNIEnv *_env, jobject _this, jint program, jint index, jstring name) {
+ const char* _nativename = 0;
+
+ if (!name) {
+ _env->ThrowNew(IAEClass, "name == null");
+ goto exit;
+ }
+ _nativename = _env->GetStringUTFChars(name, 0);
+
+ glBindAttribLocation(
+ (GLuint)program,
+ (GLuint)index,
+ (char *)_nativename
+ );
+
+exit:
+ if (_nativename) {
+ _env->ReleaseStringUTFChars(name, _nativename);
+ }
+
+}
+
+/* void glBindBuffer ( GLenum target, GLuint buffer ) */
+static void
+android_glBindBuffer__II
+ (JNIEnv *_env, jobject _this, jint target, jint buffer) {
+ glBindBuffer(
+ (GLenum)target,
+ (GLuint)buffer
+ );
+}
+
+/* void glBindFramebuffer ( GLenum target, GLuint framebuffer ) */
+static void
+android_glBindFramebuffer__II
+ (JNIEnv *_env, jobject _this, jint target, jint framebuffer) {
+ glBindFramebuffer(
+ (GLenum)target,
+ (GLuint)framebuffer
+ );
+}
+
+/* void glBindRenderbuffer ( GLenum target, GLuint renderbuffer ) */
+static void
+android_glBindRenderbuffer__II
+ (JNIEnv *_env, jobject _this, jint target, jint renderbuffer) {
+ glBindRenderbuffer(
+ (GLenum)target,
+ (GLuint)renderbuffer
+ );
+}
+
+/* void glBindTexture ( GLenum target, GLuint texture ) */
+static void
+android_glBindTexture__II
+ (JNIEnv *_env, jobject _this, jint target, jint texture) {
+ glBindTexture(
+ (GLenum)target,
+ (GLuint)texture
+ );
+}
+
+/* void glBlendColor ( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ) */
+static void
+android_glBlendColor__FFFF
+ (JNIEnv *_env, jobject _this, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
+ glBlendColor(
+ (GLclampf)red,
+ (GLclampf)green,
+ (GLclampf)blue,
+ (GLclampf)alpha
+ );
+}
+
+/* void glBlendEquation ( GLenum mode ) */
+static void
+android_glBlendEquation__I
+ (JNIEnv *_env, jobject _this, jint mode) {
+ _env->ThrowNew(UOEClass,
+ "glBlendEquation");
+}
+
+/* void glBlendEquationSeparate ( GLenum modeRGB, GLenum modeAlpha ) */
+static void
+android_glBlendEquationSeparate__II
+ (JNIEnv *_env, jobject _this, jint modeRGB, jint modeAlpha) {
+ _env->ThrowNew(UOEClass,
+ "glBlendEquationSeparate");
+}
+
+/* void glBlendFunc ( GLenum sfactor, GLenum dfactor ) */
+static void
+android_glBlendFunc__II
+ (JNIEnv *_env, jobject _this, jint sfactor, jint dfactor) {
+ glBlendFunc(
+ (GLenum)sfactor,
+ (GLenum)dfactor
+ );
+}
+
+/* void glBlendFuncSeparate ( GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha ) */
+static void
+android_glBlendFuncSeparate__IIII
+ (JNIEnv *_env, jobject _this, jint srcRGB, jint dstRGB, jint srcAlpha, jint dstAlpha) {
+ _env->ThrowNew(UOEClass,
+ "glBlendFuncSeparate");
+}
+
+/* void glBufferData ( GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage ) */
+static void
+android_glBufferData__IILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint target, jint size, jobject data_buf, jint usage) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *data = (GLvoid *) 0;
+
+ if (data_buf) {
+ data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+ if (_remaining < size) {
+ _env->ThrowNew(IAEClass, "remaining() < size");
+ goto exit;
+ }
+ }
+ glBufferData(
+ (GLenum)target,
+ (GLsizeiptr)size,
+ (GLvoid *)data,
+ (GLenum)usage
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, data, JNI_FALSE);
+ }
+}
+
+/* void glBufferSubData ( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data ) */
+static void
+android_glBufferSubData__IIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint offset, jint size, jobject data_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *data = (GLvoid *) 0;
+
+ data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+ if (_remaining < size) {
+ _env->ThrowNew(IAEClass, "remaining() < size");
+ goto exit;
+ }
+ glBufferSubData(
+ (GLenum)target,
+ (GLintptr)offset,
+ (GLsizeiptr)size,
+ (GLvoid *)data
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, data, JNI_FALSE);
+ }
+}
+
+/* GLenum glCheckFramebufferStatus ( GLenum target ) */
+static jint
+android_glCheckFramebufferStatus__I
+ (JNIEnv *_env, jobject _this, jint target) {
+ GLenum _returnValue;
+ _returnValue = glCheckFramebufferStatus(
+ (GLenum)target
+ );
+ return _returnValue;
+}
+
+/* void glClear ( GLbitfield mask ) */
+static void
+android_glClear__I
+ (JNIEnv *_env, jobject _this, jint mask) {
+ glClear(
+ (GLbitfield)mask
+ );
+}
+
+/* void glClearColor ( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha ) */
+static void
+android_glClearColor__FFFF
+ (JNIEnv *_env, jobject _this, jfloat red, jfloat green, jfloat blue, jfloat alpha) {
+ glClearColor(
+ (GLclampf)red,
+ (GLclampf)green,
+ (GLclampf)blue,
+ (GLclampf)alpha
+ );
+}
+
+/* void glClearDepthf ( GLclampf depth ) */
+static void
+android_glClearDepthf__F
+ (JNIEnv *_env, jobject _this, jfloat depth) {
+ glClearDepthf(
+ (GLclampf)depth
+ );
+}
+
+/* void glClearStencil ( GLint s ) */
+static void
+android_glClearStencil__I
+ (JNIEnv *_env, jobject _this, jint s) {
+ glClearStencil(
+ (GLint)s
+ );
+}
+
+/* void glColorMask ( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha ) */
+static void
+android_glColorMask__ZZZZ
+ (JNIEnv *_env, jobject _this, jboolean red, jboolean green, jboolean blue, jboolean alpha) {
+ glColorMask(
+ (GLboolean)red,
+ (GLboolean)green,
+ (GLboolean)blue,
+ (GLboolean)alpha
+ );
+}
+
+/* void glCompileShader ( GLuint shader ) */
+static void
+android_glCompileShader__I
+ (JNIEnv *_env, jobject _this, jint shader) {
+ glCompileShader(
+ (GLuint)shader
+ );
+}
+
+/* void glCompressedTexImage2D ( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data ) */
+static void
+android_glCompressedTexImage2D__IIIIIIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint imageSize, jobject data_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *data = (GLvoid *) 0;
+
+ data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+ glCompressedTexImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLenum)internalformat,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLint)border,
+ (GLsizei)imageSize,
+ (GLvoid *)data
+ );
+ if (_array) {
+ releasePointer(_env, _array, data, JNI_FALSE);
+ }
+}
+
+/* void glCompressedTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data ) */
+static void
+android_glCompressedTexSubImage2D__IIIIIIIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint imageSize, jobject data_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *data = (GLvoid *) 0;
+
+ data = (GLvoid *)getPointer(_env, data_buf, &_array, &_remaining);
+ glCompressedTexSubImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLint)xoffset,
+ (GLint)yoffset,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLenum)format,
+ (GLsizei)imageSize,
+ (GLvoid *)data
+ );
+ if (_array) {
+ releasePointer(_env, _array, data, JNI_FALSE);
+ }
+}
+
+/* void glCopyTexImage2D ( GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border ) */
+static void
+android_glCopyTexImage2D__IIIIIIII
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint x, jint y, jint width, jint height, jint border) {
+ glCopyTexImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLenum)internalformat,
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLint)border
+ );
+}
+
+/* void glCopyTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glCopyTexSubImage2D__IIIIIIII
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint x, jint y, jint width, jint height) {
+ glCopyTexSubImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLint)xoffset,
+ (GLint)yoffset,
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height
+ );
+}
+
+/* GLuint glCreateProgram ( void ) */
+static jint
+android_glCreateProgram__
+ (JNIEnv *_env, jobject _this) {
+ GLuint _returnValue;
+ _returnValue = glCreateProgram();
+ return _returnValue;
+}
+
+/* GLuint glCreateShader ( GLenum type ) */
+static jint
+android_glCreateShader__I
+ (JNIEnv *_env, jobject _this, jint type) {
+ GLuint _returnValue;
+ _returnValue = glCreateShader(
+ (GLenum)type
+ );
+ return _returnValue;
+}
+
+/* void glCullFace ( GLenum mode ) */
+static void
+android_glCullFace__I
+ (JNIEnv *_env, jobject _this, jint mode) {
+ glCullFace(
+ (GLenum)mode
+ );
+}
+
+/* void glDeleteBuffers ( GLsizei n, const GLuint *buffers ) */
+static void
+android_glDeleteBuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray buffers_ref, jint offset) {
+ GLuint *buffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *buffers = (GLuint *) 0;
+
+ if (!buffers_ref) {
+ _env->ThrowNew(IAEClass, "buffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(buffers_ref) - offset;
+ if (_remaining < n) {
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ buffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(buffers_ref, (jboolean *)0);
+ buffers = buffers_base + offset;
+
+ glDeleteBuffers(
+ (GLsizei)n,
+ (GLuint *)buffers
+ );
+
+exit:
+ if (buffers_base) {
+ _env->ReleasePrimitiveArrayCritical(buffers_ref, buffers_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glDeleteBuffers ( GLsizei n, const GLuint *buffers ) */
+static void
+android_glDeleteBuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject buffers_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *buffers = (GLuint *) 0;
+
+ buffers = (GLuint *)getPointer(_env, buffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glDeleteBuffers(
+ (GLsizei)n,
+ (GLuint *)buffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, buffers, JNI_FALSE);
+ }
+}
+
+/* void glDeleteFramebuffers ( GLsizei n, const GLuint *framebuffers ) */
+static void
+android_glDeleteFramebuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
+ GLuint *framebuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ if (!framebuffers_ref) {
+ _env->ThrowNew(IAEClass, "framebuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(framebuffers_ref) - offset;
+ framebuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(framebuffers_ref, (jboolean *)0);
+ framebuffers = framebuffers_base + offset;
+
+ glDeleteFramebuffers(
+ (GLsizei)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (framebuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(framebuffers_ref, framebuffers_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glDeleteFramebuffers ( GLsizei n, const GLuint *framebuffers ) */
+static void
+android_glDeleteFramebuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ framebuffers = (GLuint *)getPointer(_env, framebuffers_buf, &_array, &_remaining);
+ glDeleteFramebuffers(
+ (GLsizei)n,
+ (GLuint *)framebuffers
+ );
+ if (_array) {
+ releasePointer(_env, _array, framebuffers, JNI_FALSE);
+ }
+}
+
+/* void glDeleteProgram ( GLuint program ) */
+static void
+android_glDeleteProgram__I
+ (JNIEnv *_env, jobject _this, jint program) {
+ glDeleteProgram(
+ (GLuint)program
+ );
+}
+
+/* void glDeleteRenderbuffers ( GLsizei n, const GLuint *renderbuffers ) */
+static void
+android_glDeleteRenderbuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
+ GLuint *renderbuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ if (!renderbuffers_ref) {
+ _env->ThrowNew(IAEClass, "renderbuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(renderbuffers_ref) - offset;
+ renderbuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(renderbuffers_ref, (jboolean *)0);
+ renderbuffers = renderbuffers_base + offset;
+
+ glDeleteRenderbuffers(
+ (GLsizei)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (renderbuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(renderbuffers_ref, renderbuffers_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glDeleteRenderbuffers ( GLsizei n, const GLuint *renderbuffers ) */
+static void
+android_glDeleteRenderbuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ renderbuffers = (GLuint *)getPointer(_env, renderbuffers_buf, &_array, &_remaining);
+ glDeleteRenderbuffers(
+ (GLsizei)n,
+ (GLuint *)renderbuffers
+ );
+ if (_array) {
+ releasePointer(_env, _array, renderbuffers, JNI_FALSE);
+ }
+}
+
+/* void glDeleteShader ( GLuint shader ) */
+static void
+android_glDeleteShader__I
+ (JNIEnv *_env, jobject _this, jint shader) {
+ glDeleteShader(
+ (GLuint)shader
+ );
+}
+
+/* void glDeleteTextures ( GLsizei n, const GLuint *textures ) */
+static void
+android_glDeleteTextures__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray textures_ref, jint offset) {
+ GLuint *textures_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *textures = (GLuint *) 0;
+
+ if (!textures_ref) {
+ _env->ThrowNew(IAEClass, "textures == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(textures_ref) - offset;
+ if (_remaining < n) {
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ textures_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(textures_ref, (jboolean *)0);
+ textures = textures_base + offset;
+
+ glDeleteTextures(
+ (GLsizei)n,
+ (GLuint *)textures
+ );
+
+exit:
+ if (textures_base) {
+ _env->ReleasePrimitiveArrayCritical(textures_ref, textures_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glDeleteTextures ( GLsizei n, const GLuint *textures ) */
+static void
+android_glDeleteTextures__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject textures_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *textures = (GLuint *) 0;
+
+ textures = (GLuint *)getPointer(_env, textures_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glDeleteTextures(
+ (GLsizei)n,
+ (GLuint *)textures
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, textures, JNI_FALSE);
+ }
+}
+
+/* void glDepthFunc ( GLenum func ) */
+static void
+android_glDepthFunc__I
+ (JNIEnv *_env, jobject _this, jint func) {
+ glDepthFunc(
+ (GLenum)func
+ );
+}
+
+/* void glDepthMask ( GLboolean flag ) */
+static void
+android_glDepthMask__Z
+ (JNIEnv *_env, jobject _this, jboolean flag) {
+ glDepthMask(
+ (GLboolean)flag
+ );
+}
+
+/* void glDepthRangef ( GLclampf zNear, GLclampf zFar ) */
+static void
+android_glDepthRangef__FF
+ (JNIEnv *_env, jobject _this, jfloat zNear, jfloat zFar) {
+ glDepthRangef(
+ (GLclampf)zNear,
+ (GLclampf)zFar
+ );
+}
+
+/* void glDetachShader ( GLuint program, GLuint shader ) */
+static void
+android_glDetachShader__II
+ (JNIEnv *_env, jobject _this, jint program, jint shader) {
+ glDetachShader(
+ (GLuint)program,
+ (GLuint)shader
+ );
+}
+
+/* void glDisable ( GLenum cap ) */
+static void
+android_glDisable__I
+ (JNIEnv *_env, jobject _this, jint cap) {
+ glDisable(
+ (GLenum)cap
+ );
+}
+
+/* void glDisableVertexAttribArray ( GLuint index ) */
+static void
+android_glDisableVertexAttribArray__I
+ (JNIEnv *_env, jobject _this, jint index) {
+ glDisableVertexAttribArray(
+ (GLuint)index
+ );
+}
+
+/* void glDrawArrays ( GLenum mode, GLint first, GLsizei count ) */
+static void
+android_glDrawArrays__III
+ (JNIEnv *_env, jobject _this, jint mode, jint first, jint count) {
+ glDrawArrays(
+ (GLenum)mode,
+ (GLint)first,
+ (GLsizei)count
+ );
+}
+
+/* void glDrawElements ( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) */
+static void
+android_glDrawElements__IIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint mode, jint count, jint type, jobject indices_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *indices = (GLvoid *) 0;
+
+ indices = (GLvoid *)getPointer(_env, indices_buf, &_array, &_remaining);
+ if (_remaining < count) {
+ _env->ThrowNew(AIOOBEClass, "remaining() < count");
+ goto exit;
+ }
+ glDrawElements(
+ (GLenum)mode,
+ (GLsizei)count,
+ (GLenum)type,
+ (GLvoid *)indices
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, indices, JNI_FALSE);
+ }
+}
+
+/* void glEnable ( GLenum cap ) */
+static void
+android_glEnable__I
+ (JNIEnv *_env, jobject _this, jint cap) {
+ glEnable(
+ (GLenum)cap
+ );
+}
+
+/* void glEnableVertexAttribArray ( GLuint index ) */
+static void
+android_glEnableVertexAttribArray__I
+ (JNIEnv *_env, jobject _this, jint index) {
+ glEnableVertexAttribArray(
+ (GLuint)index
+ );
+}
+
+/* void glFinish ( void ) */
+static void
+android_glFinish__
+ (JNIEnv *_env, jobject _this) {
+ glFinish();
+}
+
+/* void glFlush ( void ) */
+static void
+android_glFlush__
+ (JNIEnv *_env, jobject _this) {
+ glFlush();
+}
+
+/* void glFramebufferRenderbuffer ( GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer ) */
+static void
+android_glFramebufferRenderbuffer__IIII
+ (JNIEnv *_env, jobject _this, jint target, jint attachment, jint renderbuffertarget, jint renderbuffer) {
+ glFramebufferRenderbuffer(
+ (GLenum)target,
+ (GLenum)attachment,
+ (GLenum)renderbuffertarget,
+ (GLuint)renderbuffer
+ );
+}
+
+/* void glFramebufferTexture2D ( GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level ) */
+static void
+android_glFramebufferTexture2D__IIIII
+ (JNIEnv *_env, jobject _this, jint target, jint attachment, jint textarget, jint texture, jint level) {
+ glFramebufferTexture2D(
+ (GLenum)target,
+ (GLenum)attachment,
+ (GLenum)textarget,
+ (GLuint)texture,
+ (GLint)level
+ );
+}
+
+/* void glFrontFace ( GLenum mode ) */
+static void
+android_glFrontFace__I
+ (JNIEnv *_env, jobject _this, jint mode) {
+ glFrontFace(
+ (GLenum)mode
+ );
+}
+
+/* void glGenBuffers ( GLsizei n, GLuint *buffers ) */
+static void
+android_glGenBuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray buffers_ref, jint offset) {
+ jint _exception = 0;
+ GLuint *buffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *buffers = (GLuint *) 0;
+
+ if (!buffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "buffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(buffers_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ buffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(buffers_ref, (jboolean *)0);
+ buffers = buffers_base + offset;
+
+ glGenBuffers(
+ (GLsizei)n,
+ (GLuint *)buffers
+ );
+
+exit:
+ if (buffers_base) {
+ _env->ReleasePrimitiveArrayCritical(buffers_ref, buffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGenBuffers ( GLsizei n, GLuint *buffers ) */
+static void
+android_glGenBuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject buffers_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *buffers = (GLuint *) 0;
+
+ buffers = (GLuint *)getPointer(_env, buffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glGenBuffers(
+ (GLsizei)n,
+ (GLuint *)buffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, buffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGenerateMipmap ( GLenum target ) */
+static void
+android_glGenerateMipmap__I
+ (JNIEnv *_env, jobject _this, jint target) {
+ glGenerateMipmap(
+ (GLenum)target
+ );
+}
+
+/* void glGenFramebuffers ( GLsizei n, GLuint *framebuffers ) */
+static void
+android_glGenFramebuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
+ jint _exception = 0;
+ GLuint *framebuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ if (!framebuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "framebuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(framebuffers_ref) - offset;
+ framebuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(framebuffers_ref, (jboolean *)0);
+ framebuffers = framebuffers_base + offset;
+
+ glGenFramebuffers(
+ (GLsizei)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (framebuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(framebuffers_ref, framebuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGenFramebuffers ( GLsizei n, GLuint *framebuffers ) */
+static void
+android_glGenFramebuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ framebuffers = (GLuint *)getPointer(_env, framebuffers_buf, &_array, &_remaining);
+ glGenFramebuffers(
+ (GLsizei)n,
+ (GLuint *)framebuffers
+ );
+ if (_array) {
+ releasePointer(_env, _array, framebuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGenRenderbuffers ( GLsizei n, GLuint *renderbuffers ) */
+static void
+android_glGenRenderbuffers__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
+ jint _exception = 0;
+ GLuint *renderbuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ if (!renderbuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "renderbuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(renderbuffers_ref) - offset;
+ renderbuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(renderbuffers_ref, (jboolean *)0);
+ renderbuffers = renderbuffers_base + offset;
+
+ glGenRenderbuffers(
+ (GLsizei)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (renderbuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(renderbuffers_ref, renderbuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGenRenderbuffers ( GLsizei n, GLuint *renderbuffers ) */
+static void
+android_glGenRenderbuffers__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ renderbuffers = (GLuint *)getPointer(_env, renderbuffers_buf, &_array, &_remaining);
+ glGenRenderbuffers(
+ (GLsizei)n,
+ (GLuint *)renderbuffers
+ );
+ if (_array) {
+ releasePointer(_env, _array, renderbuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGenTextures ( GLsizei n, GLuint *textures ) */
+static void
+android_glGenTextures__I_3II
+ (JNIEnv *_env, jobject _this, jint n, jintArray textures_ref, jint offset) {
+ jint _exception = 0;
+ GLuint *textures_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *textures = (GLuint *) 0;
+
+ if (!textures_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "textures == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(textures_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ textures_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(textures_ref, (jboolean *)0);
+ textures = textures_base + offset;
+
+ glGenTextures(
+ (GLsizei)n,
+ (GLuint *)textures
+ );
+
+exit:
+ if (textures_base) {
+ _env->ReleasePrimitiveArrayCritical(textures_ref, textures_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGenTextures ( GLsizei n, GLuint *textures ) */
+static void
+android_glGenTextures__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint n, jobject textures_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *textures = (GLuint *) 0;
+
+ textures = (GLuint *)getPointer(_env, textures_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glGenTextures(
+ (GLsizei)n,
+ (GLuint *)textures
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, textures, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetActiveAttrib ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name ) */
+static void
+android_glGetActiveAttrib__III_3II_3II_3II_3BI
+ (JNIEnv *_env, jobject _this, jint program, jint index, jint bufsize, jintArray length_ref, jint lengthOffset, jintArray size_ref, jint sizeOffset, jintArray type_ref, jint typeOffset, jbyteArray name_ref, jint nameOffset) {
+ jint _exception = 0;
+ GLsizei *length_base = (GLsizei *) 0;
+ jint _lengthRemaining;
+ GLsizei *length = (GLsizei *) 0;
+ GLint *size_base = (GLint *) 0;
+ jint _sizeRemaining;
+ GLint *size = (GLint *) 0;
+ GLenum *type_base = (GLenum *) 0;
+ jint _typeRemaining;
+ GLenum *type = (GLenum *) 0;
+ char *name_base = (char *) 0;
+ jint _nameRemaining;
+ char *name = (char *) 0;
+
+ if (!length_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length == null");
+ goto exit;
+ }
+ if (lengthOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "lengthOffset < 0");
+ goto exit;
+ }
+ _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+ length_base = (GLsizei *)
+ _env->GetPrimitiveArrayCritical(length_ref, (jboolean *)0);
+ length = length_base + lengthOffset;
+
+ if (!size_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "size == null");
+ goto exit;
+ }
+ if (sizeOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "sizeOffset < 0");
+ goto exit;
+ }
+ _sizeRemaining = _env->GetArrayLength(size_ref) - sizeOffset;
+ size_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(size_ref, (jboolean *)0);
+ size = size_base + sizeOffset;
+
+ if (!type_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "type == null");
+ goto exit;
+ }
+ if (typeOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "typeOffset < 0");
+ goto exit;
+ }
+ _typeRemaining = _env->GetArrayLength(type_ref) - typeOffset;
+ type_base = (GLenum *)
+ _env->GetPrimitiveArrayCritical(type_ref, (jboolean *)0);
+ type = type_base + typeOffset;
+
+ if (!name_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "name == null");
+ goto exit;
+ }
+ if (nameOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "nameOffset < 0");
+ goto exit;
+ }
+ _nameRemaining = _env->GetArrayLength(name_ref) - nameOffset;
+ name_base = (char *)
+ _env->GetPrimitiveArrayCritical(name_ref, (jboolean *)0);
+ name = name_base + nameOffset;
+
+ glGetActiveAttrib(
+ (GLuint)program,
+ (GLuint)index,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (GLint *)size,
+ (GLenum *)type,
+ (char *)name
+ );
+
+exit:
+ if (name_base) {
+ _env->ReleasePrimitiveArrayCritical(name_ref, name_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (type_base) {
+ _env->ReleasePrimitiveArrayCritical(type_ref, type_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (size_base) {
+ _env->ReleasePrimitiveArrayCritical(size_ref, size_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (length_base) {
+ _env->ReleasePrimitiveArrayCritical(length_ref, length_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetActiveAttrib ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name ) */
+static void
+android_glGetActiveAttrib__IIILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2B
+ (JNIEnv *_env, jobject _this, jint program, jint index, jint bufsize, jobject length_buf, jobject size_buf, jobject type_buf, jbyte name) {
+ jint _exception = 0;
+ jarray _lengthArray = (jarray) 0;
+ jarray _sizeArray = (jarray) 0;
+ jarray _typeArray = (jarray) 0;
+ jint _lengthRemaining;
+ GLsizei *length = (GLsizei *) 0;
+ jint _sizeRemaining;
+ GLint *size = (GLint *) 0;
+ jint _typeRemaining;
+ GLenum *type = (GLenum *) 0;
+
+ length = (GLsizei *)getPointer(_env, length_buf, &_lengthArray, &_lengthRemaining);
+ size = (GLint *)getPointer(_env, size_buf, &_sizeArray, &_sizeRemaining);
+ type = (GLenum *)getPointer(_env, type_buf, &_typeArray, &_typeRemaining);
+ glGetActiveAttrib(
+ (GLuint)program,
+ (GLuint)index,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (GLint *)size,
+ (GLenum *)type,
+ (char *)name
+ );
+ if (_lengthArray) {
+ releasePointer(_env, _lengthArray, type, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_sizeArray) {
+ releasePointer(_env, _sizeArray, size, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_typeArray) {
+ releasePointer(_env, _typeArray, length, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetActiveUniform ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name ) */
+static void
+android_glGetActiveUniform__III_3II_3II_3II_3BI
+ (JNIEnv *_env, jobject _this, jint program, jint index, jint bufsize, jintArray length_ref, jint lengthOffset, jintArray size_ref, jint sizeOffset, jintArray type_ref, jint typeOffset, jbyteArray name_ref, jint nameOffset) {
+ jint _exception = 0;
+ GLsizei *length_base = (GLsizei *) 0;
+ jint _lengthRemaining;
+ GLsizei *length = (GLsizei *) 0;
+ GLint *size_base = (GLint *) 0;
+ jint _sizeRemaining;
+ GLint *size = (GLint *) 0;
+ GLenum *type_base = (GLenum *) 0;
+ jint _typeRemaining;
+ GLenum *type = (GLenum *) 0;
+ char *name_base = (char *) 0;
+ jint _nameRemaining;
+ char *name = (char *) 0;
+
+ if (!length_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length == null");
+ goto exit;
+ }
+ if (lengthOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "lengthOffset < 0");
+ goto exit;
+ }
+ _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+ length_base = (GLsizei *)
+ _env->GetPrimitiveArrayCritical(length_ref, (jboolean *)0);
+ length = length_base + lengthOffset;
+
+ if (!size_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "size == null");
+ goto exit;
+ }
+ if (sizeOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "sizeOffset < 0");
+ goto exit;
+ }
+ _sizeRemaining = _env->GetArrayLength(size_ref) - sizeOffset;
+ size_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(size_ref, (jboolean *)0);
+ size = size_base + sizeOffset;
+
+ if (!type_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "type == null");
+ goto exit;
+ }
+ if (typeOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "typeOffset < 0");
+ goto exit;
+ }
+ _typeRemaining = _env->GetArrayLength(type_ref) - typeOffset;
+ type_base = (GLenum *)
+ _env->GetPrimitiveArrayCritical(type_ref, (jboolean *)0);
+ type = type_base + typeOffset;
+
+ if (!name_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "name == null");
+ goto exit;
+ }
+ if (nameOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "nameOffset < 0");
+ goto exit;
+ }
+ _nameRemaining = _env->GetArrayLength(name_ref) - nameOffset;
+ name_base = (char *)
+ _env->GetPrimitiveArrayCritical(name_ref, (jboolean *)0);
+ name = name_base + nameOffset;
+
+ glGetActiveUniform(
+ (GLuint)program,
+ (GLuint)index,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (GLint *)size,
+ (GLenum *)type,
+ (char *)name
+ );
+
+exit:
+ if (name_base) {
+ _env->ReleasePrimitiveArrayCritical(name_ref, name_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (type_base) {
+ _env->ReleasePrimitiveArrayCritical(type_ref, type_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (size_base) {
+ _env->ReleasePrimitiveArrayCritical(size_ref, size_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (length_base) {
+ _env->ReleasePrimitiveArrayCritical(length_ref, length_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetActiveUniform ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name ) */
+static void
+android_glGetActiveUniform__IIILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2B
+ (JNIEnv *_env, jobject _this, jint program, jint index, jint bufsize, jobject length_buf, jobject size_buf, jobject type_buf, jbyte name) {
+ jint _exception = 0;
+ jarray _lengthArray = (jarray) 0;
+ jarray _sizeArray = (jarray) 0;
+ jarray _typeArray = (jarray) 0;
+ jint _lengthRemaining;
+ GLsizei *length = (GLsizei *) 0;
+ jint _sizeRemaining;
+ GLint *size = (GLint *) 0;
+ jint _typeRemaining;
+ GLenum *type = (GLenum *) 0;
+
+ length = (GLsizei *)getPointer(_env, length_buf, &_lengthArray, &_lengthRemaining);
+ size = (GLint *)getPointer(_env, size_buf, &_sizeArray, &_sizeRemaining);
+ type = (GLenum *)getPointer(_env, type_buf, &_typeArray, &_typeRemaining);
+ glGetActiveUniform(
+ (GLuint)program,
+ (GLuint)index,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (GLint *)size,
+ (GLenum *)type,
+ (char *)name
+ );
+ if (_lengthArray) {
+ releasePointer(_env, _lengthArray, type, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_sizeArray) {
+ releasePointer(_env, _sizeArray, size, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_typeArray) {
+ releasePointer(_env, _typeArray, length, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetAttachedShaders ( GLuint program, GLsizei maxcount, GLsizei *count, GLuint *shaders ) */
+static void
+android_glGetAttachedShaders__II_3II_3II
+ (JNIEnv *_env, jobject _this, jint program, jint maxcount, jintArray count_ref, jint countOffset, jintArray shaders_ref, jint shadersOffset) {
+ jint _exception = 0;
+ GLsizei *count_base = (GLsizei *) 0;
+ jint _countRemaining;
+ GLsizei *count = (GLsizei *) 0;
+ GLuint *shaders_base = (GLuint *) 0;
+ jint _shadersRemaining;
+ GLuint *shaders = (GLuint *) 0;
+
+ if (!count_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "count == null");
+ goto exit;
+ }
+ if (countOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "countOffset < 0");
+ goto exit;
+ }
+ _countRemaining = _env->GetArrayLength(count_ref) - countOffset;
+ count_base = (GLsizei *)
+ _env->GetPrimitiveArrayCritical(count_ref, (jboolean *)0);
+ count = count_base + countOffset;
+
+ if (!shaders_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "shaders == null");
+ goto exit;
+ }
+ if (shadersOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "shadersOffset < 0");
+ goto exit;
+ }
+ _shadersRemaining = _env->GetArrayLength(shaders_ref) - shadersOffset;
+ shaders_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(shaders_ref, (jboolean *)0);
+ shaders = shaders_base + shadersOffset;
+
+ glGetAttachedShaders(
+ (GLuint)program,
+ (GLsizei)maxcount,
+ (GLsizei *)count,
+ (GLuint *)shaders
+ );
+
+exit:
+ if (shaders_base) {
+ _env->ReleasePrimitiveArrayCritical(shaders_ref, shaders_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (count_base) {
+ _env->ReleasePrimitiveArrayCritical(count_ref, count_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetAttachedShaders ( GLuint program, GLsizei maxcount, GLsizei *count, GLuint *shaders ) */
+static void
+android_glGetAttachedShaders__IILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint program, jint maxcount, jobject count_buf, jobject shaders_buf) {
+ jint _exception = 0;
+ jarray _countArray = (jarray) 0;
+ jarray _shadersArray = (jarray) 0;
+ jint _countRemaining;
+ GLsizei *count = (GLsizei *) 0;
+ jint _shadersRemaining;
+ GLuint *shaders = (GLuint *) 0;
+
+ count = (GLsizei *)getPointer(_env, count_buf, &_countArray, &_countRemaining);
+ shaders = (GLuint *)getPointer(_env, shaders_buf, &_shadersArray, &_shadersRemaining);
+ glGetAttachedShaders(
+ (GLuint)program,
+ (GLsizei)maxcount,
+ (GLsizei *)count,
+ (GLuint *)shaders
+ );
+ if (_countArray) {
+ releasePointer(_env, _countArray, shaders, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_shadersArray) {
+ releasePointer(_env, _shadersArray, count, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* int glGetAttribLocation ( GLuint program, const char *name ) */
+static jint
+android_glGetAttribLocation__ILjava_lang_String_2
+ (JNIEnv *_env, jobject _this, jint program, jstring name) {
+ int _returnValue = 0;
+ const char* _nativename = 0;
+
+ if (!name) {
+ _env->ThrowNew(IAEClass, "name == null");
+ goto exit;
+ }
+ _nativename = _env->GetStringUTFChars(name, 0);
+
+ _returnValue = glGetAttribLocation(
+ (GLuint)program,
+ (char *)_nativename
+ );
+
+exit:
+ if (_nativename) {
+ _env->ReleaseStringUTFChars(name, _nativename);
+ }
+
+ return _returnValue;
+}
+
+/* void glGetBooleanv ( GLenum pname, GLboolean *params ) */
+static void
+android_glGetBooleanv__I_3ZI
+ (JNIEnv *_env, jobject _this, jint pname, jbooleanArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLboolean *params_base = (GLboolean *) 0;
+ jint _remaining;
+ GLboolean *params = (GLboolean *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLboolean *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetBooleanv(
+ (GLenum)pname,
+ (GLboolean *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetBooleanv ( GLenum pname, GLboolean *params ) */
+static void
+android_glGetBooleanv__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLboolean *params = (GLboolean *) 0;
+
+ params = (GLboolean *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetBooleanv(
+ (GLenum)pname,
+ (GLboolean *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetBufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetBufferParameteriv__II_3II
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+ _env->ThrowNew(UOEClass,
+ "glGetBufferParameteriv");
+}
+
+/* void glGetBufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetBufferParameteriv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ _env->ThrowNew(UOEClass,
+ "glGetBufferParameteriv");
+}
+
+/* GLenum glGetError ( void ) */
+static jint
+android_glGetError__
+ (JNIEnv *_env, jobject _this) {
+ GLenum _returnValue;
+ _returnValue = glGetError();
+ return _returnValue;
+}
+
+/* void glGetFloatv ( GLenum pname, GLfloat *params ) */
+static void
+android_glGetFloatv__I_3FI
+ (JNIEnv *_env, jobject _this, jint pname, jfloatArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetFloatv(
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetFloatv ( GLenum pname, GLfloat *params ) */
+static void
+android_glGetFloatv__ILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetFloatv(
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetFramebufferAttachmentParameteriv ( GLenum target, GLenum attachment, GLenum pname, GLint *params ) */
+static void
+android_glGetFramebufferAttachmentParameteriv__III_3II
+ (JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetFramebufferAttachmentParameteriv(
+ (GLenum)target,
+ (GLenum)attachment,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetFramebufferAttachmentParameteriv ( GLenum target, GLenum attachment, GLenum pname, GLint *params ) */
+static void
+android_glGetFramebufferAttachmentParameteriv__IIILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetFramebufferAttachmentParameteriv(
+ (GLenum)target,
+ (GLenum)attachment,
+ (GLenum)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetIntegerv ( GLenum pname, GLint *params ) */
+static void
+android_glGetIntegerv__I_3II
+ (JNIEnv *_env, jobject _this, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ int _needed;
+ switch (pname) {
+#if defined(GL_ALPHA_BITS)
+ case GL_ALPHA_BITS:
+#endif // defined(GL_ALPHA_BITS)
+#if defined(GL_ALPHA_TEST_FUNC)
+ case GL_ALPHA_TEST_FUNC:
+#endif // defined(GL_ALPHA_TEST_FUNC)
+#if defined(GL_ALPHA_TEST_REF)
+ case GL_ALPHA_TEST_REF:
+#endif // defined(GL_ALPHA_TEST_REF)
+#if defined(GL_BLEND_DST)
+ case GL_BLEND_DST:
+#endif // defined(GL_BLEND_DST)
+#if defined(GL_BLUE_BITS)
+ case GL_BLUE_BITS:
+#endif // defined(GL_BLUE_BITS)
+#if defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+ case GL_COLOR_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+#if defined(GL_COLOR_ARRAY_SIZE)
+ case GL_COLOR_ARRAY_SIZE:
+#endif // defined(GL_COLOR_ARRAY_SIZE)
+#if defined(GL_COLOR_ARRAY_STRIDE)
+ case GL_COLOR_ARRAY_STRIDE:
+#endif // defined(GL_COLOR_ARRAY_STRIDE)
+#if defined(GL_COLOR_ARRAY_TYPE)
+ case GL_COLOR_ARRAY_TYPE:
+#endif // defined(GL_COLOR_ARRAY_TYPE)
+#if defined(GL_CULL_FACE)
+ case GL_CULL_FACE:
+#endif // defined(GL_CULL_FACE)
+#if defined(GL_DEPTH_BITS)
+ case GL_DEPTH_BITS:
+#endif // defined(GL_DEPTH_BITS)
+#if defined(GL_DEPTH_CLEAR_VALUE)
+ case GL_DEPTH_CLEAR_VALUE:
+#endif // defined(GL_DEPTH_CLEAR_VALUE)
+#if defined(GL_DEPTH_FUNC)
+ case GL_DEPTH_FUNC:
+#endif // defined(GL_DEPTH_FUNC)
+#if defined(GL_DEPTH_WRITEMASK)
+ case GL_DEPTH_WRITEMASK:
+#endif // defined(GL_DEPTH_WRITEMASK)
+#if defined(GL_FOG_DENSITY)
+ case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_END)
+ case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+#if defined(GL_FOG_MODE)
+ case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_START)
+ case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FRONT_FACE)
+ case GL_FRONT_FACE:
+#endif // defined(GL_FRONT_FACE)
+#if defined(GL_GREEN_BITS)
+ case GL_GREEN_BITS:
+#endif // defined(GL_GREEN_BITS)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+ case GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+ case GL_IMPLEMENTATION_COLOR_READ_TYPE_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+#if defined(GL_LIGHT_MODEL_COLOR_CONTROL)
+ case GL_LIGHT_MODEL_COLOR_CONTROL:
+#endif // defined(GL_LIGHT_MODEL_COLOR_CONTROL)
+#if defined(GL_LIGHT_MODEL_LOCAL_VIEWER)
+ case GL_LIGHT_MODEL_LOCAL_VIEWER:
+#endif // defined(GL_LIGHT_MODEL_LOCAL_VIEWER)
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+ case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+#if defined(GL_LINE_SMOOTH_HINT)
+ case GL_LINE_SMOOTH_HINT:
+#endif // defined(GL_LINE_SMOOTH_HINT)
+#if defined(GL_LINE_WIDTH)
+ case GL_LINE_WIDTH:
+#endif // defined(GL_LINE_WIDTH)
+#if defined(GL_LOGIC_OP_MODE)
+ case GL_LOGIC_OP_MODE:
+#endif // defined(GL_LOGIC_OP_MODE)
+#if defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+ case GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+ case GL_MATRIX_INDEX_ARRAY_SIZE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+ case GL_MATRIX_INDEX_ARRAY_STRIDE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+ case GL_MATRIX_INDEX_ARRAY_TYPE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+#if defined(GL_MATRIX_MODE)
+ case GL_MATRIX_MODE:
+#endif // defined(GL_MATRIX_MODE)
+#if defined(GL_MAX_CLIP_PLANES)
+ case GL_MAX_CLIP_PLANES:
+#endif // defined(GL_MAX_CLIP_PLANES)
+#if defined(GL_MAX_ELEMENTS_INDICES)
+ case GL_MAX_ELEMENTS_INDICES:
+#endif // defined(GL_MAX_ELEMENTS_INDICES)
+#if defined(GL_MAX_ELEMENTS_VERTICES)
+ case GL_MAX_ELEMENTS_VERTICES:
+#endif // defined(GL_MAX_ELEMENTS_VERTICES)
+#if defined(GL_MAX_LIGHTS)
+ case GL_MAX_LIGHTS:
+#endif // defined(GL_MAX_LIGHTS)
+#if defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+ case GL_MAX_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+#if defined(GL_MAX_PALETTE_MATRICES_OES)
+ case GL_MAX_PALETTE_MATRICES_OES:
+#endif // defined(GL_MAX_PALETTE_MATRICES_OES)
+#if defined(GL_MAX_PROJECTION_STACK_DEPTH)
+ case GL_MAX_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_MAX_PROJECTION_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_SIZE)
+ case GL_MAX_TEXTURE_SIZE:
+#endif // defined(GL_MAX_TEXTURE_SIZE)
+#if defined(GL_MAX_TEXTURE_STACK_DEPTH)
+ case GL_MAX_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_MAX_TEXTURE_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_UNITS)
+ case GL_MAX_TEXTURE_UNITS:
+#endif // defined(GL_MAX_TEXTURE_UNITS)
+#if defined(GL_MAX_VERTEX_UNITS_OES)
+ case GL_MAX_VERTEX_UNITS_OES:
+#endif // defined(GL_MAX_VERTEX_UNITS_OES)
+#if defined(GL_MODELVIEW_STACK_DEPTH)
+ case GL_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MODELVIEW_STACK_DEPTH)
+#if defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+ case GL_NORMAL_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+#if defined(GL_NORMAL_ARRAY_STRIDE)
+ case GL_NORMAL_ARRAY_STRIDE:
+#endif // defined(GL_NORMAL_ARRAY_STRIDE)
+#if defined(GL_NORMAL_ARRAY_TYPE)
+ case GL_NORMAL_ARRAY_TYPE:
+#endif // defined(GL_NORMAL_ARRAY_TYPE)
+#if defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+ case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_PACK_ALIGNMENT)
+ case GL_PACK_ALIGNMENT:
+#endif // defined(GL_PACK_ALIGNMENT)
+#if defined(GL_PERSPECTIVE_CORRECTION_HINT)
+ case GL_PERSPECTIVE_CORRECTION_HINT:
+#endif // defined(GL_PERSPECTIVE_CORRECTION_HINT)
+#if defined(GL_POINT_SIZE)
+ case GL_POINT_SIZE:
+#endif // defined(GL_POINT_SIZE)
+#if defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+ case GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+ case GL_POINT_SIZE_ARRAY_STRIDE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+#if defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+ case GL_POINT_SIZE_ARRAY_TYPE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+#if defined(GL_POINT_SMOOTH_HINT)
+ case GL_POINT_SMOOTH_HINT:
+#endif // defined(GL_POINT_SMOOTH_HINT)
+#if defined(GL_POLYGON_OFFSET_FACTOR)
+ case GL_POLYGON_OFFSET_FACTOR:
+#endif // defined(GL_POLYGON_OFFSET_FACTOR)
+#if defined(GL_POLYGON_OFFSET_UNITS)
+ case GL_POLYGON_OFFSET_UNITS:
+#endif // defined(GL_POLYGON_OFFSET_UNITS)
+#if defined(GL_PROJECTION_STACK_DEPTH)
+ case GL_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_PROJECTION_STACK_DEPTH)
+#if defined(GL_RED_BITS)
+ case GL_RED_BITS:
+#endif // defined(GL_RED_BITS)
+#if defined(GL_SHADE_MODEL)
+ case GL_SHADE_MODEL:
+#endif // defined(GL_SHADE_MODEL)
+#if defined(GL_STENCIL_BITS)
+ case GL_STENCIL_BITS:
+#endif // defined(GL_STENCIL_BITS)
+#if defined(GL_STENCIL_CLEAR_VALUE)
+ case GL_STENCIL_CLEAR_VALUE:
+#endif // defined(GL_STENCIL_CLEAR_VALUE)
+#if defined(GL_STENCIL_FAIL)
+ case GL_STENCIL_FAIL:
+#endif // defined(GL_STENCIL_FAIL)
+#if defined(GL_STENCIL_FUNC)
+ case GL_STENCIL_FUNC:
+#endif // defined(GL_STENCIL_FUNC)
+#if defined(GL_STENCIL_PASS_DEPTH_FAIL)
+ case GL_STENCIL_PASS_DEPTH_FAIL:
+#endif // defined(GL_STENCIL_PASS_DEPTH_FAIL)
+#if defined(GL_STENCIL_PASS_DEPTH_PASS)
+ case GL_STENCIL_PASS_DEPTH_PASS:
+#endif // defined(GL_STENCIL_PASS_DEPTH_PASS)
+#if defined(GL_STENCIL_REF)
+ case GL_STENCIL_REF:
+#endif // defined(GL_STENCIL_REF)
+#if defined(GL_STENCIL_VALUE_MASK)
+ case GL_STENCIL_VALUE_MASK:
+#endif // defined(GL_STENCIL_VALUE_MASK)
+#if defined(GL_STENCIL_WRITEMASK)
+ case GL_STENCIL_WRITEMASK:
+#endif // defined(GL_STENCIL_WRITEMASK)
+#if defined(GL_SUBPIXEL_BITS)
+ case GL_SUBPIXEL_BITS:
+#endif // defined(GL_SUBPIXEL_BITS)
+#if defined(GL_TEXTURE_BINDING_2D)
+ case GL_TEXTURE_BINDING_2D:
+#endif // defined(GL_TEXTURE_BINDING_2D)
+#if defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+ case GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+#if defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+ case GL_TEXTURE_COORD_ARRAY_SIZE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+#if defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+ case GL_TEXTURE_COORD_ARRAY_STRIDE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+#if defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+ case GL_TEXTURE_COORD_ARRAY_TYPE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+#if defined(GL_TEXTURE_STACK_DEPTH)
+ case GL_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_TEXTURE_STACK_DEPTH)
+#if defined(GL_UNPACK_ALIGNMENT)
+ case GL_UNPACK_ALIGNMENT:
+#endif // defined(GL_UNPACK_ALIGNMENT)
+#if defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+ case GL_VERTEX_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+#if defined(GL_VERTEX_ARRAY_SIZE)
+ case GL_VERTEX_ARRAY_SIZE:
+#endif // defined(GL_VERTEX_ARRAY_SIZE)
+#if defined(GL_VERTEX_ARRAY_STRIDE)
+ case GL_VERTEX_ARRAY_STRIDE:
+#endif // defined(GL_VERTEX_ARRAY_STRIDE)
+#if defined(GL_VERTEX_ARRAY_TYPE)
+ case GL_VERTEX_ARRAY_TYPE:
+#endif // defined(GL_VERTEX_ARRAY_TYPE)
+#if defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+ case GL_WEIGHT_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_WEIGHT_ARRAY_SIZE_OES)
+ case GL_WEIGHT_ARRAY_SIZE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_SIZE_OES)
+#if defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+ case GL_WEIGHT_ARRAY_STRIDE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+#if defined(GL_WEIGHT_ARRAY_TYPE_OES)
+ case GL_WEIGHT_ARRAY_TYPE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_TYPE_OES)
+ _needed = 1;
+ break;
+#if defined(GL_ALIASED_POINT_SIZE_RANGE)
+ case GL_ALIASED_POINT_SIZE_RANGE:
+#endif // defined(GL_ALIASED_POINT_SIZE_RANGE)
+#if defined(GL_ALIASED_LINE_WIDTH_RANGE)
+ case GL_ALIASED_LINE_WIDTH_RANGE:
+#endif // defined(GL_ALIASED_LINE_WIDTH_RANGE)
+#if defined(GL_DEPTH_RANGE)
+ case GL_DEPTH_RANGE:
+#endif // defined(GL_DEPTH_RANGE)
+#if defined(GL_MAX_VIEWPORT_DIMS)
+ case GL_MAX_VIEWPORT_DIMS:
+#endif // defined(GL_MAX_VIEWPORT_DIMS)
+#if defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+ case GL_SMOOTH_LINE_WIDTH_RANGE:
+#endif // defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+#if defined(GL_SMOOTH_POINT_SIZE_RANGE)
+ case GL_SMOOTH_POINT_SIZE_RANGE:
+#endif // defined(GL_SMOOTH_POINT_SIZE_RANGE)
+ _needed = 2;
+ break;
+#if defined(GL_COLOR_CLEAR_VALUE)
+ case GL_COLOR_CLEAR_VALUE:
+#endif // defined(GL_COLOR_CLEAR_VALUE)
+#if defined(GL_COLOR_WRITEMASK)
+ case GL_COLOR_WRITEMASK:
+#endif // defined(GL_COLOR_WRITEMASK)
+#if defined(GL_FOG_COLOR)
+ case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+ case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+#if defined(GL_SCISSOR_BOX)
+ case GL_SCISSOR_BOX:
+#endif // defined(GL_SCISSOR_BOX)
+#if defined(GL_VIEWPORT)
+ case GL_VIEWPORT:
+#endif // defined(GL_VIEWPORT)
+ _needed = 4;
+ break;
+#if defined(GL_MODELVIEW_MATRIX)
+ case GL_MODELVIEW_MATRIX:
+#endif // defined(GL_MODELVIEW_MATRIX)
+#if defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_PROJECTION_MATRIX)
+ case GL_PROJECTION_MATRIX:
+#endif // defined(GL_PROJECTION_MATRIX)
+#if defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_TEXTURE_MATRIX)
+ case GL_TEXTURE_MATRIX:
+#endif // defined(GL_TEXTURE_MATRIX)
+#if defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+ _needed = 16;
+ break;
+#if defined(GL_COMPRESSED_TEXTURE_FORMATS)
+ case GL_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_COMPRESSED_TEXTURE_FORMATS)
+ _needed = getNumCompressedTextureFormats();
+ break;
+ default:
+ _needed = 0;
+ break;
+ }
+ if (_remaining < _needed) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < needed");
+ goto exit;
+ }
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetIntegerv(
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetIntegerv ( GLenum pname, GLint *params ) */
+static void
+android_glGetIntegerv__ILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ int _needed;
+ switch (pname) {
+#if defined(GL_ALPHA_BITS)
+ case GL_ALPHA_BITS:
+#endif // defined(GL_ALPHA_BITS)
+#if defined(GL_ALPHA_TEST_FUNC)
+ case GL_ALPHA_TEST_FUNC:
+#endif // defined(GL_ALPHA_TEST_FUNC)
+#if defined(GL_ALPHA_TEST_REF)
+ case GL_ALPHA_TEST_REF:
+#endif // defined(GL_ALPHA_TEST_REF)
+#if defined(GL_BLEND_DST)
+ case GL_BLEND_DST:
+#endif // defined(GL_BLEND_DST)
+#if defined(GL_BLUE_BITS)
+ case GL_BLUE_BITS:
+#endif // defined(GL_BLUE_BITS)
+#if defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+ case GL_COLOR_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_COLOR_ARRAY_BUFFER_BINDING)
+#if defined(GL_COLOR_ARRAY_SIZE)
+ case GL_COLOR_ARRAY_SIZE:
+#endif // defined(GL_COLOR_ARRAY_SIZE)
+#if defined(GL_COLOR_ARRAY_STRIDE)
+ case GL_COLOR_ARRAY_STRIDE:
+#endif // defined(GL_COLOR_ARRAY_STRIDE)
+#if defined(GL_COLOR_ARRAY_TYPE)
+ case GL_COLOR_ARRAY_TYPE:
+#endif // defined(GL_COLOR_ARRAY_TYPE)
+#if defined(GL_CULL_FACE)
+ case GL_CULL_FACE:
+#endif // defined(GL_CULL_FACE)
+#if defined(GL_DEPTH_BITS)
+ case GL_DEPTH_BITS:
+#endif // defined(GL_DEPTH_BITS)
+#if defined(GL_DEPTH_CLEAR_VALUE)
+ case GL_DEPTH_CLEAR_VALUE:
+#endif // defined(GL_DEPTH_CLEAR_VALUE)
+#if defined(GL_DEPTH_FUNC)
+ case GL_DEPTH_FUNC:
+#endif // defined(GL_DEPTH_FUNC)
+#if defined(GL_DEPTH_WRITEMASK)
+ case GL_DEPTH_WRITEMASK:
+#endif // defined(GL_DEPTH_WRITEMASK)
+#if defined(GL_FOG_DENSITY)
+ case GL_FOG_DENSITY:
+#endif // defined(GL_FOG_DENSITY)
+#if defined(GL_FOG_END)
+ case GL_FOG_END:
+#endif // defined(GL_FOG_END)
+#if defined(GL_FOG_MODE)
+ case GL_FOG_MODE:
+#endif // defined(GL_FOG_MODE)
+#if defined(GL_FOG_START)
+ case GL_FOG_START:
+#endif // defined(GL_FOG_START)
+#if defined(GL_FRONT_FACE)
+ case GL_FRONT_FACE:
+#endif // defined(GL_FRONT_FACE)
+#if defined(GL_GREEN_BITS)
+ case GL_GREEN_BITS:
+#endif // defined(GL_GREEN_BITS)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+ case GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES)
+#if defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+ case GL_IMPLEMENTATION_COLOR_READ_TYPE_OES:
+#endif // defined(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES)
+#if defined(GL_LIGHT_MODEL_COLOR_CONTROL)
+ case GL_LIGHT_MODEL_COLOR_CONTROL:
+#endif // defined(GL_LIGHT_MODEL_COLOR_CONTROL)
+#if defined(GL_LIGHT_MODEL_LOCAL_VIEWER)
+ case GL_LIGHT_MODEL_LOCAL_VIEWER:
+#endif // defined(GL_LIGHT_MODEL_LOCAL_VIEWER)
+#if defined(GL_LIGHT_MODEL_TWO_SIDE)
+ case GL_LIGHT_MODEL_TWO_SIDE:
+#endif // defined(GL_LIGHT_MODEL_TWO_SIDE)
+#if defined(GL_LINE_SMOOTH_HINT)
+ case GL_LINE_SMOOTH_HINT:
+#endif // defined(GL_LINE_SMOOTH_HINT)
+#if defined(GL_LINE_WIDTH)
+ case GL_LINE_WIDTH:
+#endif // defined(GL_LINE_WIDTH)
+#if defined(GL_LOGIC_OP_MODE)
+ case GL_LOGIC_OP_MODE:
+#endif // defined(GL_LOGIC_OP_MODE)
+#if defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+ case GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+ case GL_MATRIX_INDEX_ARRAY_SIZE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_SIZE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+ case GL_MATRIX_INDEX_ARRAY_STRIDE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_STRIDE_OES)
+#if defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+ case GL_MATRIX_INDEX_ARRAY_TYPE_OES:
+#endif // defined(GL_MATRIX_INDEX_ARRAY_TYPE_OES)
+#if defined(GL_MATRIX_MODE)
+ case GL_MATRIX_MODE:
+#endif // defined(GL_MATRIX_MODE)
+#if defined(GL_MAX_CLIP_PLANES)
+ case GL_MAX_CLIP_PLANES:
+#endif // defined(GL_MAX_CLIP_PLANES)
+#if defined(GL_MAX_ELEMENTS_INDICES)
+ case GL_MAX_ELEMENTS_INDICES:
+#endif // defined(GL_MAX_ELEMENTS_INDICES)
+#if defined(GL_MAX_ELEMENTS_VERTICES)
+ case GL_MAX_ELEMENTS_VERTICES:
+#endif // defined(GL_MAX_ELEMENTS_VERTICES)
+#if defined(GL_MAX_LIGHTS)
+ case GL_MAX_LIGHTS:
+#endif // defined(GL_MAX_LIGHTS)
+#if defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+ case GL_MAX_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MAX_MODELVIEW_STACK_DEPTH)
+#if defined(GL_MAX_PALETTE_MATRICES_OES)
+ case GL_MAX_PALETTE_MATRICES_OES:
+#endif // defined(GL_MAX_PALETTE_MATRICES_OES)
+#if defined(GL_MAX_PROJECTION_STACK_DEPTH)
+ case GL_MAX_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_MAX_PROJECTION_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_SIZE)
+ case GL_MAX_TEXTURE_SIZE:
+#endif // defined(GL_MAX_TEXTURE_SIZE)
+#if defined(GL_MAX_TEXTURE_STACK_DEPTH)
+ case GL_MAX_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_MAX_TEXTURE_STACK_DEPTH)
+#if defined(GL_MAX_TEXTURE_UNITS)
+ case GL_MAX_TEXTURE_UNITS:
+#endif // defined(GL_MAX_TEXTURE_UNITS)
+#if defined(GL_MAX_VERTEX_UNITS_OES)
+ case GL_MAX_VERTEX_UNITS_OES:
+#endif // defined(GL_MAX_VERTEX_UNITS_OES)
+#if defined(GL_MODELVIEW_STACK_DEPTH)
+ case GL_MODELVIEW_STACK_DEPTH:
+#endif // defined(GL_MODELVIEW_STACK_DEPTH)
+#if defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+ case GL_NORMAL_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_NORMAL_ARRAY_BUFFER_BINDING)
+#if defined(GL_NORMAL_ARRAY_STRIDE)
+ case GL_NORMAL_ARRAY_STRIDE:
+#endif // defined(GL_NORMAL_ARRAY_STRIDE)
+#if defined(GL_NORMAL_ARRAY_TYPE)
+ case GL_NORMAL_ARRAY_TYPE:
+#endif // defined(GL_NORMAL_ARRAY_TYPE)
+#if defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+ case GL_NUM_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_NUM_COMPRESSED_TEXTURE_FORMATS)
+#if defined(GL_PACK_ALIGNMENT)
+ case GL_PACK_ALIGNMENT:
+#endif // defined(GL_PACK_ALIGNMENT)
+#if defined(GL_PERSPECTIVE_CORRECTION_HINT)
+ case GL_PERSPECTIVE_CORRECTION_HINT:
+#endif // defined(GL_PERSPECTIVE_CORRECTION_HINT)
+#if defined(GL_POINT_SIZE)
+ case GL_POINT_SIZE:
+#endif // defined(GL_POINT_SIZE)
+#if defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+ case GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+ case GL_POINT_SIZE_ARRAY_STRIDE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_STRIDE_OES)
+#if defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+ case GL_POINT_SIZE_ARRAY_TYPE_OES:
+#endif // defined(GL_POINT_SIZE_ARRAY_TYPE_OES)
+#if defined(GL_POINT_SMOOTH_HINT)
+ case GL_POINT_SMOOTH_HINT:
+#endif // defined(GL_POINT_SMOOTH_HINT)
+#if defined(GL_POLYGON_OFFSET_FACTOR)
+ case GL_POLYGON_OFFSET_FACTOR:
+#endif // defined(GL_POLYGON_OFFSET_FACTOR)
+#if defined(GL_POLYGON_OFFSET_UNITS)
+ case GL_POLYGON_OFFSET_UNITS:
+#endif // defined(GL_POLYGON_OFFSET_UNITS)
+#if defined(GL_PROJECTION_STACK_DEPTH)
+ case GL_PROJECTION_STACK_DEPTH:
+#endif // defined(GL_PROJECTION_STACK_DEPTH)
+#if defined(GL_RED_BITS)
+ case GL_RED_BITS:
+#endif // defined(GL_RED_BITS)
+#if defined(GL_SHADE_MODEL)
+ case GL_SHADE_MODEL:
+#endif // defined(GL_SHADE_MODEL)
+#if defined(GL_STENCIL_BITS)
+ case GL_STENCIL_BITS:
+#endif // defined(GL_STENCIL_BITS)
+#if defined(GL_STENCIL_CLEAR_VALUE)
+ case GL_STENCIL_CLEAR_VALUE:
+#endif // defined(GL_STENCIL_CLEAR_VALUE)
+#if defined(GL_STENCIL_FAIL)
+ case GL_STENCIL_FAIL:
+#endif // defined(GL_STENCIL_FAIL)
+#if defined(GL_STENCIL_FUNC)
+ case GL_STENCIL_FUNC:
+#endif // defined(GL_STENCIL_FUNC)
+#if defined(GL_STENCIL_PASS_DEPTH_FAIL)
+ case GL_STENCIL_PASS_DEPTH_FAIL:
+#endif // defined(GL_STENCIL_PASS_DEPTH_FAIL)
+#if defined(GL_STENCIL_PASS_DEPTH_PASS)
+ case GL_STENCIL_PASS_DEPTH_PASS:
+#endif // defined(GL_STENCIL_PASS_DEPTH_PASS)
+#if defined(GL_STENCIL_REF)
+ case GL_STENCIL_REF:
+#endif // defined(GL_STENCIL_REF)
+#if defined(GL_STENCIL_VALUE_MASK)
+ case GL_STENCIL_VALUE_MASK:
+#endif // defined(GL_STENCIL_VALUE_MASK)
+#if defined(GL_STENCIL_WRITEMASK)
+ case GL_STENCIL_WRITEMASK:
+#endif // defined(GL_STENCIL_WRITEMASK)
+#if defined(GL_SUBPIXEL_BITS)
+ case GL_SUBPIXEL_BITS:
+#endif // defined(GL_SUBPIXEL_BITS)
+#if defined(GL_TEXTURE_BINDING_2D)
+ case GL_TEXTURE_BINDING_2D:
+#endif // defined(GL_TEXTURE_BINDING_2D)
+#if defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+ case GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING)
+#if defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+ case GL_TEXTURE_COORD_ARRAY_SIZE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_SIZE)
+#if defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+ case GL_TEXTURE_COORD_ARRAY_STRIDE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_STRIDE)
+#if defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+ case GL_TEXTURE_COORD_ARRAY_TYPE:
+#endif // defined(GL_TEXTURE_COORD_ARRAY_TYPE)
+#if defined(GL_TEXTURE_STACK_DEPTH)
+ case GL_TEXTURE_STACK_DEPTH:
+#endif // defined(GL_TEXTURE_STACK_DEPTH)
+#if defined(GL_UNPACK_ALIGNMENT)
+ case GL_UNPACK_ALIGNMENT:
+#endif // defined(GL_UNPACK_ALIGNMENT)
+#if defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+ case GL_VERTEX_ARRAY_BUFFER_BINDING:
+#endif // defined(GL_VERTEX_ARRAY_BUFFER_BINDING)
+#if defined(GL_VERTEX_ARRAY_SIZE)
+ case GL_VERTEX_ARRAY_SIZE:
+#endif // defined(GL_VERTEX_ARRAY_SIZE)
+#if defined(GL_VERTEX_ARRAY_STRIDE)
+ case GL_VERTEX_ARRAY_STRIDE:
+#endif // defined(GL_VERTEX_ARRAY_STRIDE)
+#if defined(GL_VERTEX_ARRAY_TYPE)
+ case GL_VERTEX_ARRAY_TYPE:
+#endif // defined(GL_VERTEX_ARRAY_TYPE)
+#if defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+ case GL_WEIGHT_ARRAY_BUFFER_BINDING_OES:
+#endif // defined(GL_WEIGHT_ARRAY_BUFFER_BINDING_OES)
+#if defined(GL_WEIGHT_ARRAY_SIZE_OES)
+ case GL_WEIGHT_ARRAY_SIZE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_SIZE_OES)
+#if defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+ case GL_WEIGHT_ARRAY_STRIDE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_STRIDE_OES)
+#if defined(GL_WEIGHT_ARRAY_TYPE_OES)
+ case GL_WEIGHT_ARRAY_TYPE_OES:
+#endif // defined(GL_WEIGHT_ARRAY_TYPE_OES)
+ _needed = 1;
+ break;
+#if defined(GL_ALIASED_POINT_SIZE_RANGE)
+ case GL_ALIASED_POINT_SIZE_RANGE:
+#endif // defined(GL_ALIASED_POINT_SIZE_RANGE)
+#if defined(GL_ALIASED_LINE_WIDTH_RANGE)
+ case GL_ALIASED_LINE_WIDTH_RANGE:
+#endif // defined(GL_ALIASED_LINE_WIDTH_RANGE)
+#if defined(GL_DEPTH_RANGE)
+ case GL_DEPTH_RANGE:
+#endif // defined(GL_DEPTH_RANGE)
+#if defined(GL_MAX_VIEWPORT_DIMS)
+ case GL_MAX_VIEWPORT_DIMS:
+#endif // defined(GL_MAX_VIEWPORT_DIMS)
+#if defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+ case GL_SMOOTH_LINE_WIDTH_RANGE:
+#endif // defined(GL_SMOOTH_LINE_WIDTH_RANGE)
+#if defined(GL_SMOOTH_POINT_SIZE_RANGE)
+ case GL_SMOOTH_POINT_SIZE_RANGE:
+#endif // defined(GL_SMOOTH_POINT_SIZE_RANGE)
+ _needed = 2;
+ break;
+#if defined(GL_COLOR_CLEAR_VALUE)
+ case GL_COLOR_CLEAR_VALUE:
+#endif // defined(GL_COLOR_CLEAR_VALUE)
+#if defined(GL_COLOR_WRITEMASK)
+ case GL_COLOR_WRITEMASK:
+#endif // defined(GL_COLOR_WRITEMASK)
+#if defined(GL_FOG_COLOR)
+ case GL_FOG_COLOR:
+#endif // defined(GL_FOG_COLOR)
+#if defined(GL_LIGHT_MODEL_AMBIENT)
+ case GL_LIGHT_MODEL_AMBIENT:
+#endif // defined(GL_LIGHT_MODEL_AMBIENT)
+#if defined(GL_SCISSOR_BOX)
+ case GL_SCISSOR_BOX:
+#endif // defined(GL_SCISSOR_BOX)
+#if defined(GL_VIEWPORT)
+ case GL_VIEWPORT:
+#endif // defined(GL_VIEWPORT)
+ _needed = 4;
+ break;
+#if defined(GL_MODELVIEW_MATRIX)
+ case GL_MODELVIEW_MATRIX:
+#endif // defined(GL_MODELVIEW_MATRIX)
+#if defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_MODELVIEW_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_PROJECTION_MATRIX)
+ case GL_PROJECTION_MATRIX:
+#endif // defined(GL_PROJECTION_MATRIX)
+#if defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_PROJECTION_MATRIX_FLOAT_AS_INT_BITS_OES)
+#if defined(GL_TEXTURE_MATRIX)
+ case GL_TEXTURE_MATRIX:
+#endif // defined(GL_TEXTURE_MATRIX)
+#if defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+ case GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES:
+#endif // defined(GL_TEXTURE_MATRIX_FLOAT_AS_INT_BITS_OES)
+ _needed = 16;
+ break;
+#if defined(GL_COMPRESSED_TEXTURE_FORMATS)
+ case GL_COMPRESSED_TEXTURE_FORMATS:
+#endif // defined(GL_COMPRESSED_TEXTURE_FORMATS)
+ _needed = getNumCompressedTextureFormats();
+ break;
+ default:
+ _needed = 0;
+ break;
+ }
+ if (_remaining < _needed) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < needed");
+ goto exit;
+ }
+ glGetIntegerv(
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetProgramiv ( GLuint program, GLenum pname, GLint *params ) */
+static void
+android_glGetProgramiv__II_3II
+ (JNIEnv *_env, jobject _this, jint program, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetProgramiv(
+ (GLuint)program,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetProgramiv ( GLuint program, GLenum pname, GLint *params ) */
+static void
+android_glGetProgramiv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint program, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetProgramiv(
+ (GLuint)program,
+ (GLenum)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+#include <string.h>
+
+/* void glGetProgramInfoLog ( GLuint shader, GLsizei maxLength, GLsizei* length, GLchar* infoLog ) */
+static
+jstring
+android_glGetProgramInfoLog (JNIEnv *_env, jobject _this, jint shader) {
+ GLint infoLen = 0;
+ jstring _result = 0;
+ char* buf = 0;
+ glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf == 0) {
+ _env->ThrowNew(IAEClass, "out of memory");
+ goto exit;
+ }
+ glGetProgramInfoLog(shader, infoLen, NULL, buf);
+ _result = _env->NewStringUTF(buf);
+ } else {
+ _result = _env->NewStringUTF("");
+ }
+exit:
+ if (buf) {
+ free(buf);
+ }
+ return _result;
+}
+/* void glGetRenderbufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetRenderbufferParameteriv__II_3II
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetRenderbufferParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetRenderbufferParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetRenderbufferParameteriv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetRenderbufferParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetShaderiv ( GLuint shader, GLenum pname, GLint *params ) */
+static void
+android_glGetShaderiv__II_3II
+ (JNIEnv *_env, jobject _this, jint shader, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetShaderiv(
+ (GLuint)shader,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetShaderiv ( GLuint shader, GLenum pname, GLint *params ) */
+static void
+android_glGetShaderiv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint shader, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetShaderiv(
+ (GLuint)shader,
+ (GLenum)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+#include <string.h>
+
+/* void glGetShaderInfoLog ( GLuint shader, GLsizei maxLength, GLsizei* length, GLchar* infoLog ) */
+static
+jstring
+android_glGetShaderInfoLog (JNIEnv *_env, jobject _this, jint shader) {
+ GLint infoLen = 0;
+ jstring _result = 0;
+ char* buf = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf == 0) {
+ _env->ThrowNew(IAEClass, "out of memory");
+ goto exit;
+ }
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ _result = _env->NewStringUTF(buf);
+ } else {
+ _result = _env->NewStringUTF("");
+ }
+exit:
+ if (buf) {
+ free(buf);
+ }
+ return _result;
+}
+/* void glGetShaderPrecisionFormat ( GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision ) */
+static void
+android_glGetShaderPrecisionFormat__II_3II_3II
+ (JNIEnv *_env, jobject _this, jint shadertype, jint precisiontype, jintArray range_ref, jint rangeOffset, jintArray precision_ref, jint precisionOffset) {
+ jint _exception = 0;
+ GLint *range_base = (GLint *) 0;
+ jint _rangeRemaining;
+ GLint *range = (GLint *) 0;
+ GLint *precision_base = (GLint *) 0;
+ jint _precisionRemaining;
+ GLint *precision = (GLint *) 0;
+
+ if (!range_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "range == null");
+ goto exit;
+ }
+ if (rangeOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "rangeOffset < 0");
+ goto exit;
+ }
+ _rangeRemaining = _env->GetArrayLength(range_ref) - rangeOffset;
+ range_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(range_ref, (jboolean *)0);
+ range = range_base + rangeOffset;
+
+ if (!precision_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "precision == null");
+ goto exit;
+ }
+ if (precisionOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "precisionOffset < 0");
+ goto exit;
+ }
+ _precisionRemaining = _env->GetArrayLength(precision_ref) - precisionOffset;
+ precision_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(precision_ref, (jboolean *)0);
+ precision = precision_base + precisionOffset;
+
+ glGetShaderPrecisionFormat(
+ (GLenum)shadertype,
+ (GLenum)precisiontype,
+ (GLint *)range,
+ (GLint *)precision
+ );
+
+exit:
+ if (precision_base) {
+ _env->ReleasePrimitiveArrayCritical(precision_ref, precision_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (range_base) {
+ _env->ReleasePrimitiveArrayCritical(range_ref, range_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetShaderPrecisionFormat ( GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision ) */
+static void
+android_glGetShaderPrecisionFormat__IILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint shadertype, jint precisiontype, jobject range_buf, jobject precision_buf) {
+ jint _exception = 0;
+ jarray _rangeArray = (jarray) 0;
+ jarray _precisionArray = (jarray) 0;
+ jint _rangeRemaining;
+ GLint *range = (GLint *) 0;
+ jint _precisionRemaining;
+ GLint *precision = (GLint *) 0;
+
+ range = (GLint *)getPointer(_env, range_buf, &_rangeArray, &_rangeRemaining);
+ precision = (GLint *)getPointer(_env, precision_buf, &_precisionArray, &_precisionRemaining);
+ glGetShaderPrecisionFormat(
+ (GLenum)shadertype,
+ (GLenum)precisiontype,
+ (GLint *)range,
+ (GLint *)precision
+ );
+ if (_rangeArray) {
+ releasePointer(_env, _rangeArray, precision, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+ if (_precisionArray) {
+ releasePointer(_env, _precisionArray, range, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetShaderSource ( GLuint shader, GLsizei bufsize, GLsizei *length, char *source ) */
+static void
+android_glGetShaderSource__II_3II_3BI
+ (JNIEnv *_env, jobject _this, jint shader, jint bufsize, jintArray length_ref, jint lengthOffset, jbyteArray source_ref, jint sourceOffset) {
+ jint _exception = 0;
+ GLsizei *length_base = (GLsizei *) 0;
+ jint _lengthRemaining;
+ GLsizei *length = (GLsizei *) 0;
+ char *source_base = (char *) 0;
+ jint _sourceRemaining;
+ char *source = (char *) 0;
+
+ if (!length_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length == null");
+ goto exit;
+ }
+ if (lengthOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "lengthOffset < 0");
+ goto exit;
+ }
+ _lengthRemaining = _env->GetArrayLength(length_ref) - lengthOffset;
+ length_base = (GLsizei *)
+ _env->GetPrimitiveArrayCritical(length_ref, (jboolean *)0);
+ length = length_base + lengthOffset;
+
+ if (!source_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "source == null");
+ goto exit;
+ }
+ if (sourceOffset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "sourceOffset < 0");
+ goto exit;
+ }
+ _sourceRemaining = _env->GetArrayLength(source_ref) - sourceOffset;
+ source_base = (char *)
+ _env->GetPrimitiveArrayCritical(source_ref, (jboolean *)0);
+ source = source_base + sourceOffset;
+
+ glGetShaderSource(
+ (GLuint)shader,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (char *)source
+ );
+
+exit:
+ if (source_base) {
+ _env->ReleasePrimitiveArrayCritical(source_ref, source_base,
+ _exception ? JNI_ABORT: 0);
+ }
+ if (length_base) {
+ _env->ReleasePrimitiveArrayCritical(length_ref, length_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetShaderSource ( GLuint shader, GLsizei bufsize, GLsizei *length, char *source ) */
+static void
+android_glGetShaderSource__IILjava_nio_IntBuffer_2B
+ (JNIEnv *_env, jobject _this, jint shader, jint bufsize, jobject length_buf, jbyte source) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLsizei *length = (GLsizei *) 0;
+
+ length = (GLsizei *)getPointer(_env, length_buf, &_array, &_remaining);
+ glGetShaderSource(
+ (GLuint)shader,
+ (GLsizei)bufsize,
+ (GLsizei *)length,
+ (char *)source
+ );
+ if (_array) {
+ releasePointer(_env, _array, length, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+#include <string.h>
+
+/* const GLubyte * glGetString ( GLenum name ) */
+static
+jstring
+android_glGetString
+ (JNIEnv *_env, jobject _this, jint name) {
+ const char * chars = (const char *)glGetString((GLenum)name);
+ jstring output = _env->NewStringUTF(chars);
+ return output;
+}
+/* void glGetTexParameterfv ( GLenum target, GLenum pname, GLfloat *params ) */
+static void
+android_glGetTexParameterfv__II_3FI
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jfloatArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ if (_remaining < 1) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < 1");
+ goto exit;
+ }
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetTexParameterfv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetTexParameterfv ( GLenum target, GLenum pname, GLfloat *params ) */
+static void
+android_glGetTexParameterfv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ if (_remaining < 1) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < 1");
+ goto exit;
+ }
+ glGetTexParameterfv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetTexParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetTexParameteriv__II_3II
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ if (_remaining < 1) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < 1");
+ goto exit;
+ }
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetTexParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetTexParameteriv ( GLenum target, GLenum pname, GLint *params ) */
+static void
+android_glGetTexParameteriv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ if (_remaining < 1) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < 1");
+ goto exit;
+ }
+ glGetTexParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetUniformfv ( GLuint program, GLint location, GLfloat *params ) */
+static void
+android_glGetUniformfv__II_3FI
+ (JNIEnv *_env, jobject _this, jint program, jint location, jfloatArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetUniformfv(
+ (GLuint)program,
+ (GLint)location,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetUniformfv ( GLuint program, GLint location, GLfloat *params ) */
+static void
+android_glGetUniformfv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint program, jint location, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetUniformfv(
+ (GLuint)program,
+ (GLint)location,
+ (GLfloat *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetUniformiv ( GLuint program, GLint location, GLint *params ) */
+static void
+android_glGetUniformiv__II_3II
+ (JNIEnv *_env, jobject _this, jint program, jint location, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetUniformiv(
+ (GLuint)program,
+ (GLint)location,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetUniformiv ( GLuint program, GLint location, GLint *params ) */
+static void
+android_glGetUniformiv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint program, jint location, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetUniformiv(
+ (GLuint)program,
+ (GLint)location,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* int glGetUniformLocation ( GLuint program, const char *name ) */
+static jint
+android_glGetUniformLocation__ILjava_lang_String_2
+ (JNIEnv *_env, jobject _this, jint program, jstring name) {
+ int _returnValue = 0;
+ const char* _nativename = 0;
+
+ if (!name) {
+ _env->ThrowNew(IAEClass, "name == null");
+ goto exit;
+ }
+ _nativename = _env->GetStringUTFChars(name, 0);
+
+ _returnValue = glGetUniformLocation(
+ (GLuint)program,
+ (char *)_nativename
+ );
+
+exit:
+ if (_nativename) {
+ _env->ReleaseStringUTFChars(name, _nativename);
+ }
+
+ return _returnValue;
+}
+
+/* void glGetVertexAttribfv ( GLuint index, GLenum pname, GLfloat *params ) */
+static void
+android_glGetVertexAttribfv__II_3FI
+ (JNIEnv *_env, jobject _this, jint index, jint pname, jfloatArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetVertexAttribfv(
+ (GLuint)index,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetVertexAttribfv ( GLuint index, GLenum pname, GLfloat *params ) */
+static void
+android_glGetVertexAttribfv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint index, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetVertexAttribfv(
+ (GLuint)index,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glGetVertexAttribiv ( GLuint index, GLenum pname, GLint *params ) */
+static void
+android_glGetVertexAttribiv__II_3II
+ (JNIEnv *_env, jobject _this, jint index, jint pname, jintArray params_ref, jint offset) {
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetVertexAttribiv(
+ (GLuint)index,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
+}
+
+/* void glGetVertexAttribiv ( GLuint index, GLenum pname, GLint *params ) */
+static void
+android_glGetVertexAttribiv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint index, jint pname, jobject params_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetVertexAttribiv(
+ (GLuint)index,
+ (GLenum)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glHint ( GLenum target, GLenum mode ) */
+static void
+android_glHint__II
+ (JNIEnv *_env, jobject _this, jint target, jint mode) {
+ glHint(
+ (GLenum)target,
+ (GLenum)mode
+ );
+}
+
+/* GLboolean glIsBuffer ( GLuint buffer ) */
+static jboolean
+android_glIsBuffer__I
+ (JNIEnv *_env, jobject _this, jint buffer) {
+ GLboolean _returnValue;
+ _returnValue = glIsBuffer(
+ (GLuint)buffer
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsEnabled ( GLenum cap ) */
+static jboolean
+android_glIsEnabled__I
+ (JNIEnv *_env, jobject _this, jint cap) {
+ GLboolean _returnValue;
+ _returnValue = glIsEnabled(
+ (GLenum)cap
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsFramebuffer ( GLuint framebuffer ) */
+static jboolean
+android_glIsFramebuffer__I
+ (JNIEnv *_env, jobject _this, jint framebuffer) {
+ GLboolean _returnValue;
+ _returnValue = glIsFramebuffer(
+ (GLuint)framebuffer
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsProgram ( GLuint program ) */
+static jboolean
+android_glIsProgram__I
+ (JNIEnv *_env, jobject _this, jint program) {
+ GLboolean _returnValue;
+ _returnValue = glIsProgram(
+ (GLuint)program
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsRenderbuffer ( GLuint renderbuffer ) */
+static jboolean
+android_glIsRenderbuffer__I
+ (JNIEnv *_env, jobject _this, jint renderbuffer) {
+ GLboolean _returnValue;
+ _returnValue = glIsRenderbuffer(
+ (GLuint)renderbuffer
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsShader ( GLuint shader ) */
+static jboolean
+android_glIsShader__I
+ (JNIEnv *_env, jobject _this, jint shader) {
+ GLboolean _returnValue;
+ _returnValue = glIsShader(
+ (GLuint)shader
+ );
+ return _returnValue;
+}
+
+/* GLboolean glIsTexture ( GLuint texture ) */
+static jboolean
+android_glIsTexture__I
+ (JNIEnv *_env, jobject _this, jint texture) {
+ GLboolean _returnValue;
+ _returnValue = glIsTexture(
+ (GLuint)texture
+ );
+ return _returnValue;
+}
+
+/* void glLineWidth ( GLfloat width ) */
+static void
+android_glLineWidth__F
+ (JNIEnv *_env, jobject _this, jfloat width) {
+ glLineWidth(
+ (GLfloat)width
+ );
+}
+
+/* void glLinkProgram ( GLuint program ) */
+static void
+android_glLinkProgram__I
+ (JNIEnv *_env, jobject _this, jint program) {
+ glLinkProgram(
+ (GLuint)program
+ );
+}
+
+/* void glPixelStorei ( GLenum pname, GLint param ) */
+static void
+android_glPixelStorei__II
+ (JNIEnv *_env, jobject _this, jint pname, jint param) {
+ glPixelStorei(
+ (GLenum)pname,
+ (GLint)param
+ );
+}
+
+/* void glPolygonOffset ( GLfloat factor, GLfloat units ) */
+static void
+android_glPolygonOffset__FF
+ (JNIEnv *_env, jobject _this, jfloat factor, jfloat units) {
+ glPolygonOffset(
+ (GLfloat)factor,
+ (GLfloat)units
+ );
+}
+
+/* void glReadPixels ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels ) */
+static void
+android_glReadPixels__IIIIIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height, jint format, jint type, jobject pixels_buf) {
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pixels = (GLvoid *) 0;
+
+ pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+ glReadPixels(
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLenum)format,
+ (GLenum)type,
+ (GLvoid *)pixels
+ );
+ if (_array) {
+ releasePointer(_env, _array, pixels, _exception ? JNI_FALSE : JNI_TRUE);
+ }
+}
+
+/* void glReleaseShaderCompiler ( void ) */
+static void
+android_glReleaseShaderCompiler__
+ (JNIEnv *_env, jobject _this) {
+ glReleaseShaderCompiler();
+}
+
+/* void glRenderbufferStorage ( GLenum target, GLenum internalformat, GLsizei width, GLsizei height ) */
+static void
+android_glRenderbufferStorage__IIII
+ (JNIEnv *_env, jobject _this, jint target, jint internalformat, jint width, jint height) {
+ glRenderbufferStorage(
+ (GLenum)target,
+ (GLenum)internalformat,
+ (GLsizei)width,
+ (GLsizei)height
+ );
+}
+
+/* void glSampleCoverage ( GLclampf value, GLboolean invert ) */
+static void
+android_glSampleCoverage__FZ
+ (JNIEnv *_env, jobject _this, jfloat value, jboolean invert) {
+ glSampleCoverage(
+ (GLclampf)value,
+ (GLboolean)invert
+ );
+}
+
+/* void glScissor ( GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glScissor__IIII
+ (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height) {
+ glScissor(
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height
+ );
+}
+
+/* void glShaderBinary ( GLsizei n, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length ) */
+static void
+android_glShaderBinary__I_3IIILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint n, jintArray shaders_ref, jint offset, jint binaryformat, jobject binary_buf, jint length) {
+ jarray _array = (jarray) 0;
+ GLuint *shaders_base = (GLuint *) 0;
+ jint _shadersRemaining;
+ GLuint *shaders = (GLuint *) 0;
+ jint _binaryRemaining;
+ GLvoid *binary = (GLvoid *) 0;
+
+ if (!shaders_ref) {
+ _env->ThrowNew(IAEClass, "shaders == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _shadersRemaining = _env->GetArrayLength(shaders_ref) - offset;
+ shaders_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(shaders_ref, (jboolean *)0);
+ shaders = shaders_base + offset;
+
+ binary = (GLvoid *)getPointer(_env, binary_buf, &_array, &_binaryRemaining);
+ glShaderBinary(
+ (GLsizei)n,
+ (GLuint *)shaders,
+ (GLenum)binaryformat,
+ (GLvoid *)binary,
+ (GLsizei)length
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, binary, JNI_FALSE);
+ }
+ if (shaders_base) {
+ _env->ReleasePrimitiveArrayCritical(shaders_ref, shaders_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glShaderBinary ( GLsizei n, const GLuint *shaders, GLenum binaryformat, const GLvoid *binary, GLsizei length ) */
+static void
+android_glShaderBinary__ILjava_nio_IntBuffer_2ILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint n, jobject shaders_buf, jint binaryformat, jobject binary_buf, jint length) {
+ jarray _shadersArray = (jarray) 0;
+ jarray _binaryArray = (jarray) 0;
+ jint _shadersRemaining;
+ GLuint *shaders = (GLuint *) 0;
+ jint _binaryRemaining;
+ GLvoid *binary = (GLvoid *) 0;
+
+ shaders = (GLuint *)getPointer(_env, shaders_buf, &_shadersArray, &_shadersRemaining);
+ binary = (GLvoid *)getPointer(_env, binary_buf, &_binaryArray, &_binaryRemaining);
+ glShaderBinary(
+ (GLsizei)n,
+ (GLuint *)shaders,
+ (GLenum)binaryformat,
+ (GLvoid *)binary,
+ (GLsizei)length
+ );
+ if (_shadersArray) {
+ releasePointer(_env, _shadersArray, binary, JNI_FALSE);
+ }
+ if (_binaryArray) {
+ releasePointer(_env, _binaryArray, shaders, JNI_FALSE);
+ }
+}
+
+
+/* void glShaderSource ( GLuint shader, GLsizei count, const GLchar ** string, const GLint * length ) */
+static
+void
+android_glShaderSource
+ (JNIEnv *_env, jobject _this, jint shader, jstring string) {
+
+ if (!string) {
+ _env->ThrowNew(IAEClass, "string == null");
+ return;
+ }
+
+ const char* nativeString = _env->GetStringUTFChars(string, 0);
+ const char* strings[] = {nativeString};
+ glShaderSource(shader, 1, strings, 0);
+ _env->ReleaseStringUTFChars(string, nativeString);
+}
+/* void glStencilFunc ( GLenum func, GLint ref, GLuint mask ) */
+static void
+android_glStencilFunc__III
+ (JNIEnv *_env, jobject _this, jint func, jint ref, jint mask) {
+ glStencilFunc(
+ (GLenum)func,
+ (GLint)ref,
+ (GLuint)mask
+ );
+}
+
+/* void glStencilFuncSeparate ( GLenum face, GLenum func, GLint ref, GLuint mask ) */
+static void
+android_glStencilFuncSeparate__IIII
+ (JNIEnv *_env, jobject _this, jint face, jint func, jint ref, jint mask) {
+ glStencilFuncSeparate(
+ (GLenum)face,
+ (GLenum)func,
+ (GLint)ref,
+ (GLuint)mask
+ );
+}
+
+/* void glStencilMask ( GLuint mask ) */
+static void
+android_glStencilMask__I
+ (JNIEnv *_env, jobject _this, jint mask) {
+ glStencilMask(
+ (GLuint)mask
+ );
+}
+
+/* void glStencilMaskSeparate ( GLenum face, GLuint mask ) */
+static void
+android_glStencilMaskSeparate__II
+ (JNIEnv *_env, jobject _this, jint face, jint mask) {
+ glStencilMaskSeparate(
+ (GLenum)face,
+ (GLuint)mask
+ );
+}
+
+/* void glStencilOp ( GLenum fail, GLenum zfail, GLenum zpass ) */
+static void
+android_glStencilOp__III
+ (JNIEnv *_env, jobject _this, jint fail, jint zfail, jint zpass) {
+ glStencilOp(
+ (GLenum)fail,
+ (GLenum)zfail,
+ (GLenum)zpass
+ );
+}
+
+/* void glStencilOpSeparate ( GLenum face, GLenum fail, GLenum zfail, GLenum zpass ) */
+static void
+android_glStencilOpSeparate__IIII
+ (JNIEnv *_env, jobject _this, jint face, jint fail, jint zfail, jint zpass) {
+ glStencilOpSeparate(
+ (GLenum)face,
+ (GLenum)fail,
+ (GLenum)zfail,
+ (GLenum)zpass
+ );
+}
+
+/* void glTexImage2D ( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels ) */
+static void
+android_glTexImage2D__IIIIIIIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint format, jint type, jobject pixels_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pixels = (GLvoid *) 0;
+
+ if (pixels_buf) {
+ pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+ }
+ glTexImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLint)internalformat,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLint)border,
+ (GLenum)format,
+ (GLenum)type,
+ (GLvoid *)pixels
+ );
+ if (_array) {
+ releasePointer(_env, _array, pixels, JNI_FALSE);
+ }
+}
+
+/* void glTexParameterf ( GLenum target, GLenum pname, GLfloat param ) */
+static void
+android_glTexParameterf__IIF
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jfloat param) {
+ glTexParameterf(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLfloat)param
+ );
+}
+
+/* void glTexParameterfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexParameterfv__II_3FI
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jfloatArray params_ref, jint offset) {
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ if (_remaining < 1) {
+ _env->ThrowNew(IAEClass, "length - offset < 1");
+ goto exit;
+ }
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glTexParameterfv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glTexParameterfv ( GLenum target, GLenum pname, const GLfloat *params ) */
+static void
+android_glTexParameterfv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ if (_remaining < 1) {
+ _env->ThrowNew(IAEClass, "remaining() < 1");
+ goto exit;
+ }
+ glTexParameterfv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, params, JNI_FALSE);
+ }
+}
+
+/* void glTexParameteri ( GLenum target, GLenum pname, GLint param ) */
+static void
+android_glTexParameteri__III
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jint param) {
+ glTexParameteri(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint)param
+ );
+}
+
+/* void glTexParameteriv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexParameteriv__II_3II
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ if (_remaining < 1) {
+ _env->ThrowNew(IAEClass, "length - offset < 1");
+ goto exit;
+ }
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glTexParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glTexParameteriv ( GLenum target, GLenum pname, const GLint *params ) */
+static void
+android_glTexParameteriv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ if (_remaining < 1) {
+ _env->ThrowNew(IAEClass, "remaining() < 1");
+ goto exit;
+ }
+ glTexParameteriv(
+ (GLenum)target,
+ (GLenum)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, params, JNI_FALSE);
+ }
+}
+
+/* void glTexSubImage2D ( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels ) */
+static void
+android_glTexSubImage2D__IIIIIIIILjava_nio_Buffer_2
+ (JNIEnv *_env, jobject _this, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint type, jobject pixels_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pixels = (GLvoid *) 0;
+
+ if (pixels_buf) {
+ pixels = (GLvoid *)getPointer(_env, pixels_buf, &_array, &_remaining);
+ }
+ glTexSubImage2D(
+ (GLenum)target,
+ (GLint)level,
+ (GLint)xoffset,
+ (GLint)yoffset,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLenum)format,
+ (GLenum)type,
+ (GLvoid *)pixels
+ );
+ if (_array) {
+ releasePointer(_env, _array, pixels, JNI_FALSE);
+ }
+}
+
+/* void glUniform1f ( GLint location, GLfloat x ) */
+static void
+android_glUniform1f__IF
+ (JNIEnv *_env, jobject _this, jint location, jfloat x) {
+ glUniform1f(
+ (GLint)location,
+ (GLfloat)x
+ );
+}
+
+/* void glUniform1fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform1fv__II_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jfloatArray v_ref, jint offset) {
+ GLfloat *v_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform1fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform1fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform1fv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ v = (GLfloat *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform1fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform1i ( GLint location, GLint x ) */
+static void
+android_glUniform1i__II
+ (JNIEnv *_env, jobject _this, jint location, jint x) {
+ glUniform1i(
+ (GLint)location,
+ (GLint)x
+ );
+}
+
+/* void glUniform1iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform1iv__II_3II
+ (JNIEnv *_env, jobject _this, jint location, jint count, jintArray v_ref, jint offset) {
+ GLint *v_base = (GLint *) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform1iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform1iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform1iv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ v = (GLint *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform1iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform2f ( GLint location, GLfloat x, GLfloat y ) */
+static void
+android_glUniform2f__IFF
+ (JNIEnv *_env, jobject _this, jint location, jfloat x, jfloat y) {
+ glUniform2f(
+ (GLint)location,
+ (GLfloat)x,
+ (GLfloat)y
+ );
+}
+
+/* void glUniform2fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform2fv__II_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jfloatArray v_ref, jint offset) {
+ GLfloat *v_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform2fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform2fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform2fv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ v = (GLfloat *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform2fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform2i ( GLint location, GLint x, GLint y ) */
+static void
+android_glUniform2i__III
+ (JNIEnv *_env, jobject _this, jint location, jint x, jint y) {
+ glUniform2i(
+ (GLint)location,
+ (GLint)x,
+ (GLint)y
+ );
+}
+
+/* void glUniform2iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform2iv__II_3II
+ (JNIEnv *_env, jobject _this, jint location, jint count, jintArray v_ref, jint offset) {
+ GLint *v_base = (GLint *) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform2iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform2iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform2iv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ v = (GLint *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform2iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform3f ( GLint location, GLfloat x, GLfloat y, GLfloat z ) */
+static void
+android_glUniform3f__IFFF
+ (JNIEnv *_env, jobject _this, jint location, jfloat x, jfloat y, jfloat z) {
+ glUniform3f(
+ (GLint)location,
+ (GLfloat)x,
+ (GLfloat)y,
+ (GLfloat)z
+ );
+}
+
+/* void glUniform3fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform3fv__II_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jfloatArray v_ref, jint offset) {
+ GLfloat *v_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform3fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform3fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform3fv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ v = (GLfloat *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform3fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform3i ( GLint location, GLint x, GLint y, GLint z ) */
+static void
+android_glUniform3i__IIII
+ (JNIEnv *_env, jobject _this, jint location, jint x, jint y, jint z) {
+ glUniform3i(
+ (GLint)location,
+ (GLint)x,
+ (GLint)y,
+ (GLint)z
+ );
+}
+
+/* void glUniform3iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform3iv__II_3II
+ (JNIEnv *_env, jobject _this, jint location, jint count, jintArray v_ref, jint offset) {
+ GLint *v_base = (GLint *) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform3iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform3iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform3iv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ v = (GLint *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform3iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform4f ( GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w ) */
+static void
+android_glUniform4f__IFFFF
+ (JNIEnv *_env, jobject _this, jint location, jfloat x, jfloat y, jfloat z, jfloat w) {
+ glUniform4f(
+ (GLint)location,
+ (GLfloat)x,
+ (GLfloat)y,
+ (GLfloat)z,
+ (GLfloat)w
+ );
+}
+
+/* void glUniform4fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform4fv__II_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jfloatArray v_ref, jint offset) {
+ GLfloat *v_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform4fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform4fv ( GLint location, GLsizei count, const GLfloat *v ) */
+static void
+android_glUniform4fv__IILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *v = (GLfloat *) 0;
+
+ v = (GLfloat *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform4fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLfloat *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniform4i ( GLint location, GLint x, GLint y, GLint z, GLint w ) */
+static void
+android_glUniform4i__IIIII
+ (JNIEnv *_env, jobject _this, jint location, jint x, jint y, jint z, jint w) {
+ glUniform4i(
+ (GLint)location,
+ (GLint)x,
+ (GLint)y,
+ (GLint)z,
+ (GLint)w
+ );
+}
+
+/* void glUniform4iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform4iv__II_3II
+ (JNIEnv *_env, jobject _this, jint location, jint count, jintArray v_ref, jint offset) {
+ GLint *v_base = (GLint *) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ if (!v_ref) {
+ _env->ThrowNew(IAEClass, "v == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(v_ref) - offset;
+ v_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(v_ref, (jboolean *)0);
+ v = v_base + offset;
+
+ glUniform4iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+
+exit:
+ if (v_base) {
+ _env->ReleasePrimitiveArrayCritical(v_ref, v_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniform4iv ( GLint location, GLsizei count, const GLint *v ) */
+static void
+android_glUniform4iv__IILjava_nio_IntBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jobject v_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *v = (GLint *) 0;
+
+ v = (GLint *)getPointer(_env, v_buf, &_array, &_remaining);
+ glUniform4iv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLint *)v
+ );
+ if (_array) {
+ releasePointer(_env, _array, v, JNI_FALSE);
+ }
+}
+
+/* void glUniformMatrix2fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix2fv__IIZ_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jfloatArray value_ref, jint offset) {
+ GLfloat *value_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ if (!value_ref) {
+ _env->ThrowNew(IAEClass, "value == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(value_ref) - offset;
+ value_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(value_ref, (jboolean *)0);
+ value = value_base + offset;
+
+ glUniformMatrix2fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+
+exit:
+ if (value_base) {
+ _env->ReleasePrimitiveArrayCritical(value_ref, value_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniformMatrix2fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix2fv__IIZLjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jobject value_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ value = (GLfloat *)getPointer(_env, value_buf, &_array, &_remaining);
+ glUniformMatrix2fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+ if (_array) {
+ releasePointer(_env, _array, value, JNI_FALSE);
+ }
+}
+
+/* void glUniformMatrix3fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix3fv__IIZ_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jfloatArray value_ref, jint offset) {
+ GLfloat *value_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ if (!value_ref) {
+ _env->ThrowNew(IAEClass, "value == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(value_ref) - offset;
+ value_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(value_ref, (jboolean *)0);
+ value = value_base + offset;
+
+ glUniformMatrix3fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+
+exit:
+ if (value_base) {
+ _env->ReleasePrimitiveArrayCritical(value_ref, value_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniformMatrix3fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix3fv__IIZLjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jobject value_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ value = (GLfloat *)getPointer(_env, value_buf, &_array, &_remaining);
+ glUniformMatrix3fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+ if (_array) {
+ releasePointer(_env, _array, value, JNI_FALSE);
+ }
+}
+
+/* void glUniformMatrix4fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix4fv__IIZ_3FI
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jfloatArray value_ref, jint offset) {
+ GLfloat *value_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ if (!value_ref) {
+ _env->ThrowNew(IAEClass, "value == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(value_ref) - offset;
+ value_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(value_ref, (jboolean *)0);
+ value = value_base + offset;
+
+ glUniformMatrix4fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+
+exit:
+ if (value_base) {
+ _env->ReleasePrimitiveArrayCritical(value_ref, value_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glUniformMatrix4fv ( GLint location, GLsizei count, GLboolean transpose, const GLfloat *value ) */
+static void
+android_glUniformMatrix4fv__IIZLjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint location, jint count, jboolean transpose, jobject value_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *value = (GLfloat *) 0;
+
+ value = (GLfloat *)getPointer(_env, value_buf, &_array, &_remaining);
+ glUniformMatrix4fv(
+ (GLint)location,
+ (GLsizei)count,
+ (GLboolean)transpose,
+ (GLfloat *)value
+ );
+ if (_array) {
+ releasePointer(_env, _array, value, JNI_FALSE);
+ }
+}
+
+/* void glUseProgram ( GLuint program ) */
+static void
+android_glUseProgram__I
+ (JNIEnv *_env, jobject _this, jint program) {
+ glUseProgram(
+ (GLuint)program
+ );
+}
+
+/* void glValidateProgram ( GLuint program ) */
+static void
+android_glValidateProgram__I
+ (JNIEnv *_env, jobject _this, jint program) {
+ glValidateProgram(
+ (GLuint)program
+ );
+}
+
+/* void glVertexAttrib1f ( GLuint indx, GLfloat x ) */
+static void
+android_glVertexAttrib1f__IF
+ (JNIEnv *_env, jobject _this, jint indx, jfloat x) {
+ glVertexAttrib1f(
+ (GLuint)indx,
+ (GLfloat)x
+ );
+}
+
+/* void glVertexAttrib1fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib1fv__I_3FI
+ (JNIEnv *_env, jobject _this, jint indx, jfloatArray values_ref, jint offset) {
+ GLfloat *values_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ if (!values_ref) {
+ _env->ThrowNew(IAEClass, "values == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(values_ref) - offset;
+ values_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(values_ref, (jboolean *)0);
+ values = values_base + offset;
+
+ glVertexAttrib1fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+
+exit:
+ if (values_base) {
+ _env->ReleasePrimitiveArrayCritical(values_ref, values_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glVertexAttrib1fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib1fv__ILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint indx, jobject values_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ values = (GLfloat *)getPointer(_env, values_buf, &_array, &_remaining);
+ glVertexAttrib1fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+ if (_array) {
+ releasePointer(_env, _array, values, JNI_FALSE);
+ }
+}
+
+/* void glVertexAttrib2f ( GLuint indx, GLfloat x, GLfloat y ) */
+static void
+android_glVertexAttrib2f__IFF
+ (JNIEnv *_env, jobject _this, jint indx, jfloat x, jfloat y) {
+ glVertexAttrib2f(
+ (GLuint)indx,
+ (GLfloat)x,
+ (GLfloat)y
+ );
+}
+
+/* void glVertexAttrib2fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib2fv__I_3FI
+ (JNIEnv *_env, jobject _this, jint indx, jfloatArray values_ref, jint offset) {
+ GLfloat *values_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ if (!values_ref) {
+ _env->ThrowNew(IAEClass, "values == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(values_ref) - offset;
+ values_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(values_ref, (jboolean *)0);
+ values = values_base + offset;
+
+ glVertexAttrib2fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+
+exit:
+ if (values_base) {
+ _env->ReleasePrimitiveArrayCritical(values_ref, values_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glVertexAttrib2fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib2fv__ILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint indx, jobject values_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ values = (GLfloat *)getPointer(_env, values_buf, &_array, &_remaining);
+ glVertexAttrib2fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+ if (_array) {
+ releasePointer(_env, _array, values, JNI_FALSE);
+ }
+}
+
+/* void glVertexAttrib3f ( GLuint indx, GLfloat x, GLfloat y, GLfloat z ) */
+static void
+android_glVertexAttrib3f__IFFF
+ (JNIEnv *_env, jobject _this, jint indx, jfloat x, jfloat y, jfloat z) {
+ glVertexAttrib3f(
+ (GLuint)indx,
+ (GLfloat)x,
+ (GLfloat)y,
+ (GLfloat)z
+ );
+}
+
+/* void glVertexAttrib3fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib3fv__I_3FI
+ (JNIEnv *_env, jobject _this, jint indx, jfloatArray values_ref, jint offset) {
+ GLfloat *values_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ if (!values_ref) {
+ _env->ThrowNew(IAEClass, "values == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(values_ref) - offset;
+ values_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(values_ref, (jboolean *)0);
+ values = values_base + offset;
+
+ glVertexAttrib3fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+
+exit:
+ if (values_base) {
+ _env->ReleasePrimitiveArrayCritical(values_ref, values_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glVertexAttrib3fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib3fv__ILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint indx, jobject values_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ values = (GLfloat *)getPointer(_env, values_buf, &_array, &_remaining);
+ glVertexAttrib3fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+ if (_array) {
+ releasePointer(_env, _array, values, JNI_FALSE);
+ }
+}
+
+/* void glVertexAttrib4f ( GLuint indx, GLfloat x, GLfloat y, GLfloat z, GLfloat w ) */
+static void
+android_glVertexAttrib4f__IFFFF
+ (JNIEnv *_env, jobject _this, jint indx, jfloat x, jfloat y, jfloat z, jfloat w) {
+ glVertexAttrib4f(
+ (GLuint)indx,
+ (GLfloat)x,
+ (GLfloat)y,
+ (GLfloat)z,
+ (GLfloat)w
+ );
+}
+
+/* void glVertexAttrib4fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib4fv__I_3FI
+ (JNIEnv *_env, jobject _this, jint indx, jfloatArray values_ref, jint offset) {
+ GLfloat *values_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ if (!values_ref) {
+ _env->ThrowNew(IAEClass, "values == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(values_ref) - offset;
+ values_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(values_ref, (jboolean *)0);
+ values = values_base + offset;
+
+ glVertexAttrib4fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+
+exit:
+ if (values_base) {
+ _env->ReleasePrimitiveArrayCritical(values_ref, values_base,
+ JNI_ABORT);
+ }
+}
+
+/* void glVertexAttrib4fv ( GLuint indx, const GLfloat *values ) */
+static void
+android_glVertexAttrib4fv__ILjava_nio_FloatBuffer_2
+ (JNIEnv *_env, jobject _this, jint indx, jobject values_buf) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *values = (GLfloat *) 0;
+
+ values = (GLfloat *)getPointer(_env, values_buf, &_array, &_remaining);
+ glVertexAttrib4fv(
+ (GLuint)indx,
+ (GLfloat *)values
+ );
+ if (_array) {
+ releasePointer(_env, _array, values, JNI_FALSE);
+ }
+}
+
+/* void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr ) */
+static void
+android_glVertexAttribPointerBounds__IIIZILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint indx, jint size, jint type, jboolean normalized, jint stride, jobject ptr_buf, jint remaining) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *ptr = (GLvoid *) 0;
+
+ if (ptr_buf) {
+ ptr = (GLvoid *) getDirectBufferPointer(_env, ptr_buf);
+ if ( ! ptr ) {
+ return;
+ }
+ }
+ glVertexAttribPointerBounds(
+ (GLuint)indx,
+ (GLint)size,
+ (GLenum)type,
+ (GLboolean)normalized,
+ (GLsizei)stride,
+ (GLvoid *)ptr,
+ (GLsizei)remaining
+ );
+}
+
+/* void glViewport ( GLint x, GLint y, GLsizei width, GLsizei height ) */
+static void
+android_glViewport__IIII
+ (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height) {
+ glViewport(
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height
+ );
+}
+
+static const char *classPathName = "android/opengl/GLES20";
+
+static JNINativeMethod methods[] = {
+{"_nativeClassInit", "()V", (void*)nativeClassInit },
+{"glActiveTexture", "(I)V", (void *) android_glActiveTexture__I },
+{"glAttachShader", "(II)V", (void *) android_glAttachShader__II },
+{"glBindAttribLocation", "(IILjava/lang/String;)V", (void *) android_glBindAttribLocation__IILjava_lang_String_2 },
+{"glBindBuffer", "(II)V", (void *) android_glBindBuffer__II },
+{"glBindFramebuffer", "(II)V", (void *) android_glBindFramebuffer__II },
+{"glBindRenderbuffer", "(II)V", (void *) android_glBindRenderbuffer__II },
+{"glBindTexture", "(II)V", (void *) android_glBindTexture__II },
+{"glBlendColor", "(FFFF)V", (void *) android_glBlendColor__FFFF },
+{"glBlendEquation", "(I)V", (void *) android_glBlendEquation__I },
+{"glBlendEquationSeparate", "(II)V", (void *) android_glBlendEquationSeparate__II },
+{"glBlendFunc", "(II)V", (void *) android_glBlendFunc__II },
+{"glBlendFuncSeparate", "(IIII)V", (void *) android_glBlendFuncSeparate__IIII },
+{"glBufferData", "(IILjava/nio/Buffer;I)V", (void *) android_glBufferData__IILjava_nio_Buffer_2I },
+{"glBufferSubData", "(IIILjava/nio/Buffer;)V", (void *) android_glBufferSubData__IIILjava_nio_Buffer_2 },
+{"glCheckFramebufferStatus", "(I)I", (void *) android_glCheckFramebufferStatus__I },
+{"glClear", "(I)V", (void *) android_glClear__I },
+{"glClearColor", "(FFFF)V", (void *) android_glClearColor__FFFF },
+{"glClearDepthf", "(F)V", (void *) android_glClearDepthf__F },
+{"glClearStencil", "(I)V", (void *) android_glClearStencil__I },
+{"glColorMask", "(ZZZZ)V", (void *) android_glColorMask__ZZZZ },
+{"glCompileShader", "(I)V", (void *) android_glCompileShader__I },
+{"glCompressedTexImage2D", "(IIIIIIILjava/nio/Buffer;)V", (void *) android_glCompressedTexImage2D__IIIIIIILjava_nio_Buffer_2 },
+{"glCompressedTexSubImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glCompressedTexSubImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glCopyTexImage2D", "(IIIIIIII)V", (void *) android_glCopyTexImage2D__IIIIIIII },
+{"glCopyTexSubImage2D", "(IIIIIIII)V", (void *) android_glCopyTexSubImage2D__IIIIIIII },
+{"glCreateProgram", "()I", (void *) android_glCreateProgram__ },
+{"glCreateShader", "(I)I", (void *) android_glCreateShader__I },
+{"glCullFace", "(I)V", (void *) android_glCullFace__I },
+{"glDeleteBuffers", "(I[II)V", (void *) android_glDeleteBuffers__I_3II },
+{"glDeleteBuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteBuffers__ILjava_nio_IntBuffer_2 },
+{"glDeleteFramebuffers", "(I[II)V", (void *) android_glDeleteFramebuffers__I_3II },
+{"glDeleteFramebuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteFramebuffers__ILjava_nio_IntBuffer_2 },
+{"glDeleteProgram", "(I)V", (void *) android_glDeleteProgram__I },
+{"glDeleteRenderbuffers", "(I[II)V", (void *) android_glDeleteRenderbuffers__I_3II },
+{"glDeleteRenderbuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteRenderbuffers__ILjava_nio_IntBuffer_2 },
+{"glDeleteShader", "(I)V", (void *) android_glDeleteShader__I },
+{"glDeleteTextures", "(I[II)V", (void *) android_glDeleteTextures__I_3II },
+{"glDeleteTextures", "(ILjava/nio/IntBuffer;)V", (void *) android_glDeleteTextures__ILjava_nio_IntBuffer_2 },
+{"glDepthFunc", "(I)V", (void *) android_glDepthFunc__I },
+{"glDepthMask", "(Z)V", (void *) android_glDepthMask__Z },
+{"glDepthRangef", "(FF)V", (void *) android_glDepthRangef__FF },
+{"glDetachShader", "(II)V", (void *) android_glDetachShader__II },
+{"glDisable", "(I)V", (void *) android_glDisable__I },
+{"glDisableVertexAttribArray", "(I)V", (void *) android_glDisableVertexAttribArray__I },
+{"glDrawArrays", "(III)V", (void *) android_glDrawArrays__III },
+{"glDrawElements", "(IIILjava/nio/Buffer;)V", (void *) android_glDrawElements__IIILjava_nio_Buffer_2 },
+{"glEnable", "(I)V", (void *) android_glEnable__I },
+{"glEnableVertexAttribArray", "(I)V", (void *) android_glEnableVertexAttribArray__I },
+{"glFinish", "()V", (void *) android_glFinish__ },
+{"glFlush", "()V", (void *) android_glFlush__ },
+{"glFramebufferRenderbuffer", "(IIII)V", (void *) android_glFramebufferRenderbuffer__IIII },
+{"glFramebufferTexture2D", "(IIIII)V", (void *) android_glFramebufferTexture2D__IIIII },
+{"glFrontFace", "(I)V", (void *) android_glFrontFace__I },
+{"glGenBuffers", "(I[II)V", (void *) android_glGenBuffers__I_3II },
+{"glGenBuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenBuffers__ILjava_nio_IntBuffer_2 },
+{"glGenerateMipmap", "(I)V", (void *) android_glGenerateMipmap__I },
+{"glGenFramebuffers", "(I[II)V", (void *) android_glGenFramebuffers__I_3II },
+{"glGenFramebuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenFramebuffers__ILjava_nio_IntBuffer_2 },
+{"glGenRenderbuffers", "(I[II)V", (void *) android_glGenRenderbuffers__I_3II },
+{"glGenRenderbuffers", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenRenderbuffers__ILjava_nio_IntBuffer_2 },
+{"glGenTextures", "(I[II)V", (void *) android_glGenTextures__I_3II },
+{"glGenTextures", "(ILjava/nio/IntBuffer;)V", (void *) android_glGenTextures__ILjava_nio_IntBuffer_2 },
+{"glGetActiveAttrib", "(III[II[II[II[BI)V", (void *) android_glGetActiveAttrib__III_3II_3II_3II_3BI },
+{"glGetActiveAttrib", "(IIILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;B)V", (void *) android_glGetActiveAttrib__IIILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2B },
+{"glGetActiveUniform", "(III[II[II[II[BI)V", (void *) android_glGetActiveUniform__III_3II_3II_3II_3BI },
+{"glGetActiveUniform", "(IIILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;B)V", (void *) android_glGetActiveUniform__IIILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2Ljava_nio_IntBuffer_2B },
+{"glGetAttachedShaders", "(II[II[II)V", (void *) android_glGetAttachedShaders__II_3II_3II },
+{"glGetAttachedShaders", "(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)V", (void *) android_glGetAttachedShaders__IILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2 },
+{"glGetAttribLocation", "(ILjava/lang/String;)I", (void *) android_glGetAttribLocation__ILjava_lang_String_2 },
+{"glGetBooleanv", "(I[ZI)V", (void *) android_glGetBooleanv__I_3ZI },
+{"glGetBooleanv", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetBooleanv__ILjava_nio_IntBuffer_2 },
+{"glGetBufferParameteriv", "(II[II)V", (void *) android_glGetBufferParameteriv__II_3II },
+{"glGetBufferParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetBufferParameteriv__IILjava_nio_IntBuffer_2 },
+{"glGetError", "()I", (void *) android_glGetError__ },
+{"glGetFloatv", "(I[FI)V", (void *) android_glGetFloatv__I_3FI },
+{"glGetFloatv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glGetFloatv__ILjava_nio_FloatBuffer_2 },
+{"glGetFramebufferAttachmentParameteriv", "(III[II)V", (void *) android_glGetFramebufferAttachmentParameteriv__III_3II },
+{"glGetFramebufferAttachmentParameteriv", "(IIILjava/nio/IntBuffer;)V", (void *) android_glGetFramebufferAttachmentParameteriv__IIILjava_nio_IntBuffer_2 },
+{"glGetIntegerv", "(I[II)V", (void *) android_glGetIntegerv__I_3II },
+{"glGetIntegerv", "(ILjava/nio/IntBuffer;)V", (void *) android_glGetIntegerv__ILjava_nio_IntBuffer_2 },
+{"glGetProgramiv", "(II[II)V", (void *) android_glGetProgramiv__II_3II },
+{"glGetProgramiv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetProgramiv__IILjava_nio_IntBuffer_2 },
+{"glGetProgramInfoLog", "(I)Ljava/lang/String;", (void *) android_glGetProgramInfoLog },
+{"glGetRenderbufferParameteriv", "(II[II)V", (void *) android_glGetRenderbufferParameteriv__II_3II },
+{"glGetRenderbufferParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetRenderbufferParameteriv__IILjava_nio_IntBuffer_2 },
+{"glGetShaderiv", "(II[II)V", (void *) android_glGetShaderiv__II_3II },
+{"glGetShaderiv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetShaderiv__IILjava_nio_IntBuffer_2 },
+{"glGetShaderInfoLog", "(I)Ljava/lang/String;", (void *) android_glGetShaderInfoLog },
+{"glGetShaderPrecisionFormat", "(II[II[II)V", (void *) android_glGetShaderPrecisionFormat__II_3II_3II },
+{"glGetShaderPrecisionFormat", "(IILjava/nio/IntBuffer;Ljava/nio/IntBuffer;)V", (void *) android_glGetShaderPrecisionFormat__IILjava_nio_IntBuffer_2Ljava_nio_IntBuffer_2 },
+{"glGetShaderSource", "(II[II[BI)V", (void *) android_glGetShaderSource__II_3II_3BI },
+{"glGetShaderSource", "(IILjava/nio/IntBuffer;B)V", (void *) android_glGetShaderSource__IILjava_nio_IntBuffer_2B },
+{"glGetString", "(I)Ljava/lang/String;", (void *) android_glGetString },
+{"glGetTexParameterfv", "(II[FI)V", (void *) android_glGetTexParameterfv__II_3FI },
+{"glGetTexParameterfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetTexParameterfv__IILjava_nio_FloatBuffer_2 },
+{"glGetTexParameteriv", "(II[II)V", (void *) android_glGetTexParameteriv__II_3II },
+{"glGetTexParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetTexParameteriv__IILjava_nio_IntBuffer_2 },
+{"glGetUniformfv", "(II[FI)V", (void *) android_glGetUniformfv__II_3FI },
+{"glGetUniformfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetUniformfv__IILjava_nio_FloatBuffer_2 },
+{"glGetUniformiv", "(II[II)V", (void *) android_glGetUniformiv__II_3II },
+{"glGetUniformiv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetUniformiv__IILjava_nio_IntBuffer_2 },
+{"glGetUniformLocation", "(ILjava/lang/String;)I", (void *) android_glGetUniformLocation__ILjava_lang_String_2 },
+{"glGetVertexAttribfv", "(II[FI)V", (void *) android_glGetVertexAttribfv__II_3FI },
+{"glGetVertexAttribfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glGetVertexAttribfv__IILjava_nio_FloatBuffer_2 },
+{"glGetVertexAttribiv", "(II[II)V", (void *) android_glGetVertexAttribiv__II_3II },
+{"glGetVertexAttribiv", "(IILjava/nio/IntBuffer;)V", (void *) android_glGetVertexAttribiv__IILjava_nio_IntBuffer_2 },
+{"glHint", "(II)V", (void *) android_glHint__II },
+{"glIsBuffer", "(I)Z", (void *) android_glIsBuffer__I },
+{"glIsEnabled", "(I)Z", (void *) android_glIsEnabled__I },
+{"glIsFramebuffer", "(I)Z", (void *) android_glIsFramebuffer__I },
+{"glIsProgram", "(I)Z", (void *) android_glIsProgram__I },
+{"glIsRenderbuffer", "(I)Z", (void *) android_glIsRenderbuffer__I },
+{"glIsShader", "(I)Z", (void *) android_glIsShader__I },
+{"glIsTexture", "(I)Z", (void *) android_glIsTexture__I },
+{"glLineWidth", "(F)V", (void *) android_glLineWidth__F },
+{"glLinkProgram", "(I)V", (void *) android_glLinkProgram__I },
+{"glPixelStorei", "(II)V", (void *) android_glPixelStorei__II },
+{"glPolygonOffset", "(FF)V", (void *) android_glPolygonOffset__FF },
+{"glReadPixels", "(IIIIIILjava/nio/Buffer;)V", (void *) android_glReadPixels__IIIIIILjava_nio_Buffer_2 },
+{"glReleaseShaderCompiler", "()V", (void *) android_glReleaseShaderCompiler__ },
+{"glRenderbufferStorage", "(IIII)V", (void *) android_glRenderbufferStorage__IIII },
+{"glSampleCoverage", "(FZ)V", (void *) android_glSampleCoverage__FZ },
+{"glScissor", "(IIII)V", (void *) android_glScissor__IIII },
+{"glShaderBinary", "(I[IIILjava/nio/Buffer;I)V", (void *) android_glShaderBinary__I_3IIILjava_nio_Buffer_2I },
+{"glShaderBinary", "(ILjava/nio/IntBuffer;ILjava/nio/Buffer;I)V", (void *) android_glShaderBinary__ILjava_nio_IntBuffer_2ILjava_nio_Buffer_2I },
+{"glShaderSource", "(ILjava/lang/String;)V", (void *) android_glShaderSource },
+{"glStencilFunc", "(III)V", (void *) android_glStencilFunc__III },
+{"glStencilFuncSeparate", "(IIII)V", (void *) android_glStencilFuncSeparate__IIII },
+{"glStencilMask", "(I)V", (void *) android_glStencilMask__I },
+{"glStencilMaskSeparate", "(II)V", (void *) android_glStencilMaskSeparate__II },
+{"glStencilOp", "(III)V", (void *) android_glStencilOp__III },
+{"glStencilOpSeparate", "(IIII)V", (void *) android_glStencilOpSeparate__IIII },
+{"glTexImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glTexImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glTexParameterf", "(IIF)V", (void *) android_glTexParameterf__IIF },
+{"glTexParameterfv", "(II[FI)V", (void *) android_glTexParameterfv__II_3FI },
+{"glTexParameterfv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glTexParameterfv__IILjava_nio_FloatBuffer_2 },
+{"glTexParameteri", "(III)V", (void *) android_glTexParameteri__III },
+{"glTexParameteriv", "(II[II)V", (void *) android_glTexParameteriv__II_3II },
+{"glTexParameteriv", "(IILjava/nio/IntBuffer;)V", (void *) android_glTexParameteriv__IILjava_nio_IntBuffer_2 },
+{"glTexSubImage2D", "(IIIIIIIILjava/nio/Buffer;)V", (void *) android_glTexSubImage2D__IIIIIIIILjava_nio_Buffer_2 },
+{"glUniform1f", "(IF)V", (void *) android_glUniform1f__IF },
+{"glUniform1fv", "(II[FI)V", (void *) android_glUniform1fv__II_3FI },
+{"glUniform1fv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glUniform1fv__IILjava_nio_FloatBuffer_2 },
+{"glUniform1i", "(II)V", (void *) android_glUniform1i__II },
+{"glUniform1iv", "(II[II)V", (void *) android_glUniform1iv__II_3II },
+{"glUniform1iv", "(IILjava/nio/IntBuffer;)V", (void *) android_glUniform1iv__IILjava_nio_IntBuffer_2 },
+{"glUniform2f", "(IFF)V", (void *) android_glUniform2f__IFF },
+{"glUniform2fv", "(II[FI)V", (void *) android_glUniform2fv__II_3FI },
+{"glUniform2fv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glUniform2fv__IILjava_nio_FloatBuffer_2 },
+{"glUniform2i", "(III)V", (void *) android_glUniform2i__III },
+{"glUniform2iv", "(II[II)V", (void *) android_glUniform2iv__II_3II },
+{"glUniform2iv", "(IILjava/nio/IntBuffer;)V", (void *) android_glUniform2iv__IILjava_nio_IntBuffer_2 },
+{"glUniform3f", "(IFFF)V", (void *) android_glUniform3f__IFFF },
+{"glUniform3fv", "(II[FI)V", (void *) android_glUniform3fv__II_3FI },
+{"glUniform3fv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glUniform3fv__IILjava_nio_FloatBuffer_2 },
+{"glUniform3i", "(IIII)V", (void *) android_glUniform3i__IIII },
+{"glUniform3iv", "(II[II)V", (void *) android_glUniform3iv__II_3II },
+{"glUniform3iv", "(IILjava/nio/IntBuffer;)V", (void *) android_glUniform3iv__IILjava_nio_IntBuffer_2 },
+{"glUniform4f", "(IFFFF)V", (void *) android_glUniform4f__IFFFF },
+{"glUniform4fv", "(II[FI)V", (void *) android_glUniform4fv__II_3FI },
+{"glUniform4fv", "(IILjava/nio/FloatBuffer;)V", (void *) android_glUniform4fv__IILjava_nio_FloatBuffer_2 },
+{"glUniform4i", "(IIIII)V", (void *) android_glUniform4i__IIIII },
+{"glUniform4iv", "(II[II)V", (void *) android_glUniform4iv__II_3II },
+{"glUniform4iv", "(IILjava/nio/IntBuffer;)V", (void *) android_glUniform4iv__IILjava_nio_IntBuffer_2 },
+{"glUniformMatrix2fv", "(IIZ[FI)V", (void *) android_glUniformMatrix2fv__IIZ_3FI },
+{"glUniformMatrix2fv", "(IIZLjava/nio/FloatBuffer;)V", (void *) android_glUniformMatrix2fv__IIZLjava_nio_FloatBuffer_2 },
+{"glUniformMatrix3fv", "(IIZ[FI)V", (void *) android_glUniformMatrix3fv__IIZ_3FI },
+{"glUniformMatrix3fv", "(IIZLjava/nio/FloatBuffer;)V", (void *) android_glUniformMatrix3fv__IIZLjava_nio_FloatBuffer_2 },
+{"glUniformMatrix4fv", "(IIZ[FI)V", (void *) android_glUniformMatrix4fv__IIZ_3FI },
+{"glUniformMatrix4fv", "(IIZLjava/nio/FloatBuffer;)V", (void *) android_glUniformMatrix4fv__IIZLjava_nio_FloatBuffer_2 },
+{"glUseProgram", "(I)V", (void *) android_glUseProgram__I },
+{"glValidateProgram", "(I)V", (void *) android_glValidateProgram__I },
+{"glVertexAttrib1f", "(IF)V", (void *) android_glVertexAttrib1f__IF },
+{"glVertexAttrib1fv", "(I[FI)V", (void *) android_glVertexAttrib1fv__I_3FI },
+{"glVertexAttrib1fv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glVertexAttrib1fv__ILjava_nio_FloatBuffer_2 },
+{"glVertexAttrib2f", "(IFF)V", (void *) android_glVertexAttrib2f__IFF },
+{"glVertexAttrib2fv", "(I[FI)V", (void *) android_glVertexAttrib2fv__I_3FI },
+{"glVertexAttrib2fv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glVertexAttrib2fv__ILjava_nio_FloatBuffer_2 },
+{"glVertexAttrib3f", "(IFFF)V", (void *) android_glVertexAttrib3f__IFFF },
+{"glVertexAttrib3fv", "(I[FI)V", (void *) android_glVertexAttrib3fv__I_3FI },
+{"glVertexAttrib3fv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glVertexAttrib3fv__ILjava_nio_FloatBuffer_2 },
+{"glVertexAttrib4f", "(IFFFF)V", (void *) android_glVertexAttrib4f__IFFFF },
+{"glVertexAttrib4fv", "(I[FI)V", (void *) android_glVertexAttrib4fv__I_3FI },
+{"glVertexAttrib4fv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glVertexAttrib4fv__ILjava_nio_FloatBuffer_2 },
+{"glVertexAttribPointerBounds", "(IIIZILjava/nio/Buffer;I)V", (void *) android_glVertexAttribPointerBounds__IIIZILjava_nio_Buffer_2I },
+{"glViewport", "(IIII)V", (void *) android_glViewport__IIII },
+};
+
+int register_android_opengl_jni_GLES20(JNIEnv *_env)
+{
+ int err;
+ err = android::AndroidRuntime::registerNativeMethods(_env, classPathName, methods, NELEM(methods));
+ return err;
+}
diff --git a/core/jni/android_os_Hardware.cpp b/core/jni/android_os_Hardware.cpp
deleted file mode 100644
index 8007662..0000000
--- a/core/jni/android_os_Hardware.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 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.
-*/
-
-#include <hardware_legacy/flashlight.h>
-#include <hardware_legacy/power.h>
-
-#include <nativehelper/jni.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <nativehelper/JNIHelp.h>
-
-namespace android {
-
-static jint
-getFlashlightEnabled(JNIEnv *env, jobject clazz)
-{
- return get_flashlight_enabled();
-}
-
-static void
-setFlashlightEnabled(JNIEnv *env, jobject clazz, jboolean on)
-{
- set_flashlight_enabled(on);
-}
-
-static void
-enableCameraFlash(JNIEnv *env, jobject clazz, jint milliseconds)
-{
- enable_camera_flash(milliseconds);
-}
-
-// ============================================================================
-/*
- * JNI registration.
- */
-
-static JNINativeMethod g_methods[] = {
- /* name, signature, funcPtr */
- { "getFlashlightEnabled", "()Z", (void*)getFlashlightEnabled },
- { "setFlashlightEnabled", "(Z)V", (void*)setFlashlightEnabled },
- { "enableCameraFlash", "(I)V", (void*)enableCameraFlash },
-};
-
-int register_android_os_Hardware(JNIEnv* env)
-{
- return AndroidRuntime::registerNativeMethods(env,
- "android/os/Hardware", g_methods, NELEM(g_methods));
-}
-
-}; // namespace android
diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp
index 1ae3ec7..ee8d836 100644
--- a/core/jni/android_os_MemoryFile.cpp
+++ b/core/jni/android_os_MemoryFile.cpp
@@ -30,8 +30,6 @@ static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring na
{
const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
- // round up length to page boundary
- length = (((length - 1) / getpagesize()) + 1) * getpagesize();
int result = ashmem_create_region(namestr, length);
if (name)
@@ -118,7 +116,7 @@ static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDe
}
}
-static jint android_os_MemoryFile_get_mapped_size(JNIEnv* env, jobject clazz,
+static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz,
jobject fileDescriptor) {
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
// Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
@@ -146,8 +144,8 @@ static const JNINativeMethod methods[] = {
{"native_read", "(Ljava/io/FileDescriptor;I[BIIIZ)I", (void*)android_os_MemoryFile_read},
{"native_write", "(Ljava/io/FileDescriptor;I[BIIIZ)V", (void*)android_os_MemoryFile_write},
{"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin},
- {"native_get_mapped_size", "(Ljava/io/FileDescriptor;)I",
- (void*)android_os_MemoryFile_get_mapped_size}
+ {"native_get_size", "(Ljava/io/FileDescriptor;)I",
+ (void*)android_os_MemoryFile_get_size}
};
static const char* const kClassPathName = "android/os/MemoryFile";
diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp
index 7a3bbbb..cf53a06 100644
--- a/core/jni/android_server_BluetoothA2dpService.cpp
+++ b/core/jni/android_server_BluetoothA2dpService.cpp
@@ -38,6 +38,7 @@ namespace android {
#ifdef HAVE_BLUETOOTH
static jmethodID method_onSinkPropertyChanged;
+static jmethodID method_onConnectSinkResult;
typedef struct {
JavaVM *vm;
@@ -47,6 +48,7 @@ typedef struct {
} native_data_t;
static native_data_t *nat = NULL; // global native data
+static void onConnectSinkResult(DBusMessage *msg, void *user, void *n);
static Properties sink_properties[] = {
{"State", DBUS_TYPE_STRING},
@@ -133,9 +135,12 @@ static jboolean connectSinkNative(JNIEnv *env, jobject object, jstring path) {
LOGV(__FUNCTION__);
if (nat) {
const char *c_path = env->GetStringUTFChars(path, NULL);
+ int len = env->GetStringLength(path) + 1;
+ char *context_path = (char *)calloc(len, sizeof(char));
+ strlcpy(context_path, c_path, len); // for callback
- bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
- c_path, "org.bluez.AudioSink", "Connect",
+ bool ret = dbus_func_args_async(env, nat->conn, -1, onConnectSinkResult, context_path,
+ nat, c_path, "org.bluez.AudioSink", "Connect",
DBUS_TYPE_INVALID);
env->ReleaseStringUTFChars(path, c_path);
@@ -195,6 +200,38 @@ static jboolean resumeSinkNative(JNIEnv *env, jobject object,
return JNI_FALSE;
}
+static jboolean avrcpVolumeUpNative(JNIEnv *env, jobject object,
+ jstring path) {
+#ifdef HAVE_BLUETOOTH
+ LOGV(__FUNCTION__);
+ if (nat) {
+ const char *c_path = env->GetStringUTFChars(path, NULL);
+ bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
+ c_path, "org.bluez.Control", "VolumeUp",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(path, c_path);
+ return ret ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
+static jboolean avrcpVolumeDownNative(JNIEnv *env, jobject object,
+ jstring path) {
+#ifdef HAVE_BLUETOOTH
+ LOGV(__FUNCTION__);
+ if (nat) {
+ const char *c_path = env->GetStringUTFChars(path, NULL);
+ bool ret = dbus_func_args_async(env, nat->conn, -1, NULL, NULL, nat,
+ c_path, "org.bluez.Control", "VolumeDown",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(path, c_path);
+ return ret ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
#ifdef HAVE_BLUETOOTH
DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
DBusError err;
@@ -237,6 +274,32 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) {
return result;
}
+
+void onConnectSinkResult(DBusMessage *msg, void *user, void *n) {
+ LOGV(__FUNCTION__);
+
+ native_data_t *nat = (native_data_t *)n;
+ const char *path = (const char *)user;
+ DBusError err;
+ dbus_error_init(&err);
+ JNIEnv *env;
+ nat->vm->GetEnv((void**)&env, nat->envVer);
+
+
+ bool result = JNI_TRUE;
+ if (dbus_set_error_from_message(&err, msg)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ result = JNI_FALSE;
+ }
+ LOGV("... Device Path = %s, result = %d", path, result);
+ env->CallVoidMethod(nat->me,
+ method_onConnectSinkResult,
+ env->NewStringUTF(path),
+ result);
+ free(user);
+}
+
+
#endif
@@ -244,13 +307,15 @@ static JNINativeMethod sMethods[] = {
{"initNative", "()Z", (void *)initNative},
{"cleanupNative", "()V", (void *)cleanupNative},
- /* Bluez audio 4.40 API */
+ /* Bluez audio 4.47 API */
{"connectSinkNative", "(Ljava/lang/String;)Z", (void *)connectSinkNative},
{"disconnectSinkNative", "(Ljava/lang/String;)Z", (void *)disconnectSinkNative},
{"suspendSinkNative", "(Ljava/lang/String;)Z", (void*)suspendSinkNative},
{"resumeSinkNative", "(Ljava/lang/String;)Z", (void*)resumeSinkNative},
{"getSinkPropertiesNative", "(Ljava/lang/String;)[Ljava/lang/Object;",
(void *)getSinkPropertiesNative},
+ {"avrcpVolumeUpNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeUpNative},
+ {"avrcpVolumeDownNative", "(Ljava/lang/String;)Z", (void*)avrcpVolumeDownNative},
};
int register_android_server_BluetoothA2dpService(JNIEnv *env) {
@@ -263,6 +328,8 @@ int register_android_server_BluetoothA2dpService(JNIEnv *env) {
#ifdef HAVE_BLUETOOTH
method_onSinkPropertyChanged = env->GetMethodID(clazz, "onSinkPropertyChanged",
"(Ljava/lang/String;[Ljava/lang/String;)V");
+ method_onConnectSinkResult = env->GetMethodID(clazz, "onConnectSinkResult",
+ "(Ljava/lang/String;Z)V");
#endif
return AndroidRuntime::registerNativeMethods(env,
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index fdc97ee..259cc01 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -382,7 +382,7 @@ static void tearDownEventLoop(native_data_t *nat) {
dbus_connection_unregister_object_path(nat->conn, agent_path);
dbus_bus_remove_match(nat->conn,
- "type='signal',interface='org.bluez.audio.Sink'",
+ "type='signal',interface='org.bluez.AudioSink'",
&err);
if (dbus_error_is_set(&err)) {
LOG_AND_FREE_DBUS_ERROR(&err);
@@ -548,6 +548,8 @@ static void *eventLoopMain(void *ptr) {
dbus_connection_set_watch_functions(nat->conn, dbusAddWatch,
dbusRemoveWatch, dbusToggleWatch, ptr, NULL);
+ nat->running = true;
+
while (1) {
for (int i = 0; i < nat->pollMemberCount; i++) {
if (!nat->pollData[i].revents) {
@@ -591,7 +593,7 @@ static void *eventLoopMain(void *ptr) {
break;
}
}
- while (dbus_connection_dispatch(nat->conn) ==
+ while (dbus_connection_dispatch(nat->conn) ==
DBUS_DISPATCH_DATA_REMAINS) {
}
@@ -607,6 +609,8 @@ static jboolean startEventLoopNative(JNIEnv *env, jobject object) {
pthread_mutex_lock(&(nat->thread_mutex));
+ nat->running = false;
+
if (nat->pollData) {
LOGW("trying to start EventLoop a second time!");
pthread_mutex_unlock( &(nat->thread_mutex) );
@@ -703,6 +707,7 @@ static void stopEventLoopNative(JNIEnv *env, jobject object) {
nat->controlFdW = 0;
close(fd);
}
+ nat->running = false;
pthread_mutex_unlock(&(nat->thread_mutex));
#endif // HAVE_BLUETOOTH
}
@@ -713,7 +718,7 @@ static jboolean isEventLoopRunningNative(JNIEnv *env, jobject object) {
native_data_t *nat = get_native_data(env, object);
pthread_mutex_lock(&(nat->thread_mutex));
- if (nat->pollData) {
+ if (nat->running) {
result = JNI_TRUE;
}
pthread_mutex_unlock(&(nat->thread_mutex));
@@ -1129,9 +1134,10 @@ void onCreateDeviceResult(DBusMessage *msg, void *user, void *n) {
if (dbus_set_error_from_message(&err, msg)) {
if (dbus_error_has_name(&err, "org.bluez.Error.AlreadyExists")) {
result = CREATE_DEVICE_ALREADY_EXISTS;
+ } else {
+ result = CREATE_DEVICE_FAILED;
}
LOG_AND_FREE_DBUS_ERROR(&err);
- result = CREATE_DEVICE_FAILED;
}
env->CallVoidMethod(nat->me,
method_onCreateDeviceResult,
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index ea64305..4420aca 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -861,6 +861,26 @@ static jboolean removeServiceRecordNative(JNIEnv *env, jobject object, jint hand
return JNI_FALSE;
}
+static jboolean setLinkTimeoutNative(JNIEnv *env, jobject object, jstring object_path,
+ jint num_slots) {
+ LOGV(__FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_object_path = env->GetStringUTFChars(object_path, NULL);
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ get_adapter_path(env, object),
+ DBUS_ADAPTER_IFACE, "SetLinkTimeout",
+ DBUS_TYPE_OBJECT_PATH, &c_object_path,
+ DBUS_TYPE_UINT32, &num_slots,
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(object_path, c_object_path);
+ return reply ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
@@ -905,6 +925,7 @@ static JNINativeMethod sMethods[] = {
{"discoverServicesNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)discoverServicesNative},
{"addRfcommServiceRecordNative", "(Ljava/lang/String;JJS)I", (void *)addRfcommServiceRecordNative},
{"removeServiceRecordNative", "(I)Z", (void *)removeServiceRecordNative},
+ {"setLinkTimeoutNative", "(Ljava/lang/String;I)Z", (void *)setLinkTimeoutNative},
};
int register_android_server_BluetoothService(JNIEnv *env) {
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
new file mode 100644
index 0000000..7696bb3
--- /dev/null
+++ b/core/jni/android_text_AndroidBidi.cpp
@@ -0,0 +1,81 @@
+/* //device/libs/android_runtime/android_text_AndroidBidi.cpp
+**
+** Copyright 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.
+*/
+
+#define LOG_TAG "AndroidUnicode"
+
+#include <jni.h>
+#include <android_runtime/AndroidRuntime.h>
+#include "utils/misc.h"
+#include "utils/Log.h"
+#include "unicode/ubidi.h"
+
+namespace android {
+
+static void jniThrowException(JNIEnv* env, const char* exc, const char* msg = NULL)
+{
+ jclass excClazz = env->FindClass(exc);
+ LOG_ASSERT(excClazz, "Unable to find class %s", exc);
+
+ env->ThrowNew(excClazz, msg);
+}
+
+static jint runBidi(JNIEnv* env, jobject obj, jint dir, jcharArray chsArray,
+ jbyteArray infoArray, int n, jboolean haveInfo)
+{
+ // Parameters are checked on java side
+ // Failures from GetXXXArrayElements indicate a serious out-of-memory condition
+ // that we don't bother to report, we're probably dead anyway.
+ jint result = 0;
+ jchar* chs = env->GetCharArrayElements(chsArray, NULL);
+ if (chs != NULL) {
+ jbyte* info = env->GetByteArrayElements(infoArray, NULL);
+ if (info != NULL) {
+ UErrorCode status = U_ZERO_ERROR;
+ UBiDi* bidi = ubidi_openSized(n, 0, &status);
+ ubidi_setPara(bidi, chs, n, dir, NULL, &status);
+ if (U_SUCCESS(status)) {
+ for (int i = 0; i < n; ++i) {
+ info[i] = ubidi_getLevelAt(bidi, i);
+ }
+ result = ubidi_getParaLevel(bidi);
+ } else {
+ jniThrowException(env, "java/lang/RuntimeException", NULL);
+ }
+ ubidi_close(bidi);
+
+ env->ReleaseByteArrayElements(infoArray, info, 0);
+ }
+ env->ReleaseCharArrayElements(chsArray, chs, JNI_ABORT);
+ }
+ return result;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "runBidi", "(I[C[BIZ)I",
+ (void*) runBidi }
+};
+
+int register_android_text_AndroidBidi(JNIEnv* env)
+{
+ jclass clazz = env->FindClass("android/text/AndroidBidi");
+ LOG_ASSERT(clazz, "Cannot find android/text/AndroidBidi");
+
+ return AndroidRuntime::registerNativeMethods(env, "android/text/AndroidBidi",
+ gMethods, NELEM(gMethods));
+}
+
+}
diff --git a/core/jni/android_text_AndroidCharacter.cpp b/core/jni/android_text_AndroidCharacter.cpp
index 450cee2..5d8d419 100644
--- a/core/jni/android_text_AndroidCharacter.cpp
+++ b/core/jni/android_text_AndroidCharacter.cpp
@@ -20,8 +20,33 @@
#include <jni.h>
#include <android_runtime/AndroidRuntime.h>
#include "utils/misc.h"
-#include "utils/AndroidUnicode.h"
#include "utils/Log.h"
+#include "unicode/uchar.h"
+
+#define PROPERTY_UNDEFINED (-1)
+
+// ICU => JDK mapping
+static int directionality_map[U_CHAR_DIRECTION_COUNT] = {
+ 0, // U_LEFT_TO_RIGHT (0) => DIRECTIONALITY_LEFT_TO_RIGHT (0)
+ 1, // U_RIGHT_TO_LEFT (1) => DIRECTIONALITY_RIGHT_TO_LEFT (1)
+ 3, // U_EUROPEAN_NUMBER (2) => DIRECTIONALITY_EUROPEAN_NUMBER (3)
+ 4, // U_EUROPEAN_NUMBER_SEPARATOR (3) => DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR (4)
+ 5, // U_EUROPEAN_NUMBER_TERMINATOR (4) => DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR (5)
+ 6, // U_ARABIC_NUMBER (5) => DIRECTIONALITY_ARABIC_NUMBER (6)
+ 7, // U_COMMON_NUMBER_SEPARATOR (6) => DIRECTIONALITY_COMMON_NUMBER_SEPARATOR (7)
+ 10, // U_BLOCK_SEPARATOR (7) => DIRECTIONALITY_PARAGRAPH_SEPARATOR (10)
+ 11, // U_SEGMENT_SEPARATOR (8) => DIRECTIONALITY_SEGMENT_SEPARATOR (11)
+ 12, // U_WHITE_SPACE_NEUTRAL (9) => DIRECTIONALITY_WHITESPACE (12)
+ 13, // U_OTHER_NEUTRAL (10) => DIRECTIONALITY_OTHER_NEUTRALS (13)
+ 14, // U_LEFT_TO_RIGHT_EMBEDDING (11) => DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING (14)
+ 15, // U_LEFT_TO_RIGHT_OVERRIDE (12) => DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE (15)
+ 2, // U_RIGHT_TO_LEFT_ARABIC (13) => DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC (2)
+ 16, // U_RIGHT_TO_LEFT_EMBEDDING (14) => DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING (16)
+ 17, // U_RIGHT_TO_LEFT_OVERRIDE (15) => DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE (17)
+ 18, // U_POP_DIRECTIONAL_FORMAT (16) => DIRECTIONALITY_POP_DIRECTIONAL_FORMAT (18)
+ 8, // U_DIR_NON_SPACING_MARK (17) => DIRECTIONALITY_NONSPACING_MARK (8)
+ 9, // U_BOUNDARY_NEUTRAL (18) => DIRECTIONALITY_BOUNDARY_NEUTRAL (9)
+};
namespace android {
@@ -53,15 +78,21 @@ static void getDirectionalities(JNIEnv* env, jobject obj, jcharArray srcArray, j
src[i + 1] >= 0xDC00 && src[i + 1] <= 0xDFFF) {
int c = 0x00010000 + ((src[i] - 0xD800) << 10) +
(src[i + 1] & 0x3FF);
- int dir = android::Unicode::getDirectionality(c);
+ int dir = u_charDirection(c);
+ if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT)
+ dir = PROPERTY_UNDEFINED;
+ else
+ dir = directionality_map[dir];
dest[i++] = dir;
dest[i] = dir;
} else {
int c = src[i];
- int dir = android::Unicode::getDirectionality(c);
-
- dest[i] = dir;
+ int dir = u_charDirection(c);
+ if (dir < 0 || dir >= U_CHAR_DIRECTION_COUNT)
+ dest[i] = PROPERTY_UNDEFINED;
+ else
+ dest[i] = directionality_map[dir];
}
}
@@ -70,6 +101,60 @@ DIRECTION_END:
env->ReleaseByteArrayElements(destArray, dest, JNI_ABORT);
}
+static jint getEastAsianWidth(JNIEnv* env, jobject obj, jchar input)
+{
+ int width = u_getIntPropertyValue(input, UCHAR_EAST_ASIAN_WIDTH);
+ if (width < 0 || width >= U_EA_COUNT)
+ width = PROPERTY_UNDEFINED;
+
+ return width;
+}
+
+static void getEastAsianWidths(JNIEnv* env, jobject obj, jcharArray srcArray,
+ int start, int count, jbyteArray destArray)
+{
+ jchar* src = env->GetCharArrayElements(srcArray, NULL);
+ jbyte* dest = env->GetByteArrayElements(destArray, NULL);
+ if (src == NULL || dest == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", NULL);
+ goto EA_END;
+ }
+
+ if (start < 0 || start > start + count
+ || env->GetArrayLength(srcArray) < (start + count)
+ || env->GetArrayLength(destArray) < count) {
+ jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+ goto EA_END;
+ }
+
+ for (int i = 0; i < count; i++) {
+ const int srci = start + i;
+ if (src[srci] >= 0xD800 && src[srci] <= 0xDBFF &&
+ i + 1 < count &&
+ src[srci + 1] >= 0xDC00 && src[srci + 1] <= 0xDFFF) {
+ int c = 0x00010000 + ((src[srci] - 0xD800) << 10) +
+ (src[srci + 1] & 0x3FF);
+ int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH);
+ if (width < 0 || width >= U_EA_COUNT)
+ width = PROPERTY_UNDEFINED;
+
+ dest[i++] = width;
+ dest[i] = width;
+ } else {
+ int c = src[srci];
+ int width = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH);
+ if (width < 0 || width >= U_EA_COUNT)
+ width = PROPERTY_UNDEFINED;
+
+ dest[i] = width;
+ }
+ }
+
+EA_END:
+ env->ReleaseCharArrayElements(srcArray, src, JNI_ABORT);
+ env->ReleaseByteArrayElements(destArray, dest, JNI_ABORT);
+}
+
static jboolean mirror(JNIEnv* env, jobject obj, jcharArray charArray, int start, int count)
{
jchar* data = env->GetCharArrayElements(charArray, NULL);
@@ -80,7 +165,8 @@ static jboolean mirror(JNIEnv* env, jobject obj, jcharArray charArray, int start
goto MIRROR_END;
}
- if (start > start + count || env->GetArrayLength(charArray) < count) {
+ if (start < 0 || start > start + count
+ || env->GetArrayLength(charArray) < start + count) {
jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
goto MIRROR_END;
}
@@ -89,7 +175,7 @@ static jboolean mirror(JNIEnv* env, jobject obj, jcharArray charArray, int start
// XXX this thinks it knows that surrogates are never mirrored
int c1 = data[i];
- int c2 = android::Unicode::toMirror(c1);
+ int c2 = u_charMirror(c1);
if (c1 != c2) {
data[i] = c2;
@@ -104,12 +190,16 @@ MIRROR_END:
static jchar getMirror(JNIEnv* env, jobject obj, jchar c)
{
- return android::Unicode::toMirror(c);
+ return u_charMirror(c);
}
static JNINativeMethod gMethods[] = {
{ "getDirectionalities", "([C[BI)V",
(void*) getDirectionalities },
+ { "getEastAsianWidth", "(C)I",
+ (void*) getEastAsianWidth },
+ { "getEastAsianWidths", "([CII[B)V",
+ (void*) getEastAsianWidths },
{ "mirror", "([CII)Z",
(void*) mirror },
{ "getMirror", "(C)C",
diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp
index d89a7e6..c152aa8 100644
--- a/core/jni/android_text_format_Time.cpp
+++ b/core/jni/android_text_format_Time.cpp
@@ -584,9 +584,9 @@ static jboolean android_text_format_Time_parse3339(JNIEnv* env,
inUtc = true;
if (offset != 0) {
- if (len < tz_index + 5) {
+ if (len < tz_index + 6) {
char msg[100];
- sprintf(msg, "Unexpected length; should be %d characters", tz_index + 5);
+ sprintf(msg, "Unexpected length; should be %d characters", tz_index + 6);
jniThrowException(env, "android/util/TimeFormatException", msg);
return false;
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index e83d2e2..a2b7cc4 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "asset"
#define DEBUG_STYLES(x) //x
+#define THROW_ON_BAD_ID 0
#include <android_runtime/android_util_AssetManager.h>
@@ -524,7 +525,6 @@ static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject
}
for (int i=0; i<N; i++) {
- LOGD("locale %2d: '%s'", i, locales[i].string());
env->SetObjectArrayElement(result, i, env->NewStringUTF(locales[i].string()));
}
@@ -538,7 +538,8 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c
jint keyboard, jint keyboardHidden,
jint navigation,
jint screenWidth, jint screenHeight,
- jint screenLayout, jint sdkVersion)
+ jint screenLayout, jint uiMode,
+ jint sdkVersion)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
@@ -561,6 +562,7 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c
config.screenWidth = (uint16_t)screenWidth;
config.screenHeight = (uint16_t)screenHeight;
config.screenLayout = (uint8_t)screenLayout;
+ config.uiMode = (uint8_t)uiMode;
config.sdkVersion = (uint16_t)sdkVersion;
config.minorVersion = 0;
am->setConfiguration(config, locale8);
@@ -718,9 +720,21 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, &typeSpecFlags, &config);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return 0;
+ }
+#endif
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return 0;
+ }
+#endif
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
}
@@ -762,6 +776,12 @@ static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobje
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return 0;
+ }
+#endif
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
}
@@ -851,6 +871,12 @@ static jint android_content_AssetManager_loadThemeAttributeValue(
uint32_t ref = 0;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return 0;
+ }
+#endif
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
}
@@ -1070,6 +1096,12 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
value.dataType, value.data));
newBlock = res.resolveReference(&value, block, &resid,
&typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
if (newBlock >= 0) block = newBlock;
DEBUG_STYLES(LOGI("-> Resolved theme: type=0x%x, data=0x%08x",
value.dataType, value.data));
@@ -1206,6 +1238,12 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job
//printf("Resolving attribute reference\n");
ssize_t newBlock = res.resolveReference(&value, block, &resid,
&typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
if (newBlock >= 0) block = newBlock;
}
@@ -1313,6 +1351,12 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz
//printf("Resolving attribute reference\n");
ssize_t newBlock = res.resolveReference(&value, block, &resid,
&typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
if (newBlock >= 0) block = newBlock;
}
@@ -1420,6 +1464,13 @@ static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jo
stringIndex = value.data;
}
+#if THROW_ON_BAD_ID
+ if (stringBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return array;
+ }
+#endif
+
//todo: It might be faster to allocate a C array to contain
// the blocknums and indices, put them in there and then
// do just one SetIntArrayRegion()
@@ -1465,19 +1516,31 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv*
for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
value = bag->map.value;
jstring str = NULL;
-
+
// Take care of resolving the found resource to its final value.
ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return array;
+ }
+#endif
if (value.dataType == Res_value::TYPE_STRING) {
- const char16_t* str16 = res.getTableStringBlock(block)->stringAt(value.data, &strLen);
- str = env->NewString(str16, strLen);
- if (str == NULL) {
- doThrow(env, "java/lang/OutOfMemoryError");
- res.unlockBag(startOfBag);
- return NULL;
+ const ResStringPool* pool = res.getTableStringBlock(block);
+ const char* str8 = pool->string8At(value.data, &strLen);
+ if (str8 != NULL) {
+ str = env->NewStringUTF(str8);
+ } else {
+ const char16_t* str16 = pool->stringAt(value.data, &strLen);
+ str = env->NewString(str16, strLen);
+ if (str == NULL) {
+ doThrow(env, "java/lang/OutOfMemoryError");
+ res.unlockBag(startOfBag);
+ return NULL;
+ }
}
}
-
+
env->SetObjectArrayElement(array, i, str);
}
res.unlockBag(startOfBag);
@@ -1513,6 +1576,12 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j
// Take care of resolving the found resource to its final value.
ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
+#if THROW_ON_BAD_ID
+ if (block == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return array;
+ }
+#endif
if (value.dataType >= Res_value::TYPE_FIRST_INT
&& value.dataType <= Res_value::TYPE_LAST_INT) {
int intVal = value.data;
@@ -1615,7 +1684,7 @@ static JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_setLocale },
{ "getLocales", "()[Ljava/lang/String;",
(void*) android_content_AssetManager_getLocales },
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIII)V",
+ { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
{ "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_getResourceIdentifier },
diff --git a/core/jni/android_util_Base64.cpp b/core/jni/android_util_Base64.cpp
deleted file mode 100644
index bc69747..0000000
--- a/core/jni/android_util_Base64.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/* //device/libs/android_runtime/android_util_Base64.cpp
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-/*********************************************************
-*
-* This code was copied from
-* system/extra/ssh/dropbear-0.49/libtomcrypt/src/misc/base64/base64_decode.c
-*
-*********************************************************/
-
-#define LOG_TAG "Base64"
-
-#include <utils/Log.h>
-
-#include <android_runtime/AndroidRuntime.h>
-
-#include "JNIHelp.h"
-
-#include <sys/errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <fcntl.h>
-#include <signal.h>
-
-namespace android {
-
-static const unsigned char map[256] = {
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
-255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6,
- 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
- 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
-255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
- 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
- 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
-255, 255, 255, 255 };
-
-/**
- base64 decode a block of memory
- @param in The base64 data to decode
- @param inlen The length of the base64 data
- @param out [out] The destination of the binary decoded data
- @param outlen [in/out] The max size and resulting size of the decoded data
- @return 0 if successful
-*/
-int base64_decode(const unsigned char *in, unsigned long inlen,
- unsigned char *out, unsigned long *outlen)
-{
- unsigned long t, x, y, z;
- unsigned char c;
- int g;
-
- g = 3;
- for (x = y = z = t = 0; x < inlen; x++) {
- c = map[in[x]&0xFF];
- if (c == 255) continue;
- /* the final = symbols are read and used to trim the remaining bytes */
- if (c == 254) {
- c = 0;
- /* prevent g < 0 which would potentially allow an overflow later */
- if (--g < 0) {
- return -3;
- }
- } else if (g != 3) {
- /* we only allow = to be at the end */
- return -4;
- }
-
- t = (t<<6)|c;
-
- if (++y == 4) {
- if (z + g > *outlen) {
- return -2;
- }
- out[z++] = (unsigned char)((t>>16)&255);
- if (g > 1) out[z++] = (unsigned char)((t>>8)&255);
- if (g > 2) out[z++] = (unsigned char)(t&255);
- y = t = 0;
- }
- }
- if (y != 0) {
- return -5;
- }
- *outlen = z;
- return 0;
-}
-
-static jbyteArray decodeBase64(JNIEnv *env, jobject jobj, jstring jdata)
-{
- const char * rawData = env->GetStringUTFChars(jdata, NULL);
- int stringLength = env->GetStringUTFLength(jdata);
-
- int resultLength = stringLength / 4 * 3;
- if (rawData[stringLength-1] == '=') {
- resultLength -= 1;
- if (rawData[stringLength-2] == '=') {
- resultLength -= 1;
- }
- }
-
- jbyteArray byteArray = env->NewByteArray(resultLength);
- jbyte* byteArrayData = env->GetByteArrayElements(byteArray, NULL);
-
- unsigned long outlen = resultLength;
- int result = base64_decode((const unsigned char*)rawData, stringLength, (unsigned char *)byteArrayData, &outlen);
- if (result != 0)
- memset((unsigned char *)byteArrayData, -result, resultLength);
-
- env->ReleaseStringUTFChars(jdata, rawData);
- env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
-
- return byteArray;
-}
-
-static const JNINativeMethod methods[] = {
- {"decodeBase64Native", "(Ljava/lang/String;)[B", (void*)decodeBase64 }
-};
-
-static const char* const kBase64PathName = "android/os/Base64Utils";
-
-int register_android_util_Base64(JNIEnv* env)
-{
- jclass clazz;
-
- clazz = env->FindClass(kBase64PathName);
- LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Base64Utils");
-
- return AndroidRuntime::registerNativeMethods(
- env, kBase64PathName,
- methods, NELEM(methods));
-}
-
-}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index f0885fd..d26cd28 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -21,16 +21,21 @@
#include "JNIHelp.h"
#include <fcntl.h>
-#include <sys/stat.h>
#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
#include <utils/Atomic.h>
#include <binder/IInterface.h>
#include <binder/IPCThreadState.h>
#include <utils/Log.h>
+#include <utils/SystemClock.h>
+#include <cutils/logger.h>
#include <binder/Parcel.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
+#include <utils/threads.h>
#include <android_runtime/AndroidRuntime.h>
@@ -590,9 +595,19 @@ static void android_os_Binder_destroy(JNIEnv* env, jobject clazz)
{
JavaBBinderHolder* jbh = (JavaBBinderHolder*)
env->GetIntField(clazz, gBinderOffsets.mObject);
- env->SetIntField(clazz, gBinderOffsets.mObject, 0);
- LOGV("Java Binder %p: removing ref on holder %p", clazz, jbh);
- jbh->decStrong(clazz);
+ if (jbh != NULL) {
+ env->SetIntField(clazz, gBinderOffsets.mObject, 0);
+ LOGV("Java Binder %p: removing ref on holder %p", clazz, jbh);
+ jbh->decStrong(clazz);
+ } else {
+ // Encountering an uninitialized binder is harmless. All it means is that
+ // the Binder was only partially initialized when its finalizer ran and called
+ // destroy(). The Binder could be partially initialized for several reasons.
+ // For example, a Binder subclass constructor might have thrown an exception before
+ // it could delegate to its superclass's constructor. Consequently init() would
+ // not have been called and the holder pointer would remain NULL.
+ LOGV("Java Binder %p: ignoring uninitialized binder", clazz);
+ }
}
// ----------------------------------------------------------------------------
@@ -670,6 +685,12 @@ static void android_os_BinderInternal_joinThreadPool(JNIEnv* env, jobject clazz)
android::IPCThreadState::self()->joinThreadPool();
}
+static void android_os_BinderInternal_disableBackgroundScheduling(JNIEnv* env,
+ jobject clazz, jboolean disable)
+{
+ IPCThreadState::disableBackgroundScheduling(disable ? true : false);
+}
+
static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
{
LOGV("Gc has executed, clearing binder ops");
@@ -682,6 +703,7 @@ static const JNINativeMethod gBinderInternalMethods[] = {
/* name, signature, funcPtr */
{ "getContextObject", "()Landroid/os/IBinder;", (void*)android_os_BinderInternal_getContextObject },
{ "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
+ { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
{ "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
};
@@ -723,7 +745,7 @@ static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobjec
{
IBinder* target = (IBinder*) env->GetIntField(obj, gBinderProxyOffsets.mObject);
if (target != NULL) {
- String16 desc = target->getInterfaceDescriptor();
+ const String16& desc = target->getInterfaceDescriptor();
return env->NewString(desc.string(), desc.size());
}
jniThrowException(env, "java/lang/RuntimeException",
@@ -742,6 +764,100 @@ static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj)
return alive ? JNI_TRUE : JNI_FALSE;
}
+static int getprocname(pid_t pid, char *buf, size_t len) {
+ char filename[20];
+ FILE *f;
+
+ sprintf(filename, "/proc/%d/cmdline", pid);
+ f = fopen(filename, "r");
+ if (!f) { *buf = '\0'; return 1; }
+ if (!fgets(buf, len, f)) { *buf = '\0'; return 2; }
+ fclose(f);
+ return 0;
+}
+
+static bool push_eventlog_string(char** pos, const char* end, const char* str) {
+ jint len = strlen(str);
+ int space_needed = 1 + sizeof(len) + len;
+ if (end - *pos < space_needed) {
+ LOGW("not enough space for string. remain=%d; needed=%d",
+ (end - *pos), space_needed);
+ return false;
+ }
+ **pos = EVENT_TYPE_STRING;
+ (*pos)++;
+ memcpy(*pos, &len, sizeof(len));
+ *pos += sizeof(len);
+ memcpy(*pos, str, len);
+ *pos += len;
+ return true;
+}
+
+static bool push_eventlog_int(char** pos, const char* end, jint val) {
+ int space_needed = 1 + sizeof(val);
+ if (end - *pos < space_needed) {
+ LOGW("not enough space for int. remain=%d; needed=%d",
+ (end - *pos), space_needed);
+ return false;
+ }
+ **pos = EVENT_TYPE_INT;
+ (*pos)++;
+ memcpy(*pos, &val, sizeof(val));
+ *pos += sizeof(val);
+ return true;
+}
+
+// From frameworks/base/core/java/android/content/EventLogTags.logtags:
+#define LOGTAG_BINDER_OPERATION 52004
+
+static void conditionally_log_binder_call(int64_t start_millis,
+ IBinder* target, jint code) {
+ int duration_ms = static_cast<int>(uptimeMillis() - start_millis);
+
+ int sample_percent;
+ if (duration_ms >= 500) {
+ sample_percent = 100;
+ } else {
+ sample_percent = 100 * duration_ms / 500;
+ if (sample_percent == 0) {
+ return;
+ }
+ if (sample_percent < (random() % 100 + 1)) {
+ return;
+ }
+ }
+
+ char process_name[40];
+ getprocname(getpid(), process_name, sizeof(process_name));
+ String8 desc(target->getInterfaceDescriptor());
+
+ char buf[LOGGER_ENTRY_MAX_PAYLOAD];
+ buf[0] = EVENT_TYPE_LIST;
+ buf[1] = 5;
+ char* pos = &buf[2];
+ char* end = &buf[LOGGER_ENTRY_MAX_PAYLOAD - 1]; // leave room for final \n
+ if (!push_eventlog_string(&pos, end, desc.string())) return;
+ if (!push_eventlog_int(&pos, end, code)) return;
+ if (!push_eventlog_int(&pos, end, duration_ms)) return;
+ if (!push_eventlog_string(&pos, end, process_name)) return;
+ if (!push_eventlog_int(&pos, end, sample_percent)) return;
+ *(pos++) = '\n'; // conventional with EVENT_TYPE_LIST apparently.
+ android_bWriteLog(LOGTAG_BINDER_OPERATION, buf, pos - buf);
+}
+
+// We only measure binder call durations to potentially log them if
+// we're on the main thread. Unfortunately sim-eng doesn't seem to
+// have gettid, so we just ignore this and don't log if we can't
+// get the thread id.
+static bool should_time_binder_calls() {
+#ifdef HAVE_GETTID
+ return (getpid() == androidGetTid());
+#else
+#warning no gettid(), so not logging Binder calls...
+ return false;
+#endif
+}
+
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj,
jobject replyObj, jint flags)
@@ -769,9 +885,22 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
LOGV("Java code calling transact on %p in Java object %p with code %d\n",
target, obj, code);
+
+ // Only log the binder call duration for things on the Java-level main thread.
+ // But if we don't
+ const bool time_binder_calls = should_time_binder_calls();
+
+ int64_t start_millis;
+ if (time_binder_calls) {
+ start_millis = uptimeMillis();
+ }
//printf("Transact from Java code to %p sending: ", target); data->print();
status_t err = target->transact(code, *data, reply, flags);
//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
+ if (time_binder_calls) {
+ conditionally_log_binder_call(start_millis, target, code);
+ }
+
if (err == NO_ERROR) {
return JNI_TRUE;
} else if (err == UNKNOWN_TRANSACTION) {
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 34b7c89..e43478c 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -21,12 +21,8 @@
#include "jni.h"
#include "cutils/logger.h"
-#define END_DELIMITER '\n'
-#define INT_BUFFER_SIZE (sizeof(jbyte)+sizeof(jint)+sizeof(END_DELIMITER))
-#define LONG_BUFFER_SIZE (sizeof(jbyte)+sizeof(jlong)+sizeof(END_DELIMITER))
-#define INITAL_BUFFER_CAPACITY 256
-
-#define MAX(a,b) ((a>b)?a:b)
+// The size of the tag number comes out of the payload size.
+#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
namespace android {
@@ -47,107 +43,6 @@ static jfieldID gLongValueID;
static jclass gStringClass;
-struct ByteBuf {
- size_t len;
- size_t capacity;
- uint8_t* buf;
-
- ByteBuf(size_t initSize) {
- buf = (uint8_t*)malloc(initSize);
- len = 0;
- capacity = initSize;
- }
-
- ~ByteBuf() {
- free(buf);
- }
-
- bool ensureExtraCapacity(size_t extra) {
- size_t spaceNeeded = len + extra;
- if (spaceNeeded > capacity) {
- size_t newCapacity = MAX(spaceNeeded, 2 * capacity);
- void* newBuf = realloc(buf, newCapacity);
- if (newBuf == NULL) {
- return false;
- }
- capacity = newCapacity;
- buf = (uint8_t*)newBuf;
- return true;
- } else {
- return true;
- }
- }
-
- void putIntEvent(jint value) {
- bool succeeded = ensureExtraCapacity(INT_BUFFER_SIZE);
- buf[len++] = EVENT_TYPE_INT;
- memcpy(buf+len, &value, sizeof(jint));
- len += sizeof(jint);
- }
-
- void putByte(uint8_t value) {
- bool succeeded = ensureExtraCapacity(sizeof(uint8_t));
- buf[len++] = value;
- }
-
- void putLongEvent(jlong value) {
- bool succeeded = ensureExtraCapacity(LONG_BUFFER_SIZE);
- buf[len++] = EVENT_TYPE_LONG;
- memcpy(buf+len, &value, sizeof(jlong));
- len += sizeof(jlong);
- }
-
-
- void putStringEvent(JNIEnv* env, jstring value) {
- const char* strValue = env->GetStringUTFChars(value, NULL);
- uint32_t strLen = strlen(strValue); //env->GetStringUTFLength(value);
- bool succeeded = ensureExtraCapacity(1 + sizeof(uint32_t) + strLen);
- buf[len++] = EVENT_TYPE_STRING;
- memcpy(buf+len, &strLen, sizeof(uint32_t));
- len += sizeof(uint32_t);
- memcpy(buf+len, strValue, strLen);
- env->ReleaseStringUTFChars(value, strValue);
- len += strLen;
- }
-
- void putList(JNIEnv* env, jobject list) {
- jobjectArray items = (jobjectArray) env->GetObjectField(list, gListItemsID);
- if (items == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return;
- }
-
- jsize numItems = env->GetArrayLength(items);
- putByte(EVENT_TYPE_LIST);
- putByte(numItems);
- // We'd like to call GetPrimitveArrayCritical() but that might
- // not be safe since we're going to be doing some I/O
- for (int i = 0; i < numItems; i++) {
- jobject item = env->GetObjectArrayElement(items, i);
- if (env->IsInstanceOf(item, gIntegerClass)) {
- jint intVal = env->GetIntField(item, gIntegerValueID);
- putIntEvent(intVal);
- } else if (env->IsInstanceOf(item, gLongClass)) {
- jlong longVal = env->GetLongField(item, gLongValueID);
- putLongEvent(longVal);
- } else if (env->IsInstanceOf(item, gStringClass)) {
- putStringEvent(env, (jstring)item);
- } else if (env->IsInstanceOf(item, gListClass)) {
- putList(env, item);
- } else {
- jniThrowException(
- env,
- "java/lang/IllegalArgumentException",
- "Attempt to log an illegal item type.");
- return;
- }
- env->DeleteLocalRef(item);
- }
-
- env->DeleteLocalRef(items);
- }
-};
-
/*
* In class android.util.EventLog:
* static native int writeEvent(int tag, int value)
@@ -170,41 +65,80 @@ static jint android_util_EventLog_writeEvent_Long(JNIEnv* env, jobject clazz,
/*
* In class android.util.EventLog:
- * static native int writeEvent(long tag, List value)
+ * static native int writeEvent(int tag, String value)
*/
-static jint android_util_EventLog_writeEvent_List(JNIEnv* env, jobject clazz,
- jint tag, jobject value) {
- if (value == NULL) {
- jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
- env->ThrowNew(clazz, "writeEvent needs a value.");
- return -1;
- }
- ByteBuf byteBuf(INITAL_BUFFER_CAPACITY);
- byteBuf.putList(env, value);
- byteBuf.putByte((uint8_t)END_DELIMITER);
- int numBytesPut = byteBuf.len;
- int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut);
- return bytesWritten;
+static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz,
+ jint tag, jstring value) {
+ uint8_t buf[MAX_EVENT_PAYLOAD];
+
+ // Don't throw NPE -- I feel like it's sort of mean for a logging function
+ // to be all crashy if you pass in NULL -- but make the NULL value explicit.
+ const char *str = value != NULL ? env->GetStringUTFChars(value, NULL) : "NULL";
+ jint len = strlen(str);
+ const int max = sizeof(buf) - sizeof(len) - 2; // Type byte, final newline
+ if (len > max) len = max;
+
+ buf[0] = EVENT_TYPE_STRING;
+ memcpy(&buf[1], &len, sizeof(len));
+ memcpy(&buf[1 + sizeof(len)], str, len);
+ buf[1 + sizeof(len) + len] = '\n';
+
+ if (value != NULL) env->ReleaseStringUTFChars(value, str);
+ return android_bWriteLog(tag, buf, 2 + sizeof(len) + len);
}
/*
* In class android.util.EventLog:
- * static native int writeEvent(int tag, String value)
+ * static native int writeEvent(long tag, Object... value)
*/
-static jint android_util_EventLog_writeEvent_String(JNIEnv* env, jobject clazz,
- jint tag, jstring value) {
+static jint android_util_EventLog_writeEvent_Array(JNIEnv* env, jobject clazz,
+ jint tag, jobjectArray value) {
if (value == NULL) {
- jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
- env->ThrowNew(clazz, "logEvent needs a value.");
- return -1;
+ return android_util_EventLog_writeEvent_String(env, clazz, tag, NULL);
}
- ByteBuf byteBuf(INITAL_BUFFER_CAPACITY);
- byteBuf.putStringEvent(env, value);
- byteBuf.putByte((uint8_t)END_DELIMITER);
- int numBytesPut = byteBuf.len;
- int bytesWritten = android_bWriteLog(tag, byteBuf.buf, numBytesPut);
- return bytesWritten;
+ uint8_t buf[MAX_EVENT_PAYLOAD];
+ const size_t max = sizeof(buf) - 1; // leave room for final newline
+ size_t pos = 2; // Save room for type tag & array count
+
+ jsize copied = 0, num = env->GetArrayLength(value);
+ for (; copied < num && copied < 255; ++copied) {
+ jobject item = env->GetObjectArrayElement(value, copied);
+ if (item == NULL || env->IsInstanceOf(item, gStringClass)) {
+ if (pos + 1 + sizeof(jint) > max) break;
+ const char *str = item != NULL ? env->GetStringUTFChars((jstring) item, NULL) : "NULL";
+ jint len = strlen(str);
+ if (pos + 1 + sizeof(len) + len > max) len = max - pos - 1 - sizeof(len);
+ buf[pos++] = EVENT_TYPE_STRING;
+ memcpy(&buf[pos], &len, sizeof(len));
+ memcpy(&buf[pos + sizeof(len)], str, len);
+ pos += sizeof(len) + len;
+ if (item != NULL) env->ReleaseStringUTFChars((jstring) item, str);
+ } else if (env->IsInstanceOf(item, gIntegerClass)) {
+ jint intVal = env->GetIntField(item, gIntegerValueID);
+ if (pos + 1 + sizeof(intVal) > max) break;
+ buf[pos++] = EVENT_TYPE_INT;
+ memcpy(&buf[pos], &intVal, sizeof(intVal));
+ pos += sizeof(intVal);
+ } else if (env->IsInstanceOf(item, gLongClass)) {
+ jlong longVal = env->GetLongField(item, gLongValueID);
+ if (pos + 1 + sizeof(longVal) > max) break;
+ buf[pos++] = EVENT_TYPE_LONG;
+ memcpy(&buf[pos], &longVal, sizeof(longVal));
+ pos += sizeof(longVal);
+ } else {
+ jniThrowException(env,
+ "java/lang/IllegalArgumentException",
+ "Invalid payload item type");
+ return -1;
+ }
+ env->DeleteLocalRef(item);
+ }
+
+ buf[0] = EVENT_TYPE_LIST;
+ buf[1] = copied;
+ buf[pos++] = '\n';
+ return android_bWriteLog(tag, buf, pos);
}
/*
@@ -231,14 +165,32 @@ static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz,
jint *tagValues = env->GetIntArrayElements(tags, NULL);
uint8_t buf[LOGGER_ENTRY_MAX_LEN];
+ struct timeval timeout = {0, 0};
+ fd_set readset;
+ FD_ZERO(&readset);
+
for (;;) {
+ // Use a short select() to try to avoid problems hanging on read().
+ // This means we block for 5ms at the end of the log -- oh well.
+ timeout.tv_usec = 5000;
+ FD_SET(fd, &readset);
+ int r = select(fd + 1, &readset, NULL, NULL, &timeout);
+ if (r == 0) {
+ break; // no more events
+ } else if (r < 0 && errno == EINTR) {
+ continue; // interrupted by signal, try again
+ } else if (r < 0) {
+ jniThrowIOException(env, errno); // Will throw on return
+ break;
+ }
+
int len = read(fd, buf, sizeof(buf));
if (len == 0 || (len < 0 && errno == EAGAIN)) {
- break;
+ break; // no more events
+ } else if (len < 0 && errno == EINTR) {
+ continue; // interrupted by signal, try again
} else if (len < 0) {
- // This calls env->ThrowNew(), which doesn't throw an exception
- // now, but sets a flag to trigger an exception after we return.
- jniThrowIOException(env, errno);
+ jniThrowIOException(env, errno); // Will throw on return
break;
} else if ((size_t) len < sizeof(logger_entry) + sizeof(int32_t)) {
jniThrowException(env, "java/io/IOException", "Event too short");
@@ -276,81 +228,6 @@ static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz,
}
/*
- * In class android.util.EventLog:
- * static native void readEvents(String path, Collection<Event> output)
- *
- * Reads events from a file (See Checkin.Aggregation). Events are stored in
- * native raw format (logger_entry + payload).
- */
-static void android_util_EventLog_readEventsFile(JNIEnv* env, jobject clazz, jstring path,
- jobject out) {
- if (path == NULL || out == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return;
- }
-
- const char *pathString = env->GetStringUTFChars(path, 0);
- int fd = open(pathString, O_RDONLY | O_NONBLOCK);
- env->ReleaseStringUTFChars(path, pathString);
-
- if (fd < 0) {
- jniThrowIOException(env, errno);
- return;
- }
-
- uint8_t buf[LOGGER_ENTRY_MAX_LEN];
- for (;;) {
- // read log entry structure from file
- int len = read(fd, buf, sizeof(logger_entry));
- if (len == 0) {
- break; // end of file
- } else if (len < 0) {
- jniThrowIOException(env, errno);
- } else if ((size_t) len < sizeof(logger_entry)) {
- jniThrowException(env, "java/io/IOException", "Event header too short");
- break;
- }
-
- // read event payload
- logger_entry* entry = (logger_entry*) buf;
- if (entry->len > LOGGER_ENTRY_MAX_PAYLOAD) {
- jniThrowException(env,
- "java/lang/IllegalArgumentException",
- "Too much data for event payload. Corrupt file?");
- break;
- }
-
- len = read(fd, buf + sizeof(logger_entry), entry->len);
- if (len == 0) {
- break; // end of file
- } else if (len < 0) {
- jniThrowIOException(env, errno);
- } else if ((size_t) len < entry->len) {
- jniThrowException(env, "java/io/IOException", "Event payload too short");
- break;
- }
-
- // create EventLog$Event and add it to the collection
- int buffer_size = sizeof(logger_entry) + entry->len;
- jbyteArray array = env->NewByteArray(buffer_size);
- if (array == NULL) break;
-
- jbyte *bytes = env->GetByteArrayElements(array, NULL);
- memcpy(bytes, buf, buffer_size);
- env->ReleaseByteArrayElements(array, bytes, 0);
-
- jobject event = env->NewObject(gEventClass, gEventInitID, array);
- if (event == NULL) break;
-
- env->CallBooleanMethod(out, gCollectionAddID, event);
- env->DeleteLocalRef(event);
- env->DeleteLocalRef(array);
- }
-
- close(fd);
-}
-
-/*
* JNI registration.
*/
static JNINativeMethod gRegisterMethods[] = {
@@ -362,22 +239,17 @@ static JNINativeMethod gRegisterMethods[] = {
(void*) android_util_EventLog_writeEvent_String
},
{ "writeEvent",
- "(ILandroid/util/EventLog$List;)I",
- (void*) android_util_EventLog_writeEvent_List
+ "(I[Ljava/lang/Object;)I",
+ (void*) android_util_EventLog_writeEvent_Array
},
{ "readEvents",
"([ILjava/util/Collection;)V",
(void*) android_util_EventLog_readEvents
},
- { "readEvents",
- "(Ljava/lang/String;Ljava/util/Collection;)V",
- (void*) android_util_EventLog_readEventsFile
- }
};
static struct { const char *name; jclass *clazz; } gClasses[] = {
{ "android/util/EventLog$Event", &gEventClass },
- { "android/util/EventLog$List", &gListClass },
{ "java/lang/Integer", &gIntegerClass },
{ "java/lang/Long", &gLongClass },
{ "java/lang/String", &gStringClass },
@@ -386,7 +258,6 @@ static struct { const char *name; jclass *clazz; } gClasses[] = {
static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
{ &gIntegerClass, "value", "I", &gIntegerValueID },
- { &gListClass, "mItems", "[Ljava/lang/Object;", &gListItemsID },
{ &gLongClass, "value", "J", &gLongValueID },
};
@@ -430,4 +301,3 @@ int register_android_util_EventLog(JNIEnv* env) {
}
}; // namespace android
-
diff --git a/core/jni/android_util_FileObserver.cpp b/core/jni/android_util_FileObserver.cpp
index 13a1645..65e7130 100644
--- a/core/jni/android_util_FileObserver.cpp
+++ b/core/jni/android_util_FileObserver.cpp
@@ -71,28 +71,32 @@ static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd
return;
}
- while (num_bytes >= (int)sizeof(*event))
- {
- int event_size;
- event = (struct inotify_event *)(event_buf + event_pos);
-
+ while (num_bytes >= (int)sizeof(*event))
+ {
+ int event_size;
+ event = (struct inotify_event *)(event_buf + event_pos);
+
jstring path = NULL;
if (event->len > 0)
{
path = env->NewStringUTF(event->name);
}
-
- env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
+
+ env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
if (path != NULL)
{
env->DeleteLocalRef(path);
}
-
- event_size = sizeof(*event) + event->len;
- num_bytes -= event_size;
- event_pos += event_size;
- }
+
+ event_size = sizeof(*event) + event->len;
+ num_bytes -= event_size;
+ event_pos += event_size;
+ }
}
#endif // HAVE_INOTIFY
diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp
index 8316b03..6b97951 100644
--- a/core/jni/android_util_Log.cpp
+++ b/core/jni/android_util_Log.cpp
@@ -98,10 +98,10 @@ static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring
/*
* In class android.util.Log:
- * public static native int println(int priority, String tag, String msg)
+ * public static native int println_native(int buffer, int priority, String tag, String msg)
*/
-static jint android_util_Log_println(JNIEnv* env, jobject clazz,
- jint priority, jstring tagObj, jstring msgObj)
+static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
+ jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
@@ -116,11 +116,21 @@ static jint android_util_Log_println(JNIEnv* env, jobject clazz,
return -1;
}
+ if (bufID < 0 || bufID >= LOG_ID_MAX) {
+ jclass npeClazz;
+
+ npeClazz = env->FindClass("java/lang/NullPointerException");
+ assert(npeClazz != NULL);
+
+ env->ThrowNew(npeClazz, "bad bufID");
+ return -1;
+ }
+
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
- int res = android_writeLog((android_LogPriority) priority, tag, msg);
+ int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
@@ -135,7 +145,7 @@ static jint android_util_Log_println(JNIEnv* env, jobject clazz,
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
- { "println", "(ILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println },
+ { "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
int register_android_util_Log(JNIEnv* env)
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 094b02d..68be741 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -120,11 +120,7 @@ jint android_os_Process_myUid(JNIEnv* env, jobject clazz)
jint android_os_Process_myTid(JNIEnv* env, jobject clazz)
{
-#ifdef HAVE_GETTID
- return gettid();
-#else
- return getpid();
-#endif
+ return androidGetTid();
}
jint android_os_Process_getUidForName(JNIEnv* env, jobject clazz, jstring name)
@@ -191,15 +187,11 @@ jint android_os_Process_getGidForName(JNIEnv* env, jobject clazz, jstring name)
void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
- if (grp > ANDROID_TGROUP_MAX || grp < 0) {
- signalExceptionForGroupError(env, clazz, EINVAL);
+ int res = androidSetThreadSchedulingGroup(pid, grp);
+ if (res != NO_ERROR) {
+ signalExceptionForGroupError(env, clazz, res == BAD_VALUE ? EINVAL : errno);
return;
}
-
- if (set_sched_policy(pid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ?
- SP_BACKGROUND : SP_FOREGROUND)) {
- signalExceptionForGroupError(env, clazz, errno);
- }
}
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
@@ -275,22 +267,15 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin
void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz,
jint pid, jint pri)
{
- int rc = 0;
-
- if (pri >= ANDROID_PRIORITY_BACKGROUND) {
- rc = set_sched_policy(pid, SP_BACKGROUND);
- } else if (getpriority(PRIO_PROCESS, pid) >= ANDROID_PRIORITY_BACKGROUND) {
- rc = set_sched_policy(pid, SP_FOREGROUND);
- }
-
- if (rc) {
- signalExceptionForGroupError(env, clazz, errno);
- return;
- }
-
- if (setpriority(PRIO_PROCESS, pid, pri) < 0) {
- signalExceptionForPriorityError(env, clazz, errno);
+ int rc = androidSetThreadPriority(pid, pri);
+ if (rc != 0) {
+ if (rc == INVALID_OPERATION) {
+ signalExceptionForPriorityError(env, clazz, errno);
+ } else {
+ signalExceptionForGroupError(env, clazz, errno);
+ }
}
+
//LOGI("Setting priority of %d: %d, getpriority returns %d\n",
// pid, pri, getpriority(PRIO_PROCESS, pid));
}
@@ -326,8 +311,8 @@ jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
sprintf(text, "%d", adj);
write(fd, text, strlen(text));
close(fd);
- return true;
}
+ return true;
}
#endif
return false;
@@ -812,6 +797,13 @@ void android_os_Process_sendSignal(JNIEnv* env, jobject clazz, jint pid, jint si
}
}
+void android_os_Process_sendSignalQuiet(JNIEnv* env, jobject clazz, jint pid, jint sig)
+{
+ if (pid > 0) {
+ kill(pid, sig);
+ }
+}
+
static jlong android_os_Process_getElapsedCpuTime(JNIEnv* env, jobject clazz)
{
struct timespec ts;
@@ -869,6 +861,7 @@ static const JNINativeMethod methods[] = {
{"setUid", "(I)I", (void*)android_os_Process_setUid},
{"setGid", "(I)I", (void*)android_os_Process_setGid},
{"sendSignal", "(II)V", (void*)android_os_Process_sendSignal},
+ {"sendSignalQuiet", "(II)V", (void*)android_os_Process_sendSignalQuiet},
{"supportsProcesses", "()Z", (void*)android_os_Process_supportsProcesses},
{"getFreeMemory", "()J", (void*)android_os_Process_getFreeMemory},
{"readProcLines", "(Ljava/lang/String;[Ljava/lang/String;[J)V", (void*)android_os_Process_readProcLines},
diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp
index ffb271c..641fbce 100644
--- a/core/jni/android_util_StringBlock.cpp
+++ b/core/jni/android_util_StringBlock.cpp
@@ -89,6 +89,11 @@ static jstring android_content_StringBlock_nativeGetString(JNIEnv* env, jobject
}
size_t len;
+ const char* str8 = osb->string8At(idx, &len);
+ if (str8 != NULL) {
+ return env->NewStringUTF(str8);
+ }
+
const char16_t* str = osb->stringAt(idx, &len);
if (str == NULL) {
doThrow(env, "java/lang/IndexOutOfBoundsException");
diff --git a/core/jni/android_view_Display.cpp b/core/jni/android_view_Display.cpp
index bb7b5ef..2e160ae 100644
--- a/core/jni/android_view_Display.cpp
+++ b/core/jni/android_view_Display.cpp
@@ -17,7 +17,7 @@
#include <stdio.h>
#include <assert.h>
-#include <ui/SurfaceComposerClient.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
#include <ui/PixelFormat.h>
#include <ui/DisplayInfo.h>
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 40c8aa0..788374b 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -18,7 +18,7 @@
#include "android_util_Binder.h"
-#include <ui/SurfaceComposerClient.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
#include <ui/Region.h>
#include <ui/Rect.h>
@@ -35,6 +35,11 @@
namespace android {
+enum {
+ // should match Parcelable.java
+ PARCELABLE_WRITE_RETURN_VALUE = 0x0001
+};
+
// ----------------------------------------------------------------------------
static const char* const OutOfResourcesException =
@@ -185,7 +190,8 @@ static void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface)
static void Surface_init(
JNIEnv* env, jobject clazz,
- jobject session, jint pid, jint dpy, jint w, jint h, jint format, jint flags)
+ jobject session,
+ jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)
{
if (session == NULL) {
doThrow(env, "java/lang/NullPointerException");
@@ -195,7 +201,16 @@ static void Surface_init(
SurfaceComposerClient* client =
(SurfaceComposerClient*)env->GetIntField(session, sso.client);
- sp<SurfaceControl> surface(client->createSurface(pid, dpy, w, h, format, flags));
+ sp<SurfaceControl> surface;
+ if (jname == NULL) {
+ surface = client->createSurface(pid, dpy, w, h, format, flags);
+ } else {
+ const jchar* str = env->GetStringCritical(jname, 0);
+ const String8 name(str, env->GetStringLength(jname));
+ env->ReleaseStringCritical(jname, str);
+ surface = client->createSurface(pid, name, dpy, w, h, format, flags);
+ }
+
if (surface == 0) {
doThrow(env, OutOfResourcesException);
return;
@@ -214,6 +229,15 @@ static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel)
setSurface(env, clazz, rhs);
}
+static jint Surface_getIdentity(JNIEnv* env, jobject clazz)
+{
+ const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
+ if (control != 0) return (jint) control->getIdentity();
+ const sp<Surface>& surface(getSurface(env, clazz));
+ if (surface != 0) return (jint) surface->getIdentity();
+ return -1;
+}
+
static void Surface_destroy(JNIEnv* env, jobject clazz, uintptr_t *ostack)
{
const sp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
@@ -246,14 +270,14 @@ static inline SkBitmap::Config convertPixelFormat(PixelFormat format)
we can map to SkBitmap::kARGB_8888_Config, and optionally call
bitmap.setIsOpaque(true) on the resulting SkBitmap (as an accelerator)
*/
- switch (format) {
- case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config;
+ switch (format) {
+ case PIXEL_FORMAT_RGBX_8888: return SkBitmap::kARGB_8888_Config;
case PIXEL_FORMAT_RGBA_8888: return SkBitmap::kARGB_8888_Config;
case PIXEL_FORMAT_RGBA_4444: return SkBitmap::kARGB_4444_Config;
- case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config;
- case PIXEL_FORMAT_A_8: return SkBitmap::kA8_Config;
- default: return SkBitmap::kNo_Config;
- }
+ case PIXEL_FORMAT_RGB_565: return SkBitmap::kRGB_565_Config;
+ case PIXEL_FORMAT_A_8: return SkBitmap::kA8_Config;
+ default: return SkBitmap::kNo_Config;
+ }
}
static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
@@ -332,7 +356,7 @@ static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
env->SetIntField(dirtyRect, ro.b, bounds.bottom);
}
- return canvas;
+ return canvas;
}
static void Surface_unlockCanvasAndPost(
@@ -602,6 +626,9 @@ static void Surface_writeToParcel(
const sp<SurfaceControl>& control(getSurfaceControl(env, clazz));
SurfaceControl::writeSurfaceToParcel(control, parcel);
+ if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
+ setSurfaceControl(env, clazz, 0);
+ }
}
// ----------------------------------------------------------------------------
@@ -613,52 +640,53 @@ const char* const kSurfaceClassPathName = "android/view/Surface";
static void nativeClassInit(JNIEnv* env, jclass clazz);
static JNINativeMethod gSurfaceSessionMethods[] = {
- {"init", "()V", (void*)SurfaceSession_init },
- {"destroy", "()V", (void*)SurfaceSession_destroy },
+ {"init", "()V", (void*)SurfaceSession_init },
+ {"destroy", "()V", (void*)SurfaceSession_destroy },
{"kill", "()V", (void*)SurfaceSession_kill },
};
static JNINativeMethod gSurfaceMethods[] = {
{"nativeClassInit", "()V", (void*)nativeClassInit },
- {"init", "(Landroid/view/SurfaceSession;IIIIII)V", (void*)Surface_init },
+ {"init", "(Landroid/view/SurfaceSession;ILjava/lang/String;IIIII)V", (void*)Surface_init },
{"init", "(Landroid/os/Parcel;)V", (void*)Surface_initParcel },
+ {"getIdentity", "()I", (void*)Surface_getIdentity },
{"destroy", "()V", (void*)Surface_destroy },
{"release", "()V", (void*)Surface_release },
- {"copyFrom", "(Landroid/view/Surface;)V", (void*)Surface_copyFrom },
- {"isValid", "()Z", (void*)Surface_isValid },
- {"lockCanvasNative", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;", (void*)Surface_lockCanvas },
- {"unlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvasAndPost },
- {"unlockCanvas", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvas },
- {"openTransaction", "()V", (void*)Surface_openTransaction },
+ {"copyFrom", "(Landroid/view/Surface;)V", (void*)Surface_copyFrom },
+ {"isValid", "()Z", (void*)Surface_isValid },
+ {"lockCanvasNative", "(Landroid/graphics/Rect;)Landroid/graphics/Canvas;", (void*)Surface_lockCanvas },
+ {"unlockCanvasAndPost", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvasAndPost },
+ {"unlockCanvas", "(Landroid/graphics/Canvas;)V", (void*)Surface_unlockCanvas },
+ {"openTransaction", "()V", (void*)Surface_openTransaction },
{"closeTransaction", "()V", (void*)Surface_closeTransaction },
{"setOrientation", "(III)V", (void*)Surface_setOrientation },
{"freezeDisplay", "(I)V", (void*)Surface_freezeDisplay },
{"unfreezeDisplay", "(I)V", (void*)Surface_unfreezeDisplay },
{"setLayer", "(I)V", (void*)Surface_setLayer },
- {"setPosition", "(II)V",(void*)Surface_setPosition },
- {"setSize", "(II)V",(void*)Surface_setSize },
- {"hide", "()V", (void*)Surface_hide },
- {"show", "()V", (void*)Surface_show },
- {"freeze", "()V", (void*)Surface_freeze },
- {"unfreeze", "()V", (void*)Surface_unfreeze },
- {"setFlags", "(II)V",(void*)Surface_setFlags },
- {"setTransparentRegionHint","(Landroid/graphics/Region;)V", (void*)Surface_setTransparentRegion },
- {"setAlpha", "(F)V", (void*)Surface_setAlpha },
- {"setMatrix", "(FFFF)V", (void*)Surface_setMatrix },
- {"setFreezeTint", "(I)V", (void*)Surface_setFreezeTint },
- {"readFromParcel", "(Landroid/os/Parcel;)V", (void*)Surface_readFromParcel },
- {"writeToParcel", "(Landroid/os/Parcel;I)V", (void*)Surface_writeToParcel },
+ {"setPosition", "(II)V",(void*)Surface_setPosition },
+ {"setSize", "(II)V",(void*)Surface_setSize },
+ {"hide", "()V", (void*)Surface_hide },
+ {"show", "()V", (void*)Surface_show },
+ {"freeze", "()V", (void*)Surface_freeze },
+ {"unfreeze", "()V", (void*)Surface_unfreeze },
+ {"setFlags", "(II)V",(void*)Surface_setFlags },
+ {"setTransparentRegionHint","(Landroid/graphics/Region;)V", (void*)Surface_setTransparentRegion },
+ {"setAlpha", "(F)V", (void*)Surface_setAlpha },
+ {"setMatrix", "(FFFF)V", (void*)Surface_setMatrix },
+ {"setFreezeTint", "(I)V", (void*)Surface_setFreezeTint },
+ {"readFromParcel", "(Landroid/os/Parcel;)V", (void*)Surface_readFromParcel },
+ {"writeToParcel", "(Landroid/os/Parcel;I)V", (void*)Surface_writeToParcel },
};
void nativeClassInit(JNIEnv* env, jclass clazz)
{
so.surface = env->GetFieldID(clazz, "mSurface", "I");
so.surfaceControl = env->GetFieldID(clazz, "mSurfaceControl", "I");
- so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I");
- so.canvas = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;");
+ so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I");
+ so.canvas = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;");
jclass surfaceSession = env->FindClass("android/view/SurfaceSession");
- sso.client = env->GetFieldID(surfaceSession, "mClient", "I");
+ sso.client = env->GetFieldID(surfaceSession, "mClient", "I");
jclass canvas = env->FindClass("android/graphics/Canvas");
no.native_canvas = env->GetFieldID(canvas, "mNativeCanvas", "I");
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index 974fc0b..6a8c4b9 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -21,7 +21,7 @@
#include <EGL/egl.h>
#include <GLES/gl.h>
-#include <ui/Surface.h>
+#include <surfaceflinger/Surface.h>
#include <SkBitmap.h>
#include <SkPixelRef.h>
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index 3e0aea5..bf613e1 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -24,6 +24,23 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
+// Work around differences between the generated name and the actual name.
+
+#define glBlendEquation glBlendEquationOES
+#define glBlendEquationSeparate glBlendEquationSeparateOES
+#define glBlendFuncSeparate glBlendFuncSeparateOES
+#define glGetTexGenfv glGetTexGenfvOES
+#define glGetTexGeniv glGetTexGenivOES
+#define glGetTexGenxv glGetTexGenxvOES
+#define glTexGenf glTexGenfOES
+#define glTexGenfv glTexGenfvOES
+#define glTexGeni glTexGeniOES
+#define glTexGeniv glTexGenivOES
+#define glTexGenx glTexGenxOES
+#define glTexGenxv glTexGenxvOES
+
+
+
/* special calls implemented in Android's GLES wrapper used to more
* efficiently bound-check passed arrays */
extern "C" {
@@ -35,6 +52,12 @@ GL_API void GL_APIENTRY glTexCoordPointerBounds(GLint size, GLenum type,
GLsizei stride, const GLvoid *pointer, GLsizei count);
GL_API void GL_APIENTRY glVertexPointerBounds(GLint size, GLenum type,
GLsizei stride, const GLvoid *pointer, GLsizei count);
+GL_API void GL_APIENTRY glPointSizePointerOESBounds(GLenum type,
+ GLsizei stride, const GLvoid *pointer, GLsizei count);
+GL_API void GL_APIENTRY glMatrixIndexPointerOESBounds(GLint size, GLenum type,
+ GLsizei stride, const GLvoid *pointer, GLsizei count);
+GL_API void GL_APIENTRY glWeightPointerOESBounds(GLint size, GLenum type,
+ GLsizei stride, const GLvoid *pointer, GLsizei count);
}
static int initialized = 0;
@@ -53,6 +76,11 @@ static jmethodID allowIndirectBuffersID;
static jfieldID positionID;
static jfieldID limitID;
static jfieldID elementSizeShiftID;
+static jfieldID haveCheckedExtensionsID;
+static jfieldID have_OES_blend_equation_separateID;
+static jfieldID have_OES_blend_subtractID;
+static jfieldID have_OES_framebuffer_objectID;
+static jfieldID have_OES_texture_cube_mapID;
/* Cache method IDs each time the class is loaded. */
@@ -67,6 +95,11 @@ nativeClassInitBuffer(JNIEnv *_env)
jclass g11impClassLocal = _env->FindClass("com/google/android/gles_jni/GLImpl");
G11ImplClass = (jclass) _env->NewGlobalRef(g11impClassLocal);
+ haveCheckedExtensionsID = _env->GetFieldID(G11ImplClass, "haveCheckedExtensions", "Z");
+ have_OES_blend_equation_separateID = _env->GetFieldID(G11ImplClass, "have_OES_blend_equation_separate", "Z");
+ have_OES_blend_subtractID = _env->GetFieldID(G11ImplClass, "have_OES_blend_subtract", "Z");
+ have_OES_framebuffer_objectID = _env->GetFieldID(G11ImplClass, "have_OES_framebuffer_object", "Z");
+ have_OES_texture_cube_mapID = _env->GetFieldID(G11ImplClass, "have_OES_texture_cube_map", "Z");
getBasePointerID = _env->GetStaticMethodID(nioAccessClass,
"getBasePointer", "(Ljava/nio/Buffer;)J");
@@ -188,6 +221,64 @@ getNumCompressedTextureFormats() {
return numCompressedTextureFormats;
}
+// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
+// terminated by either 0 or space, while pExtension is terminated by 0.
+
+static bool
+extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
+ while (true) {
+ char a = *pExtensions++;
+ char b = *pExtension++;
+ bool aEnd = a == '\0' || a == ' ';
+ bool bEnd = b == '\0';
+ if ( aEnd || bEnd) {
+ return aEnd == bEnd;
+ }
+ if ( a != b ) {
+ return false;
+ }
+ }
+}
+
+static const GLubyte*
+nextExtension(const GLubyte* pExtensions) {
+ while (true) {
+ char a = *pExtensions++;
+ if ( a == '\0') {
+ return pExtensions-1;
+ } else if ( a == ' ') {
+ return pExtensions;
+ }
+ }
+}
+
+static bool
+checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
+ for (;*pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
+ if (extensionEqual(pExtensions, pExtension)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+supportsExtension(JNIEnv *_env, jobject impl, jfieldID fieldId) {
+ if (!_env->GetBooleanField(impl, haveCheckedExtensionsID)) {
+ _env->SetBooleanField(impl, haveCheckedExtensionsID, true);
+ const GLubyte* sExtensions = glGetString(GL_EXTENSIONS);
+ _env->SetBooleanField(impl, have_OES_blend_equation_separateID,
+ checkForExtension(sExtensions, (const GLubyte*) "GL_OES_blend_equation_separate"));
+ _env->SetBooleanField(impl, have_OES_blend_subtractID,
+ checkForExtension(sExtensions, (const GLubyte*) "GL_OES_blend_subtract"));
+ _env->SetBooleanField(impl, have_OES_framebuffer_objectID,
+ checkForExtension(sExtensions, (const GLubyte*) "GL_OES_framebuffer_object"));
+ _env->SetBooleanField(impl, have_OES_texture_cube_mapID,
+ checkForExtension(sExtensions, (const GLubyte*) "GL_OES_texture_cube_map"));
+ }
+ return _env->GetBooleanField(impl, fieldId);
+}
+
// --------------------------------------------------------------------------
/* void glActiveTexture ( GLenum texture ) */
@@ -5391,21 +5482,24 @@ exit:
/* void glPointSizePointerOES ( GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glPointSizePointerOES__IILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf) {
+android_glPointSizePointerOESBounds__IILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint type, jint stride, jobject pointer_buf, jint remaining) {
jarray _array = (jarray) 0;
jint _remaining;
GLvoid *pointer = (GLvoid *) 0;
- pointer = (GLvoid *)getPointer(_env, pointer_buf, &_array, &_remaining);
- glPointSizePointerOES(
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glPointSizePointerOESBounds(
(GLenum)type,
(GLsizei)stride,
- (GLvoid *)pointer
+ (GLvoid *)pointer,
+ (GLsizei)remaining
);
- if (_array) {
- releasePointer(_env, _array, pointer, JNI_FALSE);
- }
}
/* void glTexCoordPointer ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
@@ -5754,8 +5848,9 @@ android_glVertexPointer__IIII
static void
android_glCurrentPaletteMatrixOES__I
(JNIEnv *_env, jobject _this, jint matrixpaletteindex) {
- _env->ThrowNew(UOEClass,
- "glCurrentPaletteMatrixOES");
+ glCurrentPaletteMatrixOES(
+ (GLuint)matrixpaletteindex
+ );
}
/* void glDrawTexfOES ( GLfloat x, GLfloat y, GLfloat z, GLfloat width, GLfloat height ) */
@@ -6050,355 +6145,1161 @@ exit:
static void
android_glLoadPaletteFromModelViewMatrixOES__
(JNIEnv *_env, jobject _this) {
- _env->ThrowNew(UOEClass,
- "glLoadPaletteFromModelViewMatrixOES");
+ glLoadPaletteFromModelViewMatrixOES();
}
/* void glMatrixIndexPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
- _env->ThrowNew(UOEClass,
- "glMatrixIndexPointerOES");
+android_glMatrixIndexPointerOESBounds__IIILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pointer = (GLvoid *) 0;
+
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glMatrixIndexPointerOESBounds(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (GLvoid *)pointer,
+ (GLsizei)remaining
+ );
}
/* void glMatrixIndexPointerOES ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
static void
android_glMatrixIndexPointerOES__IIII
(JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
- _env->ThrowNew(UOEClass,
- "glMatrixIndexPointerOES");
+ glMatrixIndexPointerOES(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (const GLvoid *)offset
+ );
}
/* void glWeightPointerOES ( GLint size, GLenum type, GLsizei stride, const GLvoid *pointer ) */
static void
-android_glWeightPointerOES__IIILjava_nio_Buffer_2
- (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf) {
- _env->ThrowNew(UOEClass,
- "glWeightPointerOES");
+android_glWeightPointerOESBounds__IIILjava_nio_Buffer_2I
+ (JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jobject pointer_buf, jint remaining) {
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLvoid *pointer = (GLvoid *) 0;
+
+ if (pointer_buf) {
+ pointer = (GLvoid *) getDirectBufferPointer(_env, pointer_buf);
+ if ( ! pointer ) {
+ return;
+ }
+ }
+ glWeightPointerOESBounds(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (GLvoid *)pointer,
+ (GLsizei)remaining
+ );
}
/* void glWeightPointerOES ( GLint size, GLenum type, GLsizei stride, GLint offset ) */
static void
android_glWeightPointerOES__IIII
(JNIEnv *_env, jobject _this, jint size, jint type, jint stride, jint offset) {
- _env->ThrowNew(UOEClass,
- "glWeightPointerOES");
+ glWeightPointerOES(
+ (GLint)size,
+ (GLenum)type,
+ (GLsizei)stride,
+ (const GLvoid *)offset
+ );
}
/* void glBindFramebufferOES ( GLint target, GLint framebuffer ) */
static void
android_glBindFramebufferOES__II
(JNIEnv *_env, jobject _this, jint target, jint framebuffer) {
- _env->ThrowNew(UOEClass,
- "glBindFramebufferOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glBindFramebufferOES");
+ return;
+ }
+ glBindFramebufferOES(
+ (GLint)target,
+ (GLint)framebuffer
+ );
}
/* void glBindRenderbufferOES ( GLint target, GLint renderbuffer ) */
static void
android_glBindRenderbufferOES__II
(JNIEnv *_env, jobject _this, jint target, jint renderbuffer) {
- _env->ThrowNew(UOEClass,
- "glBindRenderbufferOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glBindRenderbufferOES");
+ return;
+ }
+ glBindRenderbufferOES(
+ (GLint)target,
+ (GLint)renderbuffer
+ );
}
/* void glBlendEquation ( GLint mode ) */
static void
android_glBlendEquation__I
(JNIEnv *_env, jobject _this, jint mode) {
- _env->ThrowNew(UOEClass,
- "glBlendEquation");
+ if (! supportsExtension(_env, _this, have_OES_blend_subtractID)) {
+ _env->ThrowNew(UOEClass,
+ "glBlendEquation");
+ return;
+ }
+ glBlendEquation(
+ (GLint)mode
+ );
}
/* void glBlendEquationSeparate ( GLint modeRGB, GLint modeAlpha ) */
static void
android_glBlendEquationSeparate__II
(JNIEnv *_env, jobject _this, jint modeRGB, jint modeAlpha) {
- _env->ThrowNew(UOEClass,
- "glBlendEquationSeparate");
+ if (! supportsExtension(_env, _this, have_OES_blend_equation_separateID)) {
+ _env->ThrowNew(UOEClass,
+ "glBlendEquationSeparate");
+ return;
+ }
+ glBlendEquationSeparate(
+ (GLint)modeRGB,
+ (GLint)modeAlpha
+ );
}
/* void glBlendFuncSeparate ( GLint srcRGB, GLint dstRGB, GLint srcAlpha, GLint dstAlpha ) */
static void
android_glBlendFuncSeparate__IIII
(JNIEnv *_env, jobject _this, jint srcRGB, jint dstRGB, jint srcAlpha, jint dstAlpha) {
- _env->ThrowNew(UOEClass,
- "glBlendFuncSeparate");
+ if (! supportsExtension(_env, _this, have_OES_blend_equation_separateID)) {
+ _env->ThrowNew(UOEClass,
+ "glBlendFuncSeparate");
+ return;
+ }
+ glBlendFuncSeparate(
+ (GLint)srcRGB,
+ (GLint)dstRGB,
+ (GLint)srcAlpha,
+ (GLint)dstAlpha
+ );
}
/* GLint glCheckFramebufferStatusOES ( GLint target ) */
static jint
android_glCheckFramebufferStatusOES__I
(JNIEnv *_env, jobject _this, jint target) {
- _env->ThrowNew(UOEClass,
- "glCheckFramebufferStatusOES");
- return 0;
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glCheckFramebufferStatusOES");
+ return 0;
+ }
+ GLint _returnValue = 0;
+ _returnValue = glCheckFramebufferStatusOES(
+ (GLint)target
+ );
+ return _returnValue;
}
-/* void glDeleteFramebuffersOES ( GLint n, GLint *framebuffers ) */
+/* void glDeleteFramebuffersOES ( GLint n, GLuint *framebuffers ) */
static void
android_glDeleteFramebuffersOES__I_3II
(JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glDeleteFramebuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glDeleteFramebuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ GLuint *framebuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ if (!framebuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "framebuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(framebuffers_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ framebuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(framebuffers_ref, (jboolean *)0);
+ framebuffers = framebuffers_base + offset;
+
+ glDeleteFramebuffersOES(
+ (GLint)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (framebuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(framebuffers_ref, framebuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
-/* void glDeleteFramebuffersOES ( GLint n, GLint *framebuffers ) */
+/* void glDeleteFramebuffersOES ( GLint n, GLuint *framebuffers ) */
static void
android_glDeleteFramebuffersOES__ILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
- _env->ThrowNew(UOEClass,
- "glDeleteFramebuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glDeleteFramebuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ framebuffers = (GLuint *)getPointer(_env, framebuffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glDeleteFramebuffersOES(
+ (GLint)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, framebuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
-/* void glDeleteRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+/* void glDeleteRenderbuffersOES ( GLint n, GLuint *renderbuffers ) */
static void
android_glDeleteRenderbuffersOES__I_3II
(JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glDeleteRenderbuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glDeleteRenderbuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ GLuint *renderbuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ if (!renderbuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "renderbuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(renderbuffers_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ renderbuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(renderbuffers_ref, (jboolean *)0);
+ renderbuffers = renderbuffers_base + offset;
+
+ glDeleteRenderbuffersOES(
+ (GLint)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (renderbuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(renderbuffers_ref, renderbuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
-/* void glDeleteRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+/* void glDeleteRenderbuffersOES ( GLint n, GLuint *renderbuffers ) */
static void
android_glDeleteRenderbuffersOES__ILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
- _env->ThrowNew(UOEClass,
- "glDeleteRenderbuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glDeleteRenderbuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ renderbuffers = (GLuint *)getPointer(_env, renderbuffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glDeleteRenderbuffersOES(
+ (GLint)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, renderbuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glFramebufferRenderbufferOES ( GLint target, GLint attachment, GLint renderbuffertarget, GLint renderbuffer ) */
static void
android_glFramebufferRenderbufferOES__IIII
(JNIEnv *_env, jobject _this, jint target, jint attachment, jint renderbuffertarget, jint renderbuffer) {
- _env->ThrowNew(UOEClass,
- "glFramebufferRenderbufferOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glFramebufferRenderbufferOES");
+ return;
+ }
+ glFramebufferRenderbufferOES(
+ (GLint)target,
+ (GLint)attachment,
+ (GLint)renderbuffertarget,
+ (GLint)renderbuffer
+ );
}
/* void glFramebufferTexture2DOES ( GLint target, GLint attachment, GLint textarget, GLint texture, GLint level ) */
static void
android_glFramebufferTexture2DOES__IIIII
(JNIEnv *_env, jobject _this, jint target, jint attachment, jint textarget, jint texture, jint level) {
- _env->ThrowNew(UOEClass,
- "glFramebufferTexture2DOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glFramebufferTexture2DOES");
+ return;
+ }
+ glFramebufferTexture2DOES(
+ (GLint)target,
+ (GLint)attachment,
+ (GLint)textarget,
+ (GLint)texture,
+ (GLint)level
+ );
}
/* void glGenerateMipmapOES ( GLint target ) */
static void
android_glGenerateMipmapOES__I
(JNIEnv *_env, jobject _this, jint target) {
- _env->ThrowNew(UOEClass,
- "glGenerateMipmapOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGenerateMipmapOES");
+ return;
+ }
+ glGenerateMipmapOES(
+ (GLint)target
+ );
}
-/* void glGenFramebuffersOES ( GLint n, GLint *framebuffers ) */
+/* void glGenFramebuffersOES ( GLint n, GLuint *framebuffers ) */
static void
android_glGenFramebuffersOES__I_3II
(JNIEnv *_env, jobject _this, jint n, jintArray framebuffers_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGenFramebuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGenFramebuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ GLuint *framebuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ if (!framebuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "framebuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(framebuffers_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ framebuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(framebuffers_ref, (jboolean *)0);
+ framebuffers = framebuffers_base + offset;
+
+ glGenFramebuffersOES(
+ (GLint)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (framebuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(framebuffers_ref, framebuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
-/* void glGenFramebuffersOES ( GLint n, GLint *framebuffers ) */
+/* void glGenFramebuffersOES ( GLint n, GLuint *framebuffers ) */
static void
android_glGenFramebuffersOES__ILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint n, jobject framebuffers_buf) {
- _env->ThrowNew(UOEClass,
- "glGenFramebuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGenFramebuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *framebuffers = (GLuint *) 0;
+
+ framebuffers = (GLuint *)getPointer(_env, framebuffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glGenFramebuffersOES(
+ (GLint)n,
+ (GLuint *)framebuffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, framebuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
-/* void glGenRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+/* void glGenRenderbuffersOES ( GLint n, GLuint *renderbuffers ) */
static void
android_glGenRenderbuffersOES__I_3II
(JNIEnv *_env, jobject _this, jint n, jintArray renderbuffers_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGenRenderbuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGenRenderbuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ GLuint *renderbuffers_base = (GLuint *) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ if (!renderbuffers_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "renderbuffers == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(renderbuffers_ref) - offset;
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "length - offset < n");
+ goto exit;
+ }
+ renderbuffers_base = (GLuint *)
+ _env->GetPrimitiveArrayCritical(renderbuffers_ref, (jboolean *)0);
+ renderbuffers = renderbuffers_base + offset;
+
+ glGenRenderbuffersOES(
+ (GLint)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (renderbuffers_base) {
+ _env->ReleasePrimitiveArrayCritical(renderbuffers_ref, renderbuffers_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
-/* void glGenRenderbuffersOES ( GLint n, GLint *renderbuffers ) */
+/* void glGenRenderbuffersOES ( GLint n, GLuint *renderbuffers ) */
static void
android_glGenRenderbuffersOES__ILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint n, jobject renderbuffers_buf) {
- _env->ThrowNew(UOEClass,
- "glGenRenderbuffersOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGenRenderbuffersOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLuint *renderbuffers = (GLuint *) 0;
+
+ renderbuffers = (GLuint *)getPointer(_env, renderbuffers_buf, &_array, &_remaining);
+ if (_remaining < n) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "remaining() < n");
+ goto exit;
+ }
+ glGenRenderbuffersOES(
+ (GLint)n,
+ (GLuint *)renderbuffers
+ );
+
+exit:
+ if (_array) {
+ releasePointer(_env, _array, renderbuffers, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glGetFramebufferAttachmentParameterivOES ( GLint target, GLint attachment, GLint pname, GLint *params ) */
static void
android_glGetFramebufferAttachmentParameterivOES__III_3II
(JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGetFramebufferAttachmentParameterivOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetFramebufferAttachmentParameterivOES");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetFramebufferAttachmentParameterivOES(
+ (GLint)target,
+ (GLint)attachment,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glGetFramebufferAttachmentParameterivOES ( GLint target, GLint attachment, GLint pname, GLint *params ) */
static void
android_glGetFramebufferAttachmentParameterivOES__IIILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint target, jint attachment, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glGetFramebufferAttachmentParameterivOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetFramebufferAttachmentParameterivOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetFramebufferAttachmentParameterivOES(
+ (GLint)target,
+ (GLint)attachment,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glGetRenderbufferParameterivOES ( GLint target, GLint pname, GLint *params ) */
static void
android_glGetRenderbufferParameterivOES__II_3II
(JNIEnv *_env, jobject _this, jint target, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGetRenderbufferParameterivOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetRenderbufferParameterivOES");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetRenderbufferParameterivOES(
+ (GLint)target,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glGetRenderbufferParameterivOES ( GLint target, GLint pname, GLint *params ) */
static void
android_glGetRenderbufferParameterivOES__IILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint target, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glGetRenderbufferParameterivOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetRenderbufferParameterivOES");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetRenderbufferParameterivOES(
+ (GLint)target,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glGetTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
static void
android_glGetTexGenfv__II_3FI
(JNIEnv *_env, jobject _this, jint coord, jint pname, jfloatArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGetTexGenfv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGenfv");
+ return;
+ }
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetTexGenfv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glGetTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
static void
android_glGetTexGenfv__IILjava_nio_FloatBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glGetTexGenfv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGenfv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetTexGenfv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLfloat *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glGetTexGeniv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glGetTexGeniv__II_3II
(JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGetTexGeniv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGeniv");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetTexGeniv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glGetTexGeniv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glGetTexGeniv__IILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glGetTexGeniv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGeniv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetTexGeniv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glGetTexGenxv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glGetTexGenxv__II_3II
(JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glGetTexGenxv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGenxv");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glGetTexGenxv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glGetTexGenxv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glGetTexGenxv__IILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glGetTexGenxv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glGetTexGenxv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glGetTexGenxv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* GLboolean glIsFramebufferOES ( GLint framebuffer ) */
static jboolean
android_glIsFramebufferOES__I
(JNIEnv *_env, jobject _this, jint framebuffer) {
- _env->ThrowNew(UOEClass,
- "glIsFramebufferOES");
- return JNI_FALSE;
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glIsFramebufferOES");
+ return JNI_FALSE;
+ }
+ GLboolean _returnValue = JNI_FALSE;
+ _returnValue = glIsFramebufferOES(
+ (GLint)framebuffer
+ );
+ return _returnValue;
}
/* GLboolean glIsRenderbufferOES ( GLint renderbuffer ) */
static jboolean
android_glIsRenderbufferOES__I
(JNIEnv *_env, jobject _this, jint renderbuffer) {
- _env->ThrowNew(UOEClass,
- "glIsRenderbufferOES");
- return JNI_FALSE;
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glIsRenderbufferOES");
+ return JNI_FALSE;
+ }
+ GLboolean _returnValue = JNI_FALSE;
+ _returnValue = glIsRenderbufferOES(
+ (GLint)renderbuffer
+ );
+ return _returnValue;
}
/* void glRenderbufferStorageOES ( GLint target, GLint internalformat, GLint width, GLint height ) */
static void
android_glRenderbufferStorageOES__IIII
(JNIEnv *_env, jobject _this, jint target, jint internalformat, jint width, jint height) {
- _env->ThrowNew(UOEClass,
- "glRenderbufferStorageOES");
+ if (! supportsExtension(_env, _this, have_OES_framebuffer_objectID)) {
+ _env->ThrowNew(UOEClass,
+ "glRenderbufferStorageOES");
+ return;
+ }
+ glRenderbufferStorageOES(
+ (GLint)target,
+ (GLint)internalformat,
+ (GLint)width,
+ (GLint)height
+ );
}
/* void glTexGenf ( GLint coord, GLint pname, GLfloat param ) */
static void
android_glTexGenf__IIF
(JNIEnv *_env, jobject _this, jint coord, jint pname, jfloat param) {
- _env->ThrowNew(UOEClass,
- "glTexGenf");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenf");
+ return;
+ }
+ glTexGenf(
+ (GLint)coord,
+ (GLint)pname,
+ (GLfloat)param
+ );
}
/* void glTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
static void
android_glTexGenfv__II_3FI
(JNIEnv *_env, jobject _this, jint coord, jint pname, jfloatArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glTexGenfv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenfv");
+ return;
+ }
+ jint _exception = 0;
+ GLfloat *params_base = (GLfloat *) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLfloat *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glTexGenfv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLfloat *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glTexGenfv ( GLint coord, GLint pname, GLfloat *params ) */
static void
android_glTexGenfv__IILjava_nio_FloatBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glTexGenfv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenfv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLfloat *params = (GLfloat *) 0;
+
+ params = (GLfloat *)getPointer(_env, params_buf, &_array, &_remaining);
+ glTexGenfv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLfloat *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glTexGeni ( GLint coord, GLint pname, GLint param ) */
static void
android_glTexGeni__III
(JNIEnv *_env, jobject _this, jint coord, jint pname, jint param) {
- _env->ThrowNew(UOEClass,
- "glTexGeni");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGeni");
+ return;
+ }
+ glTexGeni(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint)param
+ );
}
/* void glTexGeniv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glTexGeniv__II_3II
(JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glTexGeniv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGeniv");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glTexGeniv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glTexGeniv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glTexGeniv__IILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glTexGeniv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGeniv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glTexGeniv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
/* void glTexGenx ( GLint coord, GLint pname, GLint param ) */
static void
android_glTexGenx__III
(JNIEnv *_env, jobject _this, jint coord, jint pname, jint param) {
- _env->ThrowNew(UOEClass,
- "glTexGenx");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenx");
+ return;
+ }
+ glTexGenx(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint)param
+ );
}
/* void glTexGenxv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glTexGenxv__II_3II
(JNIEnv *_env, jobject _this, jint coord, jint pname, jintArray params_ref, jint offset) {
- _env->ThrowNew(UOEClass,
- "glTexGenxv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenxv");
+ return;
+ }
+ jint _exception = 0;
+ GLint *params_base = (GLint *) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ if (!params_ref) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "params == null");
+ goto exit;
+ }
+ if (offset < 0) {
+ _exception = 1;
+ _env->ThrowNew(IAEClass, "offset < 0");
+ goto exit;
+ }
+ _remaining = _env->GetArrayLength(params_ref) - offset;
+ params_base = (GLint *)
+ _env->GetPrimitiveArrayCritical(params_ref, (jboolean *)0);
+ params = params_base + offset;
+
+ glTexGenxv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+
+exit:
+ if (params_base) {
+ _env->ReleasePrimitiveArrayCritical(params_ref, params_base,
+ _exception ? JNI_ABORT: 0);
+ }
}
/* void glTexGenxv ( GLint coord, GLint pname, GLint *params ) */
static void
android_glTexGenxv__IILjava_nio_IntBuffer_2
(JNIEnv *_env, jobject _this, jint coord, jint pname, jobject params_buf) {
- _env->ThrowNew(UOEClass,
- "glTexGenxv");
+ if (! supportsExtension(_env, _this, have_OES_texture_cube_mapID)) {
+ _env->ThrowNew(UOEClass,
+ "glTexGenxv");
+ return;
+ }
+ jint _exception = 0;
+ jarray _array = (jarray) 0;
+ jint _remaining;
+ GLint *params = (GLint *) 0;
+
+ params = (GLint *)getPointer(_env, params_buf, &_array, &_remaining);
+ glTexGenxv(
+ (GLint)coord,
+ (GLint)pname,
+ (GLint *)params
+ );
+ if (_array) {
+ releasePointer(_env, _array, params, _exception ? JNI_FALSE : JNI_TRUE);
+ }
}
static const char *classPathName = "com/google/android/gles_jni/GLImpl";
@@ -6584,7 +7485,7 @@ static JNINativeMethod methods[] = {
{"glPointParameterx", "(II)V", (void *) android_glPointParameterx__II },
{"glPointParameterxv", "(I[II)V", (void *) android_glPointParameterxv__I_3II },
{"glPointParameterxv", "(ILjava/nio/IntBuffer;)V", (void *) android_glPointParameterxv__ILjava_nio_IntBuffer_2 },
-{"glPointSizePointerOES", "(IILjava/nio/Buffer;)V", (void *) android_glPointSizePointerOES__IILjava_nio_Buffer_2 },
+{"glPointSizePointerOESBounds", "(IILjava/nio/Buffer;I)V", (void *) android_glPointSizePointerOESBounds__IILjava_nio_Buffer_2I },
{"glTexCoordPointer", "(IIII)V", (void *) android_glTexCoordPointer__IIII },
{"glTexEnvi", "(III)V", (void *) android_glTexEnvi__III },
{"glTexEnviv", "(II[II)V", (void *) android_glTexEnviv__II_3II },
@@ -6611,9 +7512,9 @@ static JNINativeMethod methods[] = {
{"glDrawTexxvOES", "([II)V", (void *) android_glDrawTexxvOES___3II },
{"glDrawTexxvOES", "(Ljava/nio/IntBuffer;)V", (void *) android_glDrawTexxvOES__Ljava_nio_IntBuffer_2 },
{"glLoadPaletteFromModelViewMatrixOES", "()V", (void *) android_glLoadPaletteFromModelViewMatrixOES__ },
-{"glMatrixIndexPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glMatrixIndexPointerOES__IIILjava_nio_Buffer_2 },
+{"glMatrixIndexPointerOESBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glMatrixIndexPointerOESBounds__IIILjava_nio_Buffer_2I },
{"glMatrixIndexPointerOES", "(IIII)V", (void *) android_glMatrixIndexPointerOES__IIII },
-{"glWeightPointerOES", "(IIILjava/nio/Buffer;)V", (void *) android_glWeightPointerOES__IIILjava_nio_Buffer_2 },
+{"glWeightPointerOESBounds", "(IIILjava/nio/Buffer;I)V", (void *) android_glWeightPointerOESBounds__IIILjava_nio_Buffer_2I },
{"glWeightPointerOES", "(IIII)V", (void *) android_glWeightPointerOES__IIII },
{"glBindFramebufferOES", "(II)V", (void *) android_glBindFramebufferOES__II },
{"glBindRenderbufferOES", "(II)V", (void *) android_glBindRenderbufferOES__II },
diff --git a/core/res/Android.mk b/core/res/Android.mk
index 78cb86d..7d11148 100644
--- a/core/res/Android.mk
+++ b/core/res/Android.mk
@@ -24,7 +24,7 @@ LOCAL_CERTIFICATE := platform
# since these resources will be used by many apps.
LOCAL_AAPT_FLAGS := -x
-LOCAL_MODULE_TAGS := user
+LOCAL_MODULE_TAGS := optional
# Install this alongside the libraries.
LOCAL_MODULE_PATH := $(TARGET_OUT_JAVA_LIBRARIES)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3c35fc7..5a92dbe 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -52,7 +52,13 @@
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
<protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
<protected-broadcast android:name="android.intent.action.REBOOT" />
+ <protected-broadcast android:name="android.intent.action.DOCK_EVENT" />
+ <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
+ <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
+ <protected-broadcast android:name="android.app.action.ENTER_DESK_MODE" />
+ <protected-broadcast android:name="android.app.action.EXIT_DESK_MODE" />
+
<protected-broadcast android:name="android.backup.intent.RUN" />
<protected-broadcast android:name="android.backup.intent.CLEAR" />
<protected-broadcast android:name="android.backup.intent.INIT" />
@@ -521,7 +527,7 @@
<!-- Allows an application to modify the Google service map. -->
<permission android:name="android.permission.WRITE_GSERVICES"
- android:protectionLevel="signature"
+ android:protectionLevel="signatureOrSystem"
android:label="@string/permlab_writeGservices"
android:description="@string/permdesc_writeGservices" />
@@ -556,12 +562,30 @@
android:label="@string/permlab_changeConfiguration"
android:description="@string/permdesc_changeConfiguration" />
- <!-- Allows an application to restart other applications. -->
+ <!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
+ API is no longer supported. -->
<permission android:name="android.permission.RESTART_PACKAGES"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="dangerous"
- android:label="@string/permlab_restartPackages"
- android:description="@string/permdesc_restartPackages" />
+ android:protectionLevel="normal"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses" />
+
+ <!-- Allows an application to call
+ {@link android.app.ActivityManager#killBackgroundProcesses}. -->
+ <permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_killBackgroundProcesses"
+ android:description="@string/permdesc_killBackgroundProcesses" />
+
+ <!-- Allows an application to call
+ {@link android.app.ActivityManager#forceStopPackage}.
+ @hide -->
+ <permission android:name="android.permission.FORCE_STOP_PACKAGES"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_forceStopPackages"
+ android:description="@string/permdesc_forceStopPackages" />
<!-- Allows an application to retrieve state dump information from system
services. -->
@@ -608,7 +632,7 @@
for details. -->
<permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="dangerous"
+ android:protectionLevel="signature"
android:label="@string/permlab_setPreferredApplications"
android:description="@string/permdesc_setPreferredApplications" />
@@ -661,6 +685,12 @@
android:label="@string/permlab_setWallpaperHints"
android:description="@string/permdesc_setWallpaperHints" />
+ <!-- Allows applications to set the system time -->
+ <permission android:name="android.permission.SET_TIME"
+ android:protectionLevel="signatureOrSystem"
+ android:label="@string/permlab_setTime"
+ android:description="@string/permdesc_setTime" />
+
<!-- Allows applications to set the system time zone -->
<permission android:name="android.permission.SET_TIME_ZONE"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
@@ -682,6 +712,46 @@
android:label="@string/permlab_mount_format_filesystems"
android:description="@string/permdesc_mount_format_filesystems" />
+ <!-- Allows access to ASEC non-destructive API calls
+ @hide -->
+ <permission android:name="android.permission.ASEC_ACCESS"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_asec_access"
+ android:description="@string/permdesc_asec_access" />
+
+ <!-- Allows creation of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_CREATE"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_asec_create"
+ android:description="@string/permdesc_asec_create" />
+
+ <!-- Allows destruction of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_DESTROY"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_asec_destroy"
+ android:description="@string/permdesc_asec_destroy" />
+
+ <!-- Allows mount / unmount of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_MOUNT_UNMOUNT"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_asec_mount_unmount"
+ android:description="@string/permdesc_asec_mount_unmount" />
+
+ <!-- Allows rename of ASEC volumes
+ @hide -->
+ <permission android:name="android.permission.ASEC_RENAME"
+ android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_asec_rename"
+ android:description="@string/permdesc_asec_rename" />
+
<!-- Allows applications to disable the keyguard -->
<permission android:name="android.permission.DISABLE_KEYGUARD"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
@@ -909,21 +979,27 @@
android:description="@string/permdesc_readInputState"
android:protectionLevel="signature" />
- <!-- Must be required by input method services, to ensure that only the
- system can bind to them. -->
+ <!-- Must be required by an {@link android.inputmethodservice.InputMethodService},
+ to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_INPUT_METHOD"
android:label="@string/permlab_bindInputMethod"
android:description="@string/permdesc_bindInputMethod"
android:protectionLevel="signature" />
- <!-- Must be required by wallpaper services, to ensure that only the
- system can bind to them.
- @hide Live Wallpaper -->
+ <!-- Must be required by a {@link android.service.wallpaper.WallpaperService},
+ to ensure that only the system can bind to it. -->
<permission android:name="android.permission.BIND_WALLPAPER"
android:label="@string/permlab_bindWallpaper"
android:description="@string/permdesc_bindWallpaper"
android:protectionLevel="signatureOrSystem" />
+ <!-- Must be required by device administration receiver, to ensure that only the
+ system can interact with it. -->
+ <permission android:name="android.permission.BIND_DEVICE_ADMIN"
+ android:label="@string/permlab_bindDeviceAdmin"
+ android:description="@string/permdesc_bindDeviceAdmin"
+ android:protectionLevel="signature" />
+
<!-- Allows low-level access to setting the orientation (actually
rotation) of the screen. Not for use by normal applications. -->
<permission android:name="android.permission.SET_ORIENTATION"
@@ -947,7 +1023,7 @@
<permission android:name="android.permission.DELETE_CACHE_FILES"
android:label="@string/permlab_deleteCacheFiles"
android:description="@string/permdesc_deleteCacheFiles"
- android:protectionLevel="signature" />
+ android:protectionLevel="signatureOrSystem" />
<!-- Allows an application to delete packages. -->
<permission android:name="android.permission.DELETE_PACKAGES"
@@ -955,6 +1031,13 @@
android:description="@string/permdesc_deletePackages"
android:protectionLevel="signatureOrSystem" />
+ <!-- Allows an application to move location of installed package.
+ @hide -->
+ <permission android:name="android.permission.MOVE_PACKAGE"
+ android:label="@string/permlab_movePackage"
+ android:description="@string/permdesc_movePackage"
+ android:protectionLevel="signatureOrSystem" />
+
<!-- Allows an application to change whether an application component (other than its own) is
enabled or not. -->
<permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
@@ -985,7 +1068,7 @@
<permission android:name="android.permission.REBOOT"
android:label="@string/permlab_reboot"
android:description="@string/permdesc_reboot"
- android:protectionLevel="signature" />
+ android:protectionLevel="signatureOrSystem" />
<!-- Allows low-level access to power management -->
<permission android:name="android.permission.DEVICE_POWER"
@@ -1075,13 +1158,6 @@
android:description="@string/permdesc_backup"
android:protectionLevel="signatureOrSystem" />
- <!-- Allows an application to participate in the backup and restore process
- @hide -->
- <permission android:name="android.permission.BACKUP_DATA"
- android:label="@string/permlab_backup_data"
- android:description="@string/permdesc_backup_data"
- android:protectionLevel="signatureOrSystem" />
-
<!-- Allows an application to tell the AppWidget service which application
can access AppWidget's data. The normal user flow is that a user
picks an AppWidget to go into a particular host, thereby giving that
@@ -1132,6 +1208,30 @@
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="signatureOrSystem" />
+ <!-- Allow an application to read and write the cache partition.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM"
+ android:label="@string/permlab_cache_filesystem"
+ android:description="@string/permdesc_cache_filesystem"
+ android:protectionLevel="signatureOrSystem" />
+
+ <!-- Must be required by default container service so that only
+ the system can bind to it and use it to copy
+ protected data to secure containers or files
+ accessible to the system.
+ @hide -->
+ <permission android:name="android.permission.COPY_PROTECTED_DATA"
+ android:label="@string/permlab_copyProtectedData"
+ android:description="@string/permlab_copyProtectedData"
+ android:protectionLevel="signature" />
+
+ <!-- Data messaging permission.
+ @hide Used internally.
+ -->
+ <permission android:name="android.intent.category.MASTER_CLEAR.permission.DATA_MESSAGE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.intent.category.MASTER_CLEAR.permission.DATA_MESSAGE"/>
+
<application android:process="system"
android:persistent="true"
android:hasCode="false"
@@ -1150,6 +1250,10 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name="com.android.internal.app.DisableCarModeActivity"
+ android:theme="@style/Theme.NoDisplay"
+ android:excludeFromRecents="true">
+ </activity>
<activity android:name="com.android.internal.app.RingtonePickerActivity"
android:theme="@style/Theme.Dialog.Alert"
android:excludeFromRecents="true"
@@ -1159,12 +1263,7 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- <activity android:name="com.android.internal.app.UsbStorageActivity"
- android:theme="@style/Theme.Dialog.Alert"
- android:excludeFromRecents="true">
- </activity>
- <activity android:name="com.android.internal.app.UsbStorageStopActivity"
- android:theme="@style/Theme.Dialog.Alert"
+ <activity android:name="com.android.server.status.UsbStorageActivity"
android:excludeFromRecents="true">
</activity>
<activity android:name="com.android.internal.app.ExternalMediaFormatActivity"
@@ -1211,7 +1310,11 @@
<receiver android:name="com.android.server.MasterClearReceiver"
android:permission="android.permission.MASTER_CLEAR" >
<intent-filter>
- <action android:name="android.intent.action.REMOTE_INTENT" />
+ <!-- For Checkin, Settings, etc.: action=MASTER_CLEAR -->
+ <action android:name="android.intent.action.MASTER_CLEAR" />
+
+ <!-- MCS always uses REMOTE_INTENT: category=MASTER_CLEAR -->
+ <action android:name="com.google.android.datamessaging.intent.RECEIVE" />
<category android:name="android.intent.category.MASTER_CLEAR" />
</intent-filter>
</receiver>
diff --git a/core/res/assets/images/combobox-disabled.png b/core/res/assets/images/combobox-disabled.png
index 42fc0c5..fe220e4 100644
--- a/core/res/assets/images/combobox-disabled.png
+++ b/core/res/assets/images/combobox-disabled.png
Binary files differ
diff --git a/core/res/assets/images/combobox-noHighlight.png b/core/res/assets/images/combobox-noHighlight.png
index 838dc65..abcdf72 100644
--- a/core/res/assets/images/combobox-noHighlight.png
+++ b/core/res/assets/images/combobox-noHighlight.png
Binary files differ
diff --git a/core/res/assets/webkit/togglePlugin.png b/core/res/assets/webkit/togglePlugin.png
new file mode 100644
index 0000000..008333c
--- /dev/null
+++ b/core/res/assets/webkit/togglePlugin.png
Binary files differ
diff --git a/core/res/assets/webkit/youtube.html b/core/res/assets/webkit/youtube.html
index 2aaaa15..289f8cf 100644
--- a/core/res/assets/webkit/youtube.html
+++ b/core/res/assets/webkit/youtube.html
@@ -5,18 +5,6 @@
a:hover { text-decoration: none; }
a:link { color: black; }
a:visited { color: black; }
- #bg {
- position: fixed;
- margin: 0px;
- border: 0px;
- padding: 0px;
- left: 0px;
- top: 0px;
- width: 100%;
- height: 100%;
- overflow: hidden;
- z-index: 0;
- }
#main {
position: absolute;
left: 0%;
@@ -28,32 +16,81 @@
}
</style>
</head>
- <body>
- <div id="bg">
- <table height="100%" width="100%" border="0" cellpadding="0"
- cellspacing="0">
- <tr>
- <td valign="middle">
- <img src="http://img.youtube.com/vi/VIDEO_ID/0.jpg" width="100%"/>
- </td>
- </tr>
- </table>
- </div>
+ <body id="body">
+ <script type="text/javascript">
+ // Nominal original size. If the embed is smaller than this, the play and logo
+ // images get scaled appropriately. These are actually 3/4 of the sizes suggested
+ // by youtube, so the images don't get too tiny.
+ defHeight = 258;
+ defWidth = 318;
+
+ function setup() {
+ var width = document.body.clientWidth;
+ var height = document.body.clientHeight;
+ var canvas = document.getElementById("canvas");
+ // Resize the canvas to the right size
+ canvas.width = width;
+ canvas.height = height;
+ var ctx = canvas.getContext('2d');
+ var loadcount = 0;
+ function doload() {
+ if (++loadcount == 3) {
+ // All images are loaded, so display them.
+ // (Note that the images are loaded from javascript, so might load
+ // after document.onload fires)
+ ctx.drawImage(background, 0, 0, width, height);
+ playWidth = play.width;
+ playHeight = play.height;
+ logoWidth = logo.width;
+ logoHeight = logo.height;
+ var ratio = 1;
+ // If the page is smaller than it 'should' be in either dimension
+ // we scale the play button and logo according to the dimension that
+ // has been shrunk the most.
+ if (width / height > defWidth / defHeight && height < defHeight) {
+ ratio = height / defHeight;
+ } else if (width / height < defWidth / defHeight && width < defWidth) {
+ ratio = width / defWidth;
+ }
+ playWidth *= ratio;
+ playHeight *= ratio;
+ logoWidth *= ratio;
+ logoHeight *= ratio;
+ playLeft = (width - playWidth) / 2;
+ playTop = (height - playHeight) / 2;
+ ctx.drawImage(play, playLeft, playTop, playWidth, playHeight);
+ ctx.globalAlpha = 0.7
+ ctx.drawImage(logo, width - logoWidth, height - logoHeight, logoWidth, logoHeight);
+ // To make it slightly easier to hit, the click target is twice the width/height of the unscaled play button
+ targetLeft = width / 2 - play.width;
+ targetRight = width / 2 + play.width;
+ targetTop = height / 2 - play.height;
+ targetBottom = height / 2 + play.height;
+
+ canvas.addEventListener("click", function(e) {
+ var posx = e.clientX-canvas.offsetLeft;
+ var posy = e.clientY-canvas.offsetTop;
+ if (posx >= targetLeft && posx <= targetRight &&
+ posy >= targetTop && posy <= targetBottom) {
+ top.location.href = "vnd.youtube:VIDEO_ID";
+ }
+ }, false);
+ }
+ }
+ var background = new Image();
+ background.onload = doload;
+ background.src = "http://img.youtube.com/vi/VIDEO_ID/0.jpg";
+ play = new Image();
+ play.onload = doload;
+ play.src = "play.png";
+ logo = new Image();
+ logo.onload = doload;
+ logo.src = "youtube.png";
+ }
+ window.onload = setup;
+ </script>
<div id="main">
- <table height="100%" width="100%">
- <tr>
- <td align="center" valign="middle" height="100%">
- <a id="url" href="vnd.youtube:VIDEO_ID" target="_top">
- <img src="play.png" style="border:0px"/>
- </a>
- </td>
- </tr>
- <tr>
- <td valign="bottom" align="right">
- <img src="youtube.png" style="opacity:.7"/>
- </td>
- </tr>
- </table>
+ <canvas id="canvas"></canvas>
</div>
</body>
</html>
diff --git a/core/res/res/anim/cycle_interpolator.xml b/core/res/res/anim/cycle_interpolator.xml
new file mode 100644
index 0000000..70ebcb1
--- /dev/null
+++ b/core/res/res/anim/cycle_interpolator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<cycleInterpolator />
diff --git a/core/res/res/anim/lock_screen_enter.xml b/core/res/res/anim/lock_screen_enter.xml
new file mode 100644
index 0000000..dd47ff8
--- /dev/null
+++ b/core/res/res/anim/lock_screen_enter.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@anim/decelerate_interpolator">
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/core/res/res/anim/lock_screen_exit.xml b/core/res/res/anim/lock_screen_exit.xml
index 58bc6db..077fc6b 100644
--- a/core/res/res/anim/lock_screen_exit.xml
+++ b/core/res/res/anim/lock_screen_exit.xml
@@ -17,7 +17,8 @@
*/
-->
-<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/accelerate_interpolator">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@anim/accelerate_interpolator">
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
android:duration="@android:integer/config_longAnimTime" />
</set>
diff --git a/core/res/res/anim/wallpaper_close_enter.xml b/core/res/res/anim/wallpaper_close_enter.xml
index 2329abb..6eb2a89 100644
--- a/core/res/res/anim/wallpaper_close_enter.xml
+++ b/core/res/res/anim/wallpaper_close_enter.xml
@@ -20,14 +20,15 @@
<!-- This version zooms the new non-wallpaper up out of the
wallpaper, without zooming the wallpaper itself. -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@anim/decelerate_interpolator"
android:zAdjustment="top">
<scale android:fromXScale=".5" android:toXScale="1.0"
android:fromYScale=".5" android:toYScale="1.0"
android:pivotX="50%p" android:pivotY="50%p"
+ android:interpolator="@anim/decelerate_interpolator"
android:duration="@android:integer/config_mediumAnimTime" />
<alpha android:fromAlpha="0" android:toAlpha="1.0"
- android:duration="@android:integer/config_mediumAnimTime"/>
+ android:interpolator="@anim/accelerate_decelerate_interpolator"
+ android:duration="@android:integer/config_mediumAnimTime"/>
</set>
<!-- This version zooms the new non-wallpaper down on top of the
diff --git a/core/res/res/anim/wallpaper_open_enter.xml b/core/res/res/anim/wallpaper_open_enter.xml
index 3a6fb05..a32c39e 100644
--- a/core/res/res/anim/wallpaper_open_enter.xml
+++ b/core/res/res/anim/wallpaper_open_enter.xml
@@ -17,7 +17,7 @@
*/
-->
-<!-- This version zooms the new non-wallpaper up out of the
+<!-- This version zooms the exiting non-wallpaper down in to the
wallpaper, without zooming the wallpaper itself. -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@anim/decelerate_interpolator"
diff --git a/core/res/res/anim/wallpaper_open_exit.xml b/core/res/res/anim/wallpaper_open_exit.xml
index e5b7007..8f0e012 100644
--- a/core/res/res/anim/wallpaper_open_exit.xml
+++ b/core/res/res/anim/wallpaper_open_exit.xml
@@ -16,16 +16,17 @@
** limitations under the License.
*/
-->
-<!-- This version zooms the new non-wallpaper up out of the
+<!-- This version zooms the exiting non-wallpaper down in to the
wallpaper, without zooming the wallpaper itself. -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@anim/decelerate_interpolator"
android:zAdjustment="top">
<scale android:fromXScale="1.0" android:toXScale=".5"
android:fromYScale="1.0" android:toYScale=".5"
android:pivotX="50%p" android:pivotY="50%p"
+ android:interpolator="@anim/decelerate_interpolator"
android:duration="@android:integer/config_mediumAnimTime" />
<alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@anim/accelerate_decelerate_interpolator"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>
diff --git a/core/res/res/color/primary_text_dark.xml b/core/res/res/color/primary_text_dark.xml
index 2ec46b9..39c9e22 100644
--- a/core/res/res/color/primary_text_dark.xml
+++ b/core/res/res/color/primary_text_dark.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@android:color/bright_foreground_dark_disabled"/>
- <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
+ <item android:state_window_focused="false" android:color="@android:color/bright_foreground_dark"/>
<item android:state_pressed="true" android:color="@android:color/bright_foreground_dark_inverse"/>
<item android:state_selected="true" android:color="@android:color/bright_foreground_dark_inverse"/>
<item android:color="@android:color/bright_foreground_dark"/> <!-- not selected -->
diff --git a/core/res/res/color/primary_text_light.xml b/core/res/res/color/primary_text_light.xml
index bd9ac8b..e112034 100644
--- a/core/res/res/color/primary_text_light.xml
+++ b/core/res/res/color/primary_text_light.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@android:color/bright_foreground_light_disabled"/>
- <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
+ <item android:state_window_focused="false" android:color="@android:color/bright_foreground_light"/>
<item android:state_pressed="true" android:color="@android:color/bright_foreground_light"/>
<item android:state_selected="true" android:color="@android:color/bright_foreground_light"/>
<item android:color="@android:color/bright_foreground_light"/> <!-- not selected -->
diff --git a/core/res/res/color/secondary_text_dark.xml b/core/res/res/color/secondary_text_dark.xml
index 0d96221..c195ef0 100644
--- a/core/res/res/color/secondary_text_dark.xml
+++ b/core/res/res/color/secondary_text_dark.xml
@@ -15,7 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
+ <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_disabled"/>
<item android:state_window_focused="false" android:color="@android:color/dim_foreground_dark"/>
<item android:state_selected="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
<item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_dark_inverse_disabled"/>
diff --git a/core/res/res/color/secondary_text_light.xml b/core/res/res/color/secondary_text_light.xml
index 0846b5e..99249a5 100644
--- a/core/res/res/color/secondary_text_light.xml
+++ b/core/res/res/color/secondary_text_light.xml
@@ -15,7 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
+ <item android:state_window_focused="false" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
<item android:state_window_focused="false" android:color="@android:color/dim_foreground_light"/>
<!-- Since there is only one selector (for both light and dark), the light's selected state shouldn't be inversed like the dark's. -->
<item android:state_pressed="true" android:state_enabled="false" android:color="@android:color/dim_foreground_light_disabled"/>
diff --git a/core/res/res/color/secondary_text_nofocus.xml b/core/res/res/color/secondary_text_nofocus.xml
new file mode 100644
index 0000000..b6cbfb5
--- /dev/null
+++ b/core/res/res/color/secondary_text_nofocus.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#eeeeee"/> <!-- not selected -->
+</selector>
+
diff --git a/core/res/res/color/tertiary_text_dark.xml b/core/res/res/color/tertiary_text_dark.xml
index 7ce3580..66227bc 100644
--- a/core/res/res/color/tertiary_text_dark.xml
+++ b/core/res/res/color/tertiary_text_dark.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#808080"/>
- <item android:state_window_focused="false" android:color="#808080"/>
+ <item android:state_window_focused="false" android:color="#808080"/>
<item android:state_pressed="true" android:color="#808080"/>
<item android:state_selected="true" android:color="@android:color/dim_foreground_light"/>
<item android:color="#808080"/> <!-- not selected -->
diff --git a/core/res/res/color/tertiary_text_light.xml b/core/res/res/color/tertiary_text_light.xml
index 7e61fc8..ea65756 100644
--- a/core/res/res/color/tertiary_text_light.xml
+++ b/core/res/res/color/tertiary_text_light.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="#808080"/>
- <item android:state_window_focused="false" android:color="#808080"/>
+ <item android:state_window_focused="false" android:color="#808080"/>
<item android:state_pressed="true" android:color="#808080"/>
<item android:state_selected="true" android:color="#808080"/>
<item android:color="#808080"/> <!-- not selected -->
diff --git a/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png b/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png
new file mode 100755
index 0000000..569369e
--- /dev/null
+++ b/core/res/res/drawable-en-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
new file mode 100755
index 0000000..ca76375
--- /dev/null
+++ b/core/res/res/drawable-en-hdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png b/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..f1f7c58
--- /dev/null
+++ b/core/res/res/drawable-en-mdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png
new file mode 100644
index 0000000..3c90839
--- /dev/null
+++ b/core/res/res/drawable-en-mdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..c6503c7
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
new file mode 100644
index 0000000..152de8b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
new file mode 100644
index 0000000..b6c234c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png
new file mode 100644
index 0000000..9f3c087
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png
new file mode 100644
index 0000000..4041342
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
new file mode 100644
index 0000000..73a8cd1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
new file mode 100644
index 0000000..8473e8e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
new file mode 100644
index 0000000..f4f59c0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_normal.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_normal.9.png
index 5697369..42c7c14 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_normal.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
index 9940245..01e2506 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
index 5a26d83..83c6eb3 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
index 089dbf3..e047eaf 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
index c10a3db..218a2d2 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
index 9e83ace..afe4951 100644
--- a/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png
new file mode 100644
index 0000000..1508653
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png
new file mode 100644
index 0000000..66c231a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png
new file mode 100644
index 0000000..cdad182
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png
new file mode 100644
index 0000000..e95f4cf
--- /dev/null
+++ b/core/res/res/drawable-hdpi/btn_keyboard_key_trans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
index 7acd0df..72faccf 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
index a8b843a..369be10 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
index cfbc092..7e996ec 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
index 3d0d16e..eda6e16 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
index 2ccd3da..4158ac4 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
index 966ea44..6f68f25 100644
--- a/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-hdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_btn_search_go.png b/core/res/res/drawable-hdpi/ic_btn_search_go.png
new file mode 100644
index 0000000..8a3a402
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_btn_search_go.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_jog_dial_vibrate_on.png b/core/res/res/drawable-hdpi/ic_jog_dial_vibrate_on.png
new file mode 100644
index 0000000..86caa07
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_jog_dial_vibrate_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_idle_alarm.png b/core/res/res/drawable-hdpi/ic_lock_idle_alarm.png
index 41ad27d..6b4f66d 100644
--- a/core/res/res/drawable-hdpi/ic_lock_idle_alarm.png
+++ b/core/res/res/drawable-hdpi/ic_lock_idle_alarm.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_silent_mode.png b/core/res/res/drawable-hdpi/ic_lock_silent_mode.png
index 00e1960..0d4c590 100644
--- a/core/res/res/drawable-hdpi/ic_lock_silent_mode.png
+++ b/core/res/res/drawable-hdpi/ic_lock_silent_mode.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_silent_mode_off.png b/core/res/res/drawable-hdpi/ic_lock_silent_mode_off.png
index 6b4ce89..17d705c 100644
--- a/core/res/res/drawable-hdpi/ic_lock_silent_mode_off.png
+++ b/core/res/res/drawable-hdpi/ic_lock_silent_mode_off.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_lock_silent_mode_vibrate.png b/core/res/res/drawable-hdpi/ic_lock_silent_mode_vibrate.png
new file mode 100644
index 0000000..4503ace
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_lock_silent_mode_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_search_category_default.png b/core/res/res/drawable-hdpi/ic_search_category_default.png
index 4038129..f78234e 100644
--- a/core/res/res/drawable-hdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-hdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_vibrate.png b/core/res/res/drawable-hdpi/ic_vibrate.png
index aa83534..ca23372 100644
--- a/core/res/res/drawable-hdpi/ic_vibrate.png
+++ b/core/res/res/drawable-hdpi/ic_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_vibrate_small.png b/core/res/res/drawable-hdpi/ic_vibrate_small.png
new file mode 100644
index 0000000..61b8bd9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_vibrate_small.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_volume.png b/core/res/res/drawable-hdpi/ic_volume.png
index 7714f6a..bf538ee 100644
--- a/core/res/res/drawable-hdpi/ic_volume.png
+++ b/core/res/res/drawable-hdpi/ic_volume.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_volume_off.png b/core/res/res/drawable-hdpi/ic_volume_off.png
index 313dd5b..aa344083 100644
--- a/core/res/res/drawable-hdpi/ic_volume_off.png
+++ b/core/res/res/drawable-hdpi/ic_volume_off.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_volume_off_small.png b/core/res/res/drawable-hdpi/ic_volume_off_small.png
index 62322ec..1329414 100644
--- a/core/res/res/drawable-hdpi/ic_volume_off_small.png
+++ b/core/res/res/drawable-hdpi/ic_volume_off_small.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_volume_small.png b/core/res/res/drawable-hdpi/ic_volume_small.png
index 96c7948..4e9a7ea 100644
--- a/core/res/res/drawable-hdpi/ic_volume_small.png
+++ b/core/res/res/drawable-hdpi/ic_volume_small.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/keyboard_key_feedback_background.9.png b/core/res/res/drawable-hdpi/keyboard_key_feedback_background.9.png
index dc3e1f9..6ba42db 100644
--- a/core/res/res/drawable-hdpi/keyboard_key_feedback_background.9.png
+++ b/core/res/res/drawable-hdpi/keyboard_key_feedback_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png b/core/res/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
index c67ed53..4d0b601 100644
--- a/core/res/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
+++ b/core/res/res/drawable-hdpi/keyboard_key_feedback_more_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/password_field_default.9.png b/core/res/res/drawable-hdpi/password_field_default.9.png
new file mode 100644
index 0000000..2c424f0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/password_field_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pickerbox_background.png b/core/res/res/drawable-hdpi/pickerbox_background.png
deleted file mode 100644
index 9315a31..0000000
--- a/core/res/res/drawable-hdpi/pickerbox_background.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pickerbox_selected.9.png b/core/res/res/drawable-hdpi/pickerbox_selected.9.png
deleted file mode 100644
index a88ec63..0000000
--- a/core/res/res/drawable-hdpi/pickerbox_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/pickerbox_unselected.9.png b/core/res/res/drawable-hdpi/pickerbox_unselected.9.png
deleted file mode 100644
index 9f6b7cb..0000000
--- a/core/res/res/drawable-hdpi/pickerbox_unselected.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/progressbar_indeterminate1.png b/core/res/res/drawable-hdpi/progressbar_indeterminate1.png
index ea88e32..197b34d 100644..100755
--- a/core/res/res/drawable-hdpi/progressbar_indeterminate1.png
+++ b/core/res/res/drawable-hdpi/progressbar_indeterminate1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/progressbar_indeterminate2.png b/core/res/res/drawable-hdpi/progressbar_indeterminate2.png
index 436c48c..c6cf008 100644..100755
--- a/core/res/res/drawable-hdpi/progressbar_indeterminate2.png
+++ b/core/res/res/drawable-hdpi/progressbar_indeterminate2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/progressbar_indeterminate3.png b/core/res/res/drawable-hdpi/progressbar_indeterminate3.png
index ea88e32..bf129e0 100644..100755
--- a/core/res/res/drawable-hdpi/progressbar_indeterminate3.png
+++ b/core/res/res/drawable-hdpi/progressbar_indeterminate3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/recent_dialog_background.9.png b/core/res/res/drawable-hdpi/recent_dialog_background.9.png
new file mode 100644
index 0000000..bebcc40
--- /dev/null
+++ b/core/res/res/drawable-hdpi/recent_dialog_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png
deleted file mode 100644
index af80855..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png
deleted file mode 100644
index dc47275..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png
deleted file mode 100644
index 007f279..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png
deleted file mode 100644
index 24592a3..0000000
--- a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_notify_car_mode.png b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..03499a4
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_adb.png b/core/res/res/drawable-hdpi/stat_sys_adb.png
new file mode 100644
index 0000000..aef8650
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_adb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_secure.png b/core/res/res/drawable-hdpi/stat_sys_secure.png
new file mode 100644
index 0000000..4bae258
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000..e43fbae
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_bluetooth.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_general.png b/core/res/res/drawable-hdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000..c42b00c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_general.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_usb.png b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000..c6c533d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_usb.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png b/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png
new file mode 100644
index 0000000..9fcadef
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_tether_wifi.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/stat_sys_throttled.png b/core/res/res/drawable-hdpi/stat_sys_throttled.png
new file mode 100644
index 0000000..33c0521
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_throttled.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_app_on_sd_unavailable_icon.png b/core/res/res/drawable-hdpi/sym_app_on_sd_unavailable_icon.png
new file mode 100644
index 0000000..d915d41
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_app_on_sd_unavailable_icon.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_delete.png b/core/res/res/drawable-hdpi/sym_keyboard_delete.png
new file mode 100755
index 0000000..59d78be
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png b/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png
new file mode 100644
index 0000000..34b6d1f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_delete_dim.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png
new file mode 100755
index 0000000..ca76375
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png
new file mode 100644
index 0000000..2d144ec
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_ok.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png
new file mode 100755
index 0000000..ae57299
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png
new file mode 100755
index 0000000..4db31c8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
new file mode 100755
index 0000000..3fd5659
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png b/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png
new file mode 100755
index 0000000..98266ee
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png b/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png
new file mode 100644
index 0000000..2aad23c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num0_no_plus.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num1.png b/core/res/res/drawable-hdpi/sym_keyboard_num1.png
new file mode 100755
index 0000000..0fc03ef
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num2.png b/core/res/res/drawable-hdpi/sym_keyboard_num2.png
new file mode 100755
index 0000000..283560b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num3.png b/core/res/res/drawable-hdpi/sym_keyboard_num3.png
new file mode 100755
index 0000000..9a3b329
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num3.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num4.png b/core/res/res/drawable-hdpi/sym_keyboard_num4.png
new file mode 100755
index 0000000..f13ff1a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num4.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num5.png b/core/res/res/drawable-hdpi/sym_keyboard_num5.png
new file mode 100755
index 0000000..c251329
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num5.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num6.png b/core/res/res/drawable-hdpi/sym_keyboard_num6.png
new file mode 100755
index 0000000..4acba4c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num6.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num7.png b/core/res/res/drawable-hdpi/sym_keyboard_num7.png
new file mode 100755
index 0000000..14931c1
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num7.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num8.png b/core/res/res/drawable-hdpi/sym_keyboard_num8.png
new file mode 100755
index 0000000..d4973fd
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num8.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_num9.png b/core/res/res/drawable-hdpi/sym_keyboard_num9.png
new file mode 100755
index 0000000..49cec66
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_num9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_ok.png b/core/res/res/drawable-hdpi/sym_keyboard_ok.png
new file mode 100644
index 0000000..9105da2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_ok.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png b/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png
new file mode 100644
index 0000000..bd419de
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_ok_dim.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_return.png b/core/res/res/drawable-hdpi/sym_keyboard_return.png
new file mode 100755
index 0000000..58505c5
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_return.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_shift.png b/core/res/res/drawable-hdpi/sym_keyboard_shift.png
new file mode 100755
index 0000000..8149081
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_shift.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png b/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png
new file mode 100755
index 0000000..31ca277
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/sym_keyboard_space.png b/core/res/res/drawable-hdpi/sym_keyboard_space.png
new file mode 100755
index 0000000..3e98b30
--- /dev/null
+++ b/core/res/res/drawable-hdpi/sym_keyboard_space.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-hdpi/textfield_search_empty_default.9.png
index d8e268d..c0b84da 100644
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png
+++ b/core/res/res/drawable-hdpi/textfield_search_empty_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_empty_pressed.9.png b/core/res/res/drawable-hdpi/textfield_search_empty_pressed.9.png
new file mode 100644
index 0000000..0a0fc6b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_empty_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/textfield_search_empty_selected.9.png b/core/res/res/drawable-hdpi/textfield_search_empty_selected.9.png
new file mode 100644
index 0000000..04813c2
--- /dev/null
+++ b/core/res/res/drawable-hdpi/textfield_search_empty_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/usb_android.png b/core/res/res/drawable-hdpi/usb_android.png
new file mode 100644
index 0000000..8b5b1a9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/usb_android.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/usb_android_connected.png b/core/res/res/drawable-hdpi/usb_android_connected.png
new file mode 100644
index 0000000..442f2b3
--- /dev/null
+++ b/core/res/res/drawable-hdpi/usb_android_connected.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png
new file mode 100644
index 0000000..f7464c7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png
new file mode 100644
index 0000000..ffe219f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
new file mode 100644
index 0000000..20f3d50
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png
new file mode 100644
index 0000000..d09ce53
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png
new file mode 100644
index 0000000..a9e008c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
new file mode 100644
index 0000000..1ed3065
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
new file mode 100644
index 0000000..5710ebf
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
new file mode 100644
index 0000000..dd7d89e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_fulltrans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png
new file mode 100644
index 0000000..77426ef
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png
new file mode 100644
index 0000000..e4c9bd5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_normal_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png
new file mode 100644
index 0000000..bb98b01
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_off.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png
new file mode 100644
index 0000000..3c7dcc8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/btn_keyboard_key_trans_pressed_on.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
index febf222..42be225 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
index 70a200b..9984096 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
index 6f2989f..de2b030 100644
--- a/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
+++ b/core/res/res/drawable-mdpi/btn_search_dialog_voice_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_btn_search_go.png b/core/res/res/drawable-mdpi/ic_btn_search_go.png
new file mode 100644
index 0000000..0ed9e8e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_btn_search_go.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_jog_dial_vibrate_on.png b/core/res/res/drawable-mdpi/ic_jog_dial_vibrate_on.png
new file mode 100644
index 0000000..9aa9b13
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_jog_dial_vibrate_on.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_launcher_android.png b/core/res/res/drawable-mdpi/ic_launcher_android.png
index 855484a..6a97d5b 100644
--- a/core/res/res/drawable-mdpi/ic_launcher_android.png
+++ b/core/res/res/drawable-mdpi/ic_launcher_android.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_idle_alarm.png b/core/res/res/drawable-mdpi/ic_lock_idle_alarm.png
index ee77526..d29c6c3 100644
--- a/core/res/res/drawable-mdpi/ic_lock_idle_alarm.png
+++ b/core/res/res/drawable-mdpi/ic_lock_idle_alarm.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_silent_mode.png b/core/res/res/drawable-mdpi/ic_lock_silent_mode.png
index 439a6f5..5c3a226 100644
--- a/core/res/res/drawable-mdpi/ic_lock_silent_mode.png
+++ b/core/res/res/drawable-mdpi/ic_lock_silent_mode.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_silent_mode_off.png b/core/res/res/drawable-mdpi/ic_lock_silent_mode_off.png
index fc7e960..95257a3 100644
--- a/core/res/res/drawable-mdpi/ic_lock_silent_mode_off.png
+++ b/core/res/res/drawable-mdpi/ic_lock_silent_mode_off.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_silent_mode_vibrate.png b/core/res/res/drawable-mdpi/ic_lock_silent_mode_vibrate.png
new file mode 100644
index 0000000..7da79aa
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_lock_silent_mode_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_search_category_default.png b/core/res/res/drawable-mdpi/ic_search_category_default.png
index 7eea584..94446db 100755
--- a/core/res/res/drawable-mdpi/ic_search_category_default.png
+++ b/core/res/res/drawable-mdpi/ic_search_category_default.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_vibrate.png b/core/res/res/drawable-mdpi/ic_vibrate.png
index eb24e50..4fecce1 100755..100644
--- a/core/res/res/drawable-mdpi/ic_vibrate.png
+++ b/core/res/res/drawable-mdpi/ic_vibrate.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_vibrate_small.png b/core/res/res/drawable-mdpi/ic_vibrate_small.png
new file mode 100644
index 0000000..f04804e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_vibrate_small.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_volume.png b/core/res/res/drawable-mdpi/ic_volume.png
index cee70f0..20aa030 100755..100644
--- a/core/res/res/drawable-mdpi/ic_volume.png
+++ b/core/res/res/drawable-mdpi/ic_volume.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_volume_off.png b/core/res/res/drawable-mdpi/ic_volume_off.png
index f3850fc..fefb9c4 100644
--- a/core/res/res/drawable-mdpi/ic_volume_off.png
+++ b/core/res/res/drawable-mdpi/ic_volume_off.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_volume_off_small.png b/core/res/res/drawable-mdpi/ic_volume_off_small.png
index ae55bd6..529298c 100755..100644
--- a/core/res/res/drawable-mdpi/ic_volume_off_small.png
+++ b/core/res/res/drawable-mdpi/ic_volume_off_small.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_volume_small.png b/core/res/res/drawable-mdpi/ic_volume_small.png
index 00a4f89..2a7ec03 100755..100644
--- a/core/res/res/drawable-mdpi/ic_volume_small.png
+++ b/core/res/res/drawable-mdpi/ic_volume_small.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/password_field_default.9.png b/core/res/res/drawable-mdpi/password_field_default.9.png
new file mode 100644
index 0000000..3193275
--- /dev/null
+++ b/core/res/res/drawable-mdpi/password_field_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pickerbox_background.png b/core/res/res/drawable-mdpi/pickerbox_background.png
deleted file mode 100644
index 6494cd8..0000000
--- a/core/res/res/drawable-mdpi/pickerbox_background.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pickerbox_selected.9.png b/core/res/res/drawable-mdpi/pickerbox_selected.9.png
deleted file mode 100644
index d986a31..0000000
--- a/core/res/res/drawable-mdpi/pickerbox_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pickerbox_unselected.9.png b/core/res/res/drawable-mdpi/pickerbox_unselected.9.png
deleted file mode 100644
index 27ec6b9..0000000
--- a/core/res/res/drawable-mdpi/pickerbox_unselected.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/progressbar_indeterminate1.png b/core/res/res/drawable-mdpi/progressbar_indeterminate1.png
index 5eddb30..71780ef 100644
--- a/core/res/res/drawable-mdpi/progressbar_indeterminate1.png
+++ b/core/res/res/drawable-mdpi/progressbar_indeterminate1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/progressbar_indeterminate2.png b/core/res/res/drawable-mdpi/progressbar_indeterminate2.png
index 4ca3a63..236988b 100644
--- a/core/res/res/drawable-mdpi/progressbar_indeterminate2.png
+++ b/core/res/res/drawable-mdpi/progressbar_indeterminate2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/progressbar_indeterminate3.png b/core/res/res/drawable-mdpi/progressbar_indeterminate3.png
index da8e601..1570235 100644
--- a/core/res/res/drawable-mdpi/progressbar_indeterminate3.png
+++ b/core/res/res/drawable-mdpi/progressbar_indeterminate3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/recent_dialog_background.9.png b/core/res/res/drawable-mdpi/recent_dialog_background.9.png
new file mode 100644
index 0000000..18ed3ff
--- /dev/null
+++ b/core/res/res/drawable-mdpi/recent_dialog_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_notify_car_mode.png b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
new file mode 100644
index 0000000..0272e6b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_notify_car_mode.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_notify_chat.png b/core/res/res/drawable-mdpi/stat_notify_chat.png
index a3f2f96..068b7ec 100644
--- a/core/res/res/drawable-mdpi/stat_notify_chat.png
+++ b/core/res/res/drawable-mdpi/stat_notify_chat.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_adb.png b/core/res/res/drawable-mdpi/stat_sys_adb.png
new file mode 100644
index 0000000..12abeda
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_adb.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_secure.png b/core/res/res/drawable-mdpi/stat_sys_secure.png
new file mode 100644
index 0000000..5f9ae69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_bluetooth.png b/core/res/res/drawable-mdpi/stat_sys_tether_bluetooth.png
new file mode 100644
index 0000000..efb64ad
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_bluetooth.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_general.png b/core/res/res/drawable-mdpi/stat_sys_tether_general.png
new file mode 100644
index 0000000..3688803
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_general.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_usb.png b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png
new file mode 100644
index 0000000..73f1a81
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_usb.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_tether_wifi.png b/core/res/res/drawable-mdpi/stat_sys_tether_wifi.png
new file mode 100644
index 0000000..d448491
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_tether_wifi.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_throttled.png b/core/res/res/drawable-mdpi/stat_sys_throttled.png
new file mode 100644
index 0000000..97ac427
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_throttled.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_app_on_sd_unavailable_icon.png b/core/res/res/drawable-mdpi/sym_app_on_sd_unavailable_icon.png
new file mode 100644
index 0000000..4730668
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_app_on_sd_unavailable_icon.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_def_app_icon.png b/core/res/res/drawable-mdpi/sym_def_app_icon.png
index 8be3b54..9777d11 100644
--- a/core/res/res/drawable-mdpi/sym_def_app_icon.png
+++ b/core/res/res/drawable-mdpi/sym_def_app_icon.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_delete.png b/core/res/res/drawable-mdpi/sym_keyboard_delete.png
new file mode 100644
index 0000000..43a033e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_delete.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png b/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png
new file mode 100644
index 0000000..25460d8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_delete_dim.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png
new file mode 100644
index 0000000..1edb10b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_delete.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png
new file mode 100644
index 0000000..3148836
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_ok.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png
new file mode 100644
index 0000000..03d9c9b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_return.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png
new file mode 100644
index 0000000..97f4661
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png
new file mode 100755
index 0000000..7194b30
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png b/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png
new file mode 100644
index 0000000..739db68
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_feedback_space.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png b/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png
new file mode 100644
index 0000000..91332b1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num0_no_plus.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num1.png b/core/res/res/drawable-mdpi/sym_keyboard_num1.png
new file mode 100644
index 0000000..aaac11b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num2.png b/core/res/res/drawable-mdpi/sym_keyboard_num2.png
new file mode 100644
index 0000000..4372eb8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num3.png b/core/res/res/drawable-mdpi/sym_keyboard_num3.png
new file mode 100644
index 0000000..6f54c85
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num3.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num4.png b/core/res/res/drawable-mdpi/sym_keyboard_num4.png
new file mode 100644
index 0000000..3e50bb9
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num4.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num5.png b/core/res/res/drawable-mdpi/sym_keyboard_num5.png
new file mode 100644
index 0000000..c39ef44
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num5.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num6.png b/core/res/res/drawable-mdpi/sym_keyboard_num6.png
new file mode 100644
index 0000000..ea88ceb
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num6.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num7.png b/core/res/res/drawable-mdpi/sym_keyboard_num7.png
new file mode 100644
index 0000000..ce800ba
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num7.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num8.png b/core/res/res/drawable-mdpi/sym_keyboard_num8.png
new file mode 100644
index 0000000..1a8ff94
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num8.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_num9.png b/core/res/res/drawable-mdpi/sym_keyboard_num9.png
new file mode 100644
index 0000000..8b344c0
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_num9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_ok.png b/core/res/res/drawable-mdpi/sym_keyboard_ok.png
new file mode 100644
index 0000000..b8b5292
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_ok.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png b/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png
new file mode 100644
index 0000000..33ecff5
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_ok_dim.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_return.png b/core/res/res/drawable-mdpi/sym_keyboard_return.png
new file mode 100644
index 0000000..17f2574
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_return.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_shift.png b/core/res/res/drawable-mdpi/sym_keyboard_shift.png
new file mode 100644
index 0000000..0566e5a
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_shift.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png b/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png
new file mode 100755
index 0000000..ccaf05d
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_shift_locked.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/sym_keyboard_space.png b/core/res/res/drawable-mdpi/sym_keyboard_space.png
new file mode 100644
index 0000000..4e6273b
--- /dev/null
+++ b/core/res/res/drawable-mdpi/sym_keyboard_space.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_empty_default.9.png b/core/res/res/drawable-mdpi/textfield_search_empty_default.9.png
new file mode 100644
index 0000000..515117f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_empty_default.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_empty_pressed.9.png b/core/res/res/drawable-mdpi/textfield_search_empty_pressed.9.png
new file mode 100644
index 0000000..a01f763
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_empty_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/textfield_search_empty_selected.9.png b/core/res/res/drawable-mdpi/textfield_search_empty_selected.9.png
new file mode 100644
index 0000000..611276f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/textfield_search_empty_selected.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/usb_android.png b/core/res/res/drawable-mdpi/usb_android.png
new file mode 100644
index 0000000..492b6e1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/usb_android.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/usb_android_connected.png b/core/res/res/drawable-mdpi/usb_android_connected.png
new file mode 100644
index 0000000..3dd2950
--- /dev/null
+++ b/core/res/res/drawable-mdpi/usb_android_connected.png
Binary files differ
diff --git a/core/res/res/drawable/btn_circle.xml b/core/res/res/drawable/btn_circle.xml
index 9208010..243f506 100644
--- a/core/res/res/drawable/btn_circle.xml
+++ b/core/res/res/drawable/btn_circle.xml
@@ -19,6 +19,8 @@
android:drawable="@drawable/btn_circle_normal" />
<item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_circle_disable" />
+ <item android:state_pressed="true" android:state_enabled="false"
+ android:drawable="@drawable/btn_circle_disable" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_circle_pressed" />
<item android:state_focused="true" android:state_enabled="true"
diff --git a/core/res/res/drawable/btn_default.xml b/core/res/res/drawable/btn_default.xml
index b8ce2bf..4765084 100644
--- a/core/res/res/drawable/btn_default.xml
+++ b/core/res/res/drawable/btn_default.xml
@@ -15,9 +15,9 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_default_normal" />
- <item android:state_window_focused="false" android:state_enabled="false"
+ <item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_default_normal_disable" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_default_pressed" />
diff --git a/core/res/res/drawable/btn_default_small.xml b/core/res/res/drawable/btn_default_small.xml
index 247e9e2..5485ea0 100644
--- a/core/res/res/drawable/btn_default_small.xml
+++ b/core/res/res/drawable/btn_default_small.xml
@@ -15,9 +15,9 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_default_small_normal" />
- <item android:state_window_focused="false" android:state_enabled="false"
+ <item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_default_small_normal_disable" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_default_small_pressed" />
diff --git a/core/res/res/drawable/btn_dialog.xml b/core/res/res/drawable/btn_dialog.xml
index ed4c28a..d1d7e29 100644
--- a/core/res/res/drawable/btn_dialog.xml
+++ b/core/res/res/drawable/btn_dialog.xml
@@ -15,9 +15,9 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_dialog_normal" />
- <item android:state_window_focused="false" android:state_enabled="false"
+ <item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_dialog_disable" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_dialog_pressed" />
diff --git a/core/res/res/drawable/btn_dropdown.xml b/core/res/res/drawable/btn_dropdown.xml
index 8ec8ece..d9905c6 100644
--- a/core/res/res/drawable/btn_dropdown.xml
+++ b/core/res/res/drawable/btn_dropdown.xml
@@ -15,10 +15,24 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:drawable="@drawable/btn_dropdown_normal" />
- <item android:state_pressed="true" android:drawable="@drawable/btn_dropdown_pressed" />
- <item android:state_focused="true" android:state_pressed="false"
+ <item
+ android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/btn_dropdown_disabled" />
+ <item
+ android:state_pressed="true"
+ android:drawable="@drawable/btn_dropdown_pressed" />
+ <item
+ android:state_focused="true" android:state_enabled="true"
android:drawable="@drawable/btn_dropdown_selected" />
- <item android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_enabled="true"
+ android:drawable="@drawable/btn_dropdown_normal" />
+ <item
+ android:state_focused="true"
+ android:drawable="@drawable/btn_dropdown_disabled_focused" />
+ <item
+ android:drawable="@drawable/btn_dropdown_disabled" />
</selector>
-
diff --git a/core/res/res/drawable/btn_global_search.xml b/core/res/res/drawable/btn_global_search.xml
index 531f07e..2807a01 100644
--- a/core/res/res/drawable/btn_global_search.xml
+++ b/core/res/res/drawable/btn_global_search.xml
@@ -16,9 +16,9 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO Need different assets for some of these button states. -->
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_global_search_normal" />
- <item android:state_window_focused="false" android:state_enabled="false"
+ <item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/btn_global_search_normal" />
<item android:state_pressed="true"
android:drawable="@drawable/btn_default_pressed" />
diff --git a/core/res/res/drawable/btn_keyboard_key_fulltrans.xml b/core/res/res/drawable/btn_keyboard_key_fulltrans.xml
new file mode 100644
index 0000000..cfad6e3
--- /dev/null
+++ b/core/res/res/drawable/btn_keyboard_key_fulltrans.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Toggle keys. Use checkable/checked state. -->
+
+ <item android:state_checkable="true" android:state_checked="true"
+ android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed_on" />
+ <item android:state_checkable="true" android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed_off" />
+ <item android:state_checkable="true" android:state_checked="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_normal_on" />
+ <item android:state_checkable="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_normal_off" />
+
+ <!-- Normal keys -->
+ <item android:state_pressed="true"
+ android:drawable="@drawable/btn_keyboard_key_fulltrans_pressed" />
+ <item android:drawable="@drawable/btn_keyboard_key_fulltrans_normal" />
+
+</selector>
diff --git a/core/res/res/drawable/btn_search_dialog.xml b/core/res/res/drawable/btn_search_dialog.xml
index b7f5187..082633e 100644
--- a/core/res/res/drawable/btn_search_dialog.xml
+++ b/core/res/res/drawable/btn_search_dialog.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_search_dialog_default" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/btn_search_dialog_voice.xml b/core/res/res/drawable/btn_search_dialog_voice.xml
index 748aaf5..1a76efa 100644
--- a/core/res/res/drawable/btn_search_dialog_voice.xml
+++ b/core/res/res/drawable/btn_search_dialog_voice.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/btn_search_dialog_voice_default" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/edit_text.xml b/core/res/res/drawable/edit_text.xml
index 23a97e9..315278d 100644
--- a/core/res/res/drawable/edit_text.xml
+++ b/core/res/res/drawable/edit_text.xml
@@ -15,9 +15,9 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/textfield_default" />
- <item android:state_window_focused="false" android:state_enabled="false"
+ <item android:state_window_focused="false" android:state_enabled="false"
android:drawable="@drawable/textfield_disabled" />
<item android:state_pressed="true" android:drawable="@drawable/textfield_pressed" />
<item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/textfield_selected" />
diff --git a/core/res/res/drawable/textfield_search.xml b/core/res/res/drawable/textfield_search.xml
index 2923368..78c9486 100644
--- a/core/res/res/drawable/textfield_search.xml
+++ b/core/res/res/drawable/textfield_search.xml
@@ -16,7 +16,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_window_focused="false" android:state_enabled="true"
+ <item android:state_window_focused="false" android:state_enabled="true"
android:drawable="@drawable/textfield_search_default" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/textfield_search_empty.xml b/core/res/res/drawable/textfield_search_empty.xml
new file mode 100644
index 0000000..55eec83
--- /dev/null
+++ b/core/res/res/drawable/textfield_search_empty.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/textfield_search_empty_default" />
+
+ <item android:state_pressed="true"
+ android:drawable="@drawable/textfield_search_empty_pressed" />
+
+ <item android:state_enabled="true" android:state_focused="true"
+ android:drawable="@drawable/textfield_search_empty_selected" />
+
+ <item android:state_enabled="true"
+ android:drawable="@drawable/textfield_search_empty_default" />
+
+</selector>
diff --git a/core/res/res/layout-ja/contact_header_name.xml b/core/res/res/layout-ja/contact_header_name.xml
deleted file mode 100644
index 7c0f63e..0000000
--- a/core/res/res/layout-ja/contact_header_name.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<!-- In Japanese-language locales, the "Name" field contains two separate
- TextViews: the name itself, and also the phonetic ("furigana") field. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="0dip"
- android:layout_weight="1"
- android:layout_height="fill_parent">
-
- <TextView android:id="@+id/name"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
-
- <TextView android:id="@+id/phonetic_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
-
-</LinearLayout>
diff --git a/core/res/res/layout-land/icon_menu_layout.xml b/core/res/res/layout-land/icon_menu_layout.xml
index d1b25d9..70e3e83 100644
--- a/core/res/res/layout-land/icon_menu_layout.xml
+++ b/core/res/res/layout-land/icon_menu_layout.xml
@@ -16,9 +16,9 @@
<com.android.internal.view.menu.IconMenuView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+android:id/icon_menu"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:rowHeight="65dip"
+ android:rowHeight="66dip"
android:maxItems="6"
android:maxRows="2"
android:maxItemsPerRow="6" />
diff --git a/core/res/res/layout-land/usb_storage_activity.xml b/core/res/res/layout-land/usb_storage_activity.xml
new file mode 100644
index 0000000..50ca569
--- /dev/null
+++ b/core/res/res/layout-land/usb_storage_activity.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:padding="18dip"
+ android:orientation="horizontal"
+ >
+
+ <ImageView android:id="@+id/icon"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:src="@drawable/usb_android" />
+
+ <RelativeLayout
+ android:layout_height="wrap_content"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ >
+
+ <TextView android:id="@+id/banner"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ android:gravity="center"
+ android:text="@string/usb_storage_title" />
+
+ <TextView android:id="@+id/message"
+ android:layout_below="@id/banner"
+ android:layout_marginTop="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:gravity="center"
+ android:text="@string/usb_storage_message" />
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="20dip"
+ >
+
+ <Button android:id="@+id/mount_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="18dip"
+ android:paddingRight="18dip"
+ android:text="@string/usb_storage_button_mount"
+ />
+ <Button android:id="@+id/unmount_button"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="18dip"
+ android:paddingRight="18dip"
+ android:text="@string/usb_storage_stop_button_mount"
+ />
+ <ProgressBar android:id="@+id/progress"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyle"
+ />
+
+ </RelativeLayout>
+ </RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/layout-port/icon_menu_layout.xml b/core/res/res/layout-port/icon_menu_layout.xml
index 08edfcc..82082da 100644
--- a/core/res/res/layout-port/icon_menu_layout.xml
+++ b/core/res/res/layout-port/icon_menu_layout.xml
@@ -16,9 +16,9 @@
<com.android.internal.view.menu.IconMenuView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+android:id/icon_menu"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:rowHeight="65dip"
+ android:rowHeight="66dip"
android:maxItems="6"
android:maxRows="3"
android:maxItemsPerRow="3" />
diff --git a/core/res/res/layout/activity_list.xml b/core/res/res/layout/activity_list.xml
index 2967f0f..f489ab7 100644
--- a/core/res/res/layout/activity_list.xml
+++ b/core/res/res/layout/activity_list.xml
@@ -15,20 +15,20 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<ListView
android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
/>
<TextView
android:id="@android:id/empty"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:gravity="center"
android:text="@string/activity_list_empty"
android:visibility="gone"
diff --git a/core/res/res/layout/activity_list_item.xml b/core/res/res/layout/activity_list_item.xml
index 7a2a0e2..25d95fd 100644
--- a/core/res/res/layout/activity_list_item.xml
+++ b/core/res/res/layout/activity_list_item.xml
@@ -18,7 +18,7 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="1dip"
android:paddingBottom="1dip"
diff --git a/core/res/res/layout/activity_list_item_2.xml b/core/res/res/layout/activity_list_item_2.xml
index 78eca02..e937d7b 100644
--- a/core/res/res/layout/activity_list_item_2.xml
+++ b/core/res/res/layout/activity_list_item_2.xml
@@ -15,7 +15,7 @@
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index cf2de05..7ae68f9 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -18,24 +18,26 @@
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.WeightedLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="9dip"
android:paddingBottom="3dip"
android:paddingLeft="3dip"
android:paddingRight="1dip"
- >
+ android:majorWeight="0.65"
+ android:minorWeight="0.9">
<LinearLayout android:id="@+id/topPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
android:orientation="vertical">
<LinearLayout android:id="@+id/title_template"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
@@ -50,32 +52,30 @@
android:paddingTop="6dip"
android:paddingRight="10dip"
android:src="@drawable/ic_dialog_info" />
- <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
+ <com.android.internal.widget.DialogTitle android:id="@+id/alertTitle"
style="?android:attr/textAppearanceLarge"
android:singleLine="true"
android:ellipsize="end"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<ImageView android:id="@+id/titleDivider"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:visibility="gone"
android:scaleType="fitXY"
android:gravity="fill_horizontal"
- android:src="@android:drawable/dialog_divider_horizontal_light"
- android:layout_marginLeft="10dip"
- android:layout_marginRight="10dip"/>
+ android:src="@android:drawable/divider_horizontal_dark" />
<!-- If the client uses a customTitle, it will be added here. -->
</LinearLayout>
<LinearLayout android:id="@+id/contentPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<ScrollView android:id="@+id/scrollView"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingBottom="12dip"
@@ -83,35 +83,36 @@
android:paddingRight="10dip">
<TextView android:id="@+id/message"
style="?android:attr/textAppearanceMedium"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dip" />
</ScrollView>
</LinearLayout>
-
+
<FrameLayout android:id="@+id/customPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<FrameLayout android:id="@+android:id/custom"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:paddingBottom="5dip" />
</FrameLayout>
-
+
<LinearLayout android:id="@+id/buttonPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="54dip"
- android:orientation="vertical" >
- <LinearLayout
- android:layout_width="fill_parent"
+ android:orientation="vertical" >
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="4dip"
android:paddingLeft="2dip"
- android:paddingRight="2dip" >
+ android:paddingRight="2dip"
+ android:useLargestChild="true">
<LinearLayout android:id="@+id/leftSpacer"
android:layout_weight="0.25"
android:layout_width="0dip"
@@ -144,4 +145,4 @@
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
-</LinearLayout>
+</com.android.internal.widget.WeightedLinearLayout>
diff --git a/core/res/res/layout/alert_dialog_progress.xml b/core/res/res/layout/alert_dialog_progress.xml
index d2bb691..c519692 100644
--- a/core/res/res/layout/alert_dialog_progress.xml
+++ b/core/res/res/layout/alert_dialog_progress.xml
@@ -15,10 +15,10 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content" android:layout_height="fill_parent">
+ android:layout_width="wrap_content" android:layout_height="match_parent">
<ProgressBar android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dip"
android:layout_marginBottom="1dip"
diff --git a/core/res/res/layout/always_use_checkbox.xml b/core/res/res/layout/always_use_checkbox.xml
index 1f8d256..baa4bee 100644
--- a/core/res/res/layout/always_use_checkbox.xml
+++ b/core/res/res/layout/always_use_checkbox.xml
@@ -18,7 +18,7 @@
to make their selection the preferred activity. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="14dip"
android:paddingRight="15dip"
diff --git a/core/res/res/layout/app_permission_item.xml b/core/res/res/layout/app_permission_item.xml
index 8db4dd7..1bd267f 100644
--- a/core/res/res/layout/app_permission_item.xml
+++ b/core/res/res/layout/app_permission_item.xml
@@ -21,7 +21,7 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
diff --git a/core/res/res/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml
index 009eb8f..7160743 100755
--- a/core/res/res/layout/app_perms_summary.xml
+++ b/core/res/res/layout/app_perms_summary.xml
@@ -18,8 +18,8 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical">
<TextView
@@ -36,7 +36,7 @@
<LinearLayout
android:id="@+id/dangerous_perms_list"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:paddingLeft="16dip"
android:paddingRight="12dip"
android:layout_height="wrap_content" />
@@ -45,20 +45,20 @@
<LinearLayout
android:id="@+id/show_more"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginTop="12dip"
android:layout_marginBottom="16dip">
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<LinearLayout
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dip"
android:layout_marginBottom="12dip"
@@ -77,13 +77,13 @@
android:layout_alignTop="@id/show_more_icon"
android:layout_gravity="center_vertical"
android:paddingLeft="6dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
@@ -95,7 +95,7 @@
android:orientation="vertical"
android:paddingLeft="16dip"
android:paddingRight="12dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
diff --git a/core/res/res/layout/auto_complete_list.xml b/core/res/res/layout/auto_complete_list.xml
index addda11..8ad5dbb 100644
--- a/core/res/res/layout/auto_complete_list.xml
+++ b/core/res/res/layout/auto_complete_list.xml
@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/edit_text"
android:divider="@android:drawable/divider_horizontal_textfield"
@@ -25,14 +25,14 @@
<LinearLayout android:id="@+id/container"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="0dip"
/>
<AutoCompleteTextView android:id="@+id/edit"
android:completionThreshold="1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_gravity="center_vertical"
diff --git a/core/res/res/layout/battery_low.xml b/core/res/res/layout/battery_low.xml
index 116eae7..3b62fb0 100644
--- a/core/res/res/layout/battery_low.xml
+++ b/core/res/res/layout/battery_low.xml
@@ -25,7 +25,7 @@
>
<TextView android:id="@+id/subtitle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18dp"
android:paddingLeft="19dp"
@@ -35,7 +35,7 @@
/>
<TextView android:id="@+id/level_percent"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18dp"
android:textColor="#ffffffff"
diff --git a/core/res/res/layout/battery_status.xml b/core/res/res/layout/battery_status.xml
index 8b9828c..7cfec05 100644
--- a/core/res/res/layout/battery_status.xml
+++ b/core/res/res/layout/battery_status.xml
@@ -32,13 +32,13 @@
<LinearLayout
android:id="@+id/meter"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
>
<ImageView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="15dip"
/>
<ImageView
@@ -55,8 +55,8 @@
</LinearLayout>
<TextView android:id="@+id/level_percent"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:textStyle="bold"
android:textSize="48dp"
android:textColor="#ffffffff"
diff --git a/core/res/res/layout/character_picker.xml b/core/res/res/layout/character_picker.xml
index 70867d0..2508f72 100644
--- a/core/res/res/layout/character_picker.xml
+++ b/core/res/res/layout/character_picker.xml
@@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="304dp"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:background="@drawable/keyboard_popup_panel_trans_background">
<GridView
diff --git a/core/res/res/layout/contact_header.xml b/core/res/res/layout/contact_header.xml
index d551a26..bf467d3 100644
--- a/core/res/res/layout/contact_header.xml
+++ b/core/res/res/layout/contact_header.xml
@@ -16,8 +16,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/banner"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@drawable/title_bar_medium"
android:paddingRight="5dip">
@@ -37,7 +37,7 @@
android:layout_gravity="center_vertical" >
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
@@ -48,29 +48,49 @@
android:paddingRight="3dip"
android:paddingTop="3dip"
android:src="@drawable/ic_aggregated"
+ android:visibility="gone"
/>
- <!-- "Name" field is locale-specific. -->
- <include layout="@layout/contact_header_name"/>
-
+ <TextView android:id="@+id/name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:shadowColor="#BB000000"
+ android:shadowRadius="2.75"
+ />
</LinearLayout>
+ <TextView android:id="@+id/phonetic_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:layout_marginTop="-2dip"
+ android:visibility="gone"
+ />
+
<TextView android:id="@+id/status"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:ellipsize="end"
- android:layout_marginTop="-4dip"
+ android:layout_marginTop="-2dip"
+ android:visibility="gone"
/>
<TextView android:id="@+id/status_date"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="12sp"
android:layout_marginTop="-2dip"
+ android:visibility="gone"
/>
</LinearLayout>
@@ -80,7 +100,9 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="3dip"
- android:paddingRight="6dip"/>
+ android:paddingRight="6dip"
+ android:visibility="gone"
+ />
<CheckBox
android:id="@+id/star"
@@ -88,6 +110,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:visibility="gone"
+ android:contentDescription="@string/description_star"
style="?android:attr/starStyle" />
</LinearLayout>
diff --git a/core/res/res/layout/date_picker.xml b/core/res/res/layout/date_picker.xml
index 0760cc0..4fd46b3 100644
--- a/core/res/res/layout/date_picker.xml
+++ b/core/res/res/layout/date_picker.xml
@@ -29,7 +29,7 @@
android:layout_height="wrap_content">
<!-- Month -->
- <com.android.internal.widget.NumberPicker
+ <NumberPicker
android:id="@+id/month"
android:layout_width="80dip"
android:layout_height="wrap_content"
@@ -40,7 +40,7 @@
/>
<!-- Day -->
- <com.android.internal.widget.NumberPicker
+ <NumberPicker
android:id="@+id/day"
android:layout_width="80dip"
android:layout_height="wrap_content"
@@ -51,7 +51,7 @@
/>
<!-- Year -->
- <com.android.internal.widget.NumberPicker
+ <NumberPicker
android:id="@+id/year"
android:layout_width="95dip"
android:layout_height="wrap_content"
diff --git a/core/res/res/layout/dialog_custom_title.xml b/core/res/res/layout/dialog_custom_title.xml
index 68578f5..e52fba6 100644
--- a/core/res/res/layout/dialog_custom_title.xml
+++ b/core/res/res/layout/dialog_custom_title.xml
@@ -22,19 +22,19 @@ This is an custom layout for a dialog.
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout android:id="@android:id/title_container"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="24dip"
android:layout_weight="0"
style="?android:attr/windowTitleBackgroundStyle">
</FrameLayout>
<FrameLayout
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:foreground="?android:attr/windowContentOverlay">
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:paddingTop="6dip"
android:paddingBottom="10dip"
android:paddingLeft="10dip"
diff --git a/core/res/res/layout/dialog_title.xml b/core/res/res/layout/dialog_title.xml
index 8cfc716..949f86e 100644
--- a/core/res/res/layout/dialog_title.xml
+++ b/core/res/res/layout/dialog_title.xml
@@ -25,7 +25,7 @@ enabled.
android:orientation="vertical"
android:fitsSystemWindows="true">
<TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="53dip"
android:paddingTop="9dip"
@@ -33,13 +33,13 @@ enabled.
android:paddingLeft="10dip"
android:paddingRight="10dip" />
<FrameLayout
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:foreground="?android:attr/windowContentOverlay">
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
diff --git a/core/res/res/layout/dialog_title_icons.xml b/core/res/res/layout/dialog_title_icons.xml
index 7c3f274..0ca6706 100644
--- a/core/res/res/layout/dialog_title_icons.xml
+++ b/core/res/res/layout/dialog_title_icons.xml
@@ -24,7 +24,7 @@ enabled.
android:fitsSystemWindows="true">
<LinearLayout android:id="@+id/title_container"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
@@ -57,13 +57,13 @@ enabled.
</LinearLayout>
<FrameLayout
- android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:foreground="?android:attr/windowContentOverlay">
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
diff --git a/core/res/res/layout/expandable_list_content.xml b/core/res/res/layout/expandable_list_content.xml
index 05d74a6..4ed905c 100644
--- a/core/res/res/layout/expandable_list_content.xml
+++ b/core/res/res/layout/expandable_list_content.xml
@@ -19,6 +19,6 @@
-->
<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:drawSelectorOnTop="false" />
diff --git a/core/res/res/layout/global_actions_item.xml b/core/res/res/layout/global_actions_item.xml
index 63bb0f4..68fe960 100644
--- a/core/res/res/layout/global_actions_item.xml
+++ b/core/res/res/layout/global_actions_item.xml
@@ -15,7 +15,7 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingLeft="11dip"
@@ -25,7 +25,7 @@
<ImageView android:id="@+id/icon"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="9dip"
@@ -33,7 +33,7 @@
<TextView android:id="@+id/status"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="26dip"
android:layout_toRightOf="@id/icon"
@@ -45,7 +45,7 @@
<TextView android:id="@+id/message"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/icon"
diff --git a/core/res/res/layout/google_web_content_helper_layout.xml b/core/res/res/layout/google_web_content_helper_layout.xml
deleted file mode 100644
index 546c458..0000000
--- a/core/res/res/layout/google_web_content_helper_layout.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:foregroundGravity="center"
- android:measureAllChildren="false">
-
- <LinearLayout android:id="@+id/progressContainer"
- android:orientation="horizontal"
- android:layout_gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:baselineAligned="false"
- android:paddingLeft="8dip"
- android:paddingTop="10dip"
- android:paddingRight="8dip"
- android:paddingBottom="10dip">
-
- <ProgressBar android:id="@android:id/progress"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:max="10000"
- android:layout_marginRight="12dip" />
-
- <TextView android:id="@+id/message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical" />
- </LinearLayout>
-
- <WebView
- android:id="@+id/web"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:visibility="invisible"
- />
-
- <TextView
- android:id="@+id/text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:visibility="gone"
- android:padding="10dip"
- />
-
-</FrameLayout>
diff --git a/core/res/res/layout/grant_credentials_permission.xml b/core/res/res/layout/grant_credentials_permission.xml
index fe1c22e..4133ea9 100644
--- a/core/res/res/layout/grant_credentials_permission.xml
+++ b/core/res/res/layout/grant_credentials_permission.xml
@@ -1,41 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/*
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/**
+ * Copyright (c) 2008, Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- The header -->
<TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/message" />
- <Button android:id="@+id/allow"
- android:layout_width="fill_parent"
+ android:id="@+id/header_text"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/allow" />
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/white"
+ android:textStyle="bold"
+ android:text="@string/grant_permissions_header_text"
+ android:shadowColor="@color/shadow"
+ android:shadowRadius="2"
+ android:singleLine="true"
+ android:background="@drawable/title_bar_medium"
+ android:gravity="left|center_vertical"
+ android:paddingLeft="19dip"
+ android:ellipsize="marquee" />
- <Button android:id="@+id/deny"
- android:layout_width="fill_parent"
+ <!-- The list of packages that correspond to the requesting UID
+ and the account/authtokenType that is being requested -->
+ <ScrollView
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/deny" />
+ android:fillViewport="true"
+ android:layout_weight="1"
+ android:gravity="top|center_horizontal"
+ android:foreground="@drawable/title_bar_shadow">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="14dip"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/grant_credentials_permission_message_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/grant_credentials_permission_message_header"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:paddingLeft="19dip"
+ android:paddingBottom="12dip" />
+
+ <LinearLayout
+ android:id="@+id/packages_list"
+ android:orientation="vertical"
+ android:paddingLeft="16dip"
+ android:paddingRight="12dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <RelativeLayout
+ android:paddingLeft="16dip"
+ android:paddingRight="12dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:src="@drawable/ic_bullet_key_permission"
+ android:layout_alignParentLeft="true"
+ android:scaleType="fitCenter" />
- <ListView android:id="@+id/packages_list"
- android:layout_width="fill_parent" android:layout_height="fill_parent"/>
+ <TextView
+ android:id="@+id/account_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/account_name"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_type"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/authtoken_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/perms_dangerous_perm_color"
+ android:textStyle="bold"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_name"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </RelativeLayout>
+
+ <TextView
+ android:id="@+id/grant_credentials_permission_message_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/grant_credentials_permission_message_footer"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:paddingLeft="19dip"
+ android:paddingBottom="12dip" />
+ </LinearLayout>
+ </ScrollView>
+
+ <!-- The buttons to allow or deny -->
+ <LinearLayout
+ android:id="@+id/buttons"
+ android:layout_width="match_parent"
+ android:layout_height="52dip"
+ android:background="@drawable/bottom_bar"
+ android:paddingTop="4dip"
+ android:paddingLeft="2dip"
+ android:paddingRight="2dip">
+
+ <Button
+ android:id="@+id/allow_button"
+ android:text="@string/allow"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="2" />
+
+ <Button
+ android:id="@+id/deny_button"
+ android:text="@string/deny"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="2" />
+
+ </LinearLayout>
</LinearLayout>
+
diff --git a/core/res/res/layout/icon_menu_item_layout.xml b/core/res/res/layout/icon_menu_item_layout.xml
index c6d9496..a73dccc 100644
--- a/core/res/res/layout/icon_menu_item_layout.xml
+++ b/core/res/res/layout/icon_menu_item_layout.xml
@@ -16,8 +16,8 @@
<com.android.internal.view.menu.IconMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:paddingBottom="1dip"
android:paddingLeft="3dip"
android:paddingRight="3dip"
diff --git a/core/res/res/layout/input_method.xml b/core/res/res/layout/input_method.xml
index 1a701e9..f80d628 100644
--- a/core/res/res/layout/input_method.xml
+++ b/core/res/res/layout/input_method.xml
@@ -20,27 +20,27 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parentPanel"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<LinearLayout
android:id="@+id/fullscreenArea"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<FrameLayout android:id="@android:id/extractArea"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:visibility="gone">
</FrameLayout>
<FrameLayout android:id="@android:id/candidatesArea"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible">
</FrameLayout>
@@ -48,7 +48,7 @@
</LinearLayout>
<FrameLayout android:id="@android:id/inputArea"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
</FrameLayout>
diff --git a/core/res/res/layout/input_method_extract_view.xml b/core/res/res/layout/input_method_extract_view.xml
index 1244c1d..689ba7b 100644
--- a/core/res/res/layout/input_method_extract_view.xml
+++ b/core/res/res/layout/input_method_extract_view.xml
@@ -25,7 +25,7 @@
<android.inputmethodservice.ExtractEditText
android:id="@+id/inputExtractEditText"
android:layout_width="0px"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_weight="1"
android:scrollbars="vertical"
android:gravity="top"
@@ -38,7 +38,7 @@
<FrameLayout
android:id="@+id/inputExtractAccessories"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:background="@android:drawable/keyboard_accessory_bg_landscape"
diff --git a/core/res/res/layout/js_prompt.xml b/core/res/res/layout/js_prompt.xml
index 86974ba..d80fbc8 100644
--- a/core/res/res/layout/js_prompt.xml
+++ b/core/res/res/layout/js_prompt.xml
@@ -16,18 +16,18 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<TextView android:id="@+id/message"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<EditText android:id="@+id/value"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:inputType="text"
diff --git a/core/res/res/layout/keyboard_popup_keyboard.xml b/core/res/res/layout/keyboard_popup_keyboard.xml
index 1092cc0..5eb2732 100644
--- a/core/res/res/layout/keyboard_popup_keyboard.xml
+++ b/core/res/res/layout/keyboard_popup_keyboard.xml
@@ -19,7 +19,7 @@
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@android:drawable/keyboard_popup_panel_background"
@@ -29,7 +29,7 @@
android:id="@android:id/keyboardView"
android:background="@android:color/transparent"
android:layout_alignParentBottom="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:keyPreviewLayout="@layout/keyboard_key_preview"
android:popupLayout="@layout/keyboard_popup_keyboard"
diff --git a/core/res/res/layout/keyguard_screen_glogin_unlock.xml b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
index 6e00d11..8a46546 100644
--- a/core/res/res/layout/keyguard_screen_glogin_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
@@ -17,24 +17,24 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/background_dark"
>
<ScrollView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
android:layout_above="@+id/emergencyCall">
<RelativeLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<TextView
android:id="@+id/topHeader"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="64dip"
android:layout_alignParentTop="true"
android:layout_marginLeft="4dip"
@@ -47,14 +47,14 @@
<!-- spacer below header -->
<View
android:id="@+id/spacerTop"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_below="@id/topHeader"
android:background="@drawable/divider_horizontal_dark"/>
<TextView
android:id="@+id/instructions"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/spacerTop"
android:layout_marginTop="8dip"
@@ -65,7 +65,7 @@
<EditText
android:id="@+id/login"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/instructions"
android:layout_marginTop="8dip"
@@ -77,7 +77,7 @@
<EditText
android:id="@+id/password"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/login"
android:layout_marginTop="15dip"
@@ -106,7 +106,7 @@
<!-- spacer above emergency call -->
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginBottom="4dip"
diff --git a/core/res/res/layout/keyguard_screen_lock.xml b/core/res/res/layout/keyguard_screen_lock.xml
index b5fbace..6e4fa7d 100644
--- a/core/res/res/layout/keyguard_screen_lock.xml
+++ b/core/res/res/layout/keyguard_screen_lock.xml
@@ -24,13 +24,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="bottom"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<LinearLayout
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="15dip"
@@ -43,25 +43,25 @@
<!-- when sim is present -->
<TextView android:id="@+id/headerSimOk1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="34sp"/>
<TextView android:id="@+id/headerSimOk2"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="34sp"/>
<!-- when sim is missing / locked -->
<TextView android:id="@+id/headerSimBad1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@android:string/lockscreen_missing_sim_message"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView android:id="@+id/headerSimBad2"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dip"
android:layout_marginBottom="7dip"
@@ -71,27 +71,27 @@
<!-- spacer after carrier info / sim messages -->
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dip"
android:background="@android:drawable/divider_horizontal_dark"/>
<!-- time and date -->
<TextView android:id="@+id/time"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="34sp"/>
<TextView android:id="@+id/date"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="18sp"/>
<!-- spacer after time and date -->
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginBottom="8dip"
android:background="@android:drawable/divider_horizontal_dark"
@@ -100,7 +100,7 @@
<!-- battery info -->
<LinearLayout android:id="@+id/batteryInfo"
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
>
@@ -124,7 +124,7 @@
<!-- spacer after battery info -->
<View android:id="@+id/batteryInfoSpacer"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
@@ -135,7 +135,7 @@
<LinearLayout android:id="@+id/nextAlarmInfo"
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
>
@@ -159,7 +159,7 @@
<!-- spacer after alarm info -->
<View android:id="@+id/nextAlarmSpacer"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
@@ -169,7 +169,7 @@
(shown when SIM card is present) -->
<LinearLayout android:id="@+id/screenLockedInfo"
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
>
@@ -195,7 +195,7 @@
<!-- message about how to unlock
(shown when SIM card is present) -->
<TextView android:id="@+id/lockInstructions"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip"
android:gravity="center"
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
new file mode 100644
index 0000000..ab675c7
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="#70000000"
+ android:gravity="center_horizontal">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <!-- "Enter PIN(Password) to unlock" -->
+ <TextView android:id="@+id/enter_password_label"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="horizontal"
+ android:layout_marginRight="6dip"
+ android:layout_marginLeft="6dip"
+ android:layout_marginTop="10dip"
+ android:layout_marginBottom="10dip"
+ android:gravity="left"
+ android:ellipsize="marquee"
+ android:text="@android:string/keyguard_password_enter_password_code"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+
+ <!-- Password entry field -->
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:textSize="24sp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:background="@drawable/password_field_default"
+ android:textColor="#ffffffff"
+ />
+ </LinearLayout>
+
+ <!-- Alphanumeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#00000000"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+ />
+
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ />
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
new file mode 100644
index 0000000..9ee8781
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="#70000000"
+ android:gravity="center_horizontal">
+
+ <!-- "Enter PIN(Password) to unlock" -->
+ <TextView android:id="@+id/enter_password_label"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginRight="6dip"
+ android:layout_marginLeft="6dip"
+ android:layout_marginTop="10dip"
+ android:layout_marginBottom="10dip"
+ android:gravity="center"
+ android:text="@android:string/keyguard_password_enter_password_code"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ />
+
+ <!-- spacer above text entry field -->
+ <View
+ android:id="@+id/spacerBottom"
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_marginTop="6dip"
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <!-- Password entry field -->
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textStyle="bold"
+ android:inputType="textPassword"
+ android:gravity="center"
+ android:layout_gravity="center"
+ android:textSize="32sp"
+ android:layout_marginTop="15dip"
+ android:layout_marginLeft="30dip"
+ android:layout_marginRight="30dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:background="@drawable/password_field_default"
+ android:textColor="#ffffffff"
+ />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" />
+
+ <!-- Alphanumeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_alignParentBottom="true"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#00000000"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+ />
+
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:layout_marginTop="20dip"
+ android:layout_marginBottom="20dip"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ />
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_rotary_unlock.xml b/core/res/res/layout/keyguard_screen_rotary_unlock.xml
deleted file mode 100644
index 59b69cd..0000000
--- a/core/res/res/layout/keyguard_screen_rotary_unlock.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-
-<!-- This is the general lock screen which shows information about the
- state of the device, as well as instructions on how to get past it
- depending on the state of the device. It is the same for landscape
- and portrait.-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:id="@+id/root"
- >
-
-<RelativeLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#70000000"
- >
-
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="25dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="55sp"
- />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/time"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="-12dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <View
- android:id="@+id/divider"
- android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_marginTop="10dip"
- android:layout_below="@id/date"
- android:background="@android:drawable/divider_horizontal_dark"
- />
-
- <TextView
- android:id="@+id/status1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/divider"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status1"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/screenLocked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status2"
- android:layout_centerHorizontal="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:gravity="center"
- android:layout_marginTop="12dip"
- />
-
- <!-- By having the rotary selector hang from the top, we get a layout more
- robust for different screen sizes. On hvga, the widget should be flush with the bottom.-->
- <com.android.internal.widget.RotarySelector
- android:id="@+id/rotary"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_alignParentTop="true"
- android:layout_marginTop="286dip"
- />
-
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="24dip"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
- />
-
-</RelativeLayout>
-
-</FrameLayout>
diff --git a/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml b/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml
deleted file mode 100644
index c503455..0000000
--- a/core/res/res/layout/keyguard_screen_rotary_unlock_land.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-
-<!-- This is the general lock screen which shows information about the
- state of the device, as well as instructions on how to get past it
- depending on the state of the device.-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:rotaryunlock="http://schemas.android.com/apk/res/com.android.rotaryunlock"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:id="@+id/root"
- >
-<LinearLayout
- android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#70000000"
- >
-
- <!-- left side -->
- <RelativeLayout
- android:orientation="vertical"
- android:layout_width="0dip"
- android:layout_height="fill_parent"
- android:layout_weight="1.0"
- android:gravity="center_horizontal"
- >
-
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="20dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- />
-
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="25dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="55sp"
- />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/time"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="-12dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <View
- android:id="@+id/divider"
- android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_marginTop="10dip"
- android:layout_below="@id/date"
- android:background="@android:drawable/divider_horizontal_dark"
- />
-
- <TextView
- android:id="@+id/status1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/divider"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status1"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="6dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:drawablePadding="4dip"
- />
-
- <TextView
- android:id="@+id/screenLocked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/status2"
- android:layout_centerHorizontal="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorSecondary"
- android:gravity="center"
- android:layout_marginTop="12dip"
- />
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="24dip"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
- />
- </RelativeLayout>
-
-
- <!-- right side -->
- <com.android.internal.widget.RotarySelector
- android:id="@+id/rotary"
- android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:orientation="vertical"
- />
-
-
-</LinearLayout>
-</FrameLayout> \ No newline at end of file
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
index e75cf09..244afbe 100644
--- a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
@@ -18,8 +18,8 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:background="@android:color/background_dark"
>
@@ -49,7 +49,7 @@
<LinearLayout android:id="@+id/pinDisplayGroup"
android:orientation="horizontal"
android:layout_centerInParent="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:addStatesFromChildren="true"
android:gravity="center_vertical"
@@ -63,7 +63,7 @@
<EditText android:id="@+id/pinDisplay"
android:layout_width="0dip"
android:layout_weight="1"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:maxLines="1"
android:background="@null"
android:textSize="32sp"
@@ -73,7 +73,7 @@
<ImageButton android:id="@+id/backspace"
android:src="@android:drawable/ic_input_delete"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_marginTop="2dip"
android:layout_marginRight="2dip"
android:layout_marginBottom="2dip"
@@ -85,7 +85,7 @@
<LinearLayout
android:orientation="horizontal"
android:layout_alignParentBottom="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dip"
android:layout_marginLeft="8dip"
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
index c9a9d5d..009148f 100644
--- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
@@ -16,27 +16,25 @@
** limitations under the License.
*/
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
android:background="@android:color/background_dark"
- >
+ android:gravity="center_horizontal">
<LinearLayout android:id="@+id/topDisplayGroup"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
android:orientation="vertical">
<!-- header text ('Enter Pin Code') -->
<TextView android:id="@+id/headerText"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="9dip"
android:gravity="center"
android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceLarge"
- />
+ android:textAppearance="?android:attr/textAppearanceLarge"/>
<!-- Carrier info -->
<TextView android:id="@+id/carrier"
@@ -46,233 +44,76 @@
android:gravity="center"
android:singleLine="true"
android:ellipsize="marquee"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
- <RelativeLayout
- android:layout_width="fill_parent"
+ <!-- password entry -->
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="18dip"
+ android:orientation="horizontal"
android:layout_marginRight="6dip"
android:layout_marginLeft="6dip"
- android:background="@android:drawable/edit_text"
- >
+ android:gravity="center_vertical"
+ android:background="@android:drawable/edit_text">
<!-- displays dots as user enters pin -->
<TextView android:id="@+id/pinDisplay"
- android:layout_width="wrap_content"
- android:layout_height="64dip"
- android:layout_centerInParent="true"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:textStyle="bold"
android:inputType="textPassword"
- />
+ />
<ImageButton android:id="@+id/backspace"
android:src="@android:drawable/ic_input_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:layout_marginRight="1dip"
- />
- </RelativeLayout>
-
+ android:layout_marginRight="-3dip"
+ android:layout_marginBottom="-3dip"
+ />
+ </LinearLayout>
</LinearLayout>
- <!-- Keypad section -->
- <LinearLayout android:id="@+id/keyPad"
+ <include
+ android:id="@+id/keyPad"
+ layout="@android:layout/twelve_key_entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/topDisplayGroup"
android:layout_marginTop="10dip"
- android:orientation="vertical"
- >
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal"
- >
-
- <Button android:id="@+id/one"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/two"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/three"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal"
- >
-
- <Button android:id="@+id/four"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/five"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/six"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal"
- >
-
- <Button android:id="@+id/seven"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/eight"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/nine"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="64dip"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:orientation="horizontal"
- >
-
- <Button android:id="@+id/ok"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:text="@android:string/ok"
- />
-
- <Button android:id="@+id/zero"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textStyle="bold"
- />
-
- <Button android:id="@+id/cancel"
- android:layout_width="0sp"
- android:layout_height="fill_parent"
- android:layout_weight="1"
- android:layout_marginLeft="2dip"
- android:layout_marginRight="2dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:text="@android:string/cancel"
- />
- </LinearLayout>
-
- <!-- end keypad -->
- </LinearLayout>
-
-
- <!-- emergency call button -->
- <Button
- android:id="@+id/emergencyCall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:layout_alignParentBottom="true"
- android:drawableLeft="@android:drawable/ic_emergency"
- android:drawablePadding="8dip"
- android:text="@android:string/lockscreen_emergency_call"
- />
+ />
<!-- spacer below keypad -->
<View
android:id="@+id/spacerBottom"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
- android:layout_marginBottom="6dip"
+ android:layout_marginTop="6dip"
android:layout_above="@id/emergencyCall"
- android:background="@android:drawable/divider_horizontal_dark"/>
+ android:background="@android:drawable/divider_horizontal_dark"
+ />
+
+ <!-- The emergency button should take the rest of the space and be centered vertically -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:gravity="center"
+ android:orientation="vertical">
+ <!-- emergency call button -->
+ <Button
+ android:id="@+id/emergencyCall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@android:drawable/ic_emergency"
+ android:drawablePadding="8dip"
+ android:text="@android:string/lockscreen_emergency_call"
+ />
+ </LinearLayout>
-</RelativeLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 9d57486..200a1b2 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -23,8 +23,8 @@
and portrait.-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:background="#70000000"
android:gravity="center_horizontal"
android:id="@+id/root">
@@ -41,39 +41,57 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="22sp"
/>
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/carrier"
+ android:layout_alignParentRight="true"
+ android:layout_marginTop="0dip"
+ android:layout_marginRight="8dip"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/white"
+ />
+
<!-- time and date -->
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/carrier"
- android:layout_marginBottom="10dip"
android:layout_marginTop="52dip"
android:layout_marginLeft="20dip"
+ android:paddingBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
android:textSize="72sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="3.0"
+ android:layout_marginBottom="10dip"
/>
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:gravity="bottom"
- android:textSize="22sp"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="22sp"
android:layout_marginLeft="8dip"
- android:layout_marginBottom="-6dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
@@ -122,29 +140,32 @@
android:layout_marginLeft="24dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginTop="12dip"
+ android:drawablePadding="4dip"
/>
<com.android.internal.widget.SlidingTab
android:id="@+id/tab_selector"
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="80dip"
/>
- <!-- emergency call button shown when sim is missing or PUKd -->
+ <!-- emergency call button shown when sim is PUKd and tab_selector is
+ hidden -->
<Button
android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_marginTop="8dip"
- android:layout_marginLeft="24dip"
android:drawableLeft="@drawable/ic_emergency"
+ android:layout_centerInParent="true"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="80dip"
style="@style/Widget.Button.Transparent"
android:drawablePadding="8dip"
- />
+ android:visibility="gone"
+ />
</RelativeLayout>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index 27a29b1..23505c2 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -22,8 +22,8 @@
depending on the state of the device.-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#70000000"
android:id="@+id/root">
@@ -31,7 +31,7 @@
<!-- left side -->
<RelativeLayout
android:layout_width="0dip"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_weight="1.0"
android:layout_marginLeft="24dip"
android:gravity="left">
@@ -46,37 +46,53 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="22sp"
/>
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="20dip"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/white"
+ />
+
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/carrier"
- android:layout_marginBottom="8dip"
android:layout_marginTop="56dip"
+ android:paddingBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
android:textSize="72sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="3.0"
+ android:layout_marginBottom="6dip"
/>
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:gravity="bottom"
- android:textSize="22sp"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="22sp"
android:layout_marginLeft="8dip"
- android:layout_marginBottom="-6dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
@@ -123,19 +139,9 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="center"
android:layout_marginTop="12dip"
+ android:drawablePadding="4dip"
/>
- <!-- emergency call button shown when sim is missing or PUKd -->
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/screenLocked"
- android:layout_marginTop="8dip"
- android:drawableLeft="@drawable/ic_emergency"
- style="@style/Widget.Button.Transparent"
- android:drawablePadding="8dip"
- />
</RelativeLayout>
<!-- right side -->
@@ -143,8 +149,21 @@
android:id="@+id/tab_selector"
android:orientation="vertical"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
+ android:layout_marginRight="80dip"
+ />
+
+ <!-- emergency call button shown when sim is PUKd and tab_selector is
+ hidden -->
+ <Button
+ android:id="@+id/emergencyCallButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ style="@style/Widget.Button.Transparent"
+ android:drawablePadding="8dip"
android:layout_marginRight="80dip"
+ android:visibility="gone"
/>
</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index bca6241..b5cd442 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -24,8 +24,8 @@
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:background="#70000000"
>
@@ -33,7 +33,7 @@
<LinearLayout
android:orientation="vertical"
android:layout_width="0dip"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_weight="1.0"
android:layout_marginLeft="24dip"
android:gravity="left"
@@ -58,36 +58,40 @@
android:ellipsize="marquee"
android:gravity="right|bottom"
/>
+
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
- android:layout_marginBottom="8dip"
android:layout_marginTop="8dip"
+ android:paddingBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
android:textSize="72sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="3.0"
+ android:layout_marginBottom="6dip"
/>
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:gravity="bottom"
- android:textSize="22sp"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="22sp"
android:layout_marginLeft="8dip"
- android:layout_marginBottom="-6dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
@@ -137,20 +141,20 @@
<!-- fill space between header and button below -->
<View
android:layout_weight="1.0"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
/>
<!-- footer -->
<FrameLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
>
<!-- option 1: a single emergency call button -->
<RelativeLayout android:id="@+id/footerNormal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
>
@@ -167,18 +171,18 @@
<!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
<LinearLayout android:id="@+id/footerForgotPattern"
android:orientation="vertical"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left"
>
<Button android:id="@+id/forgotPattern"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.Button.Transparent"
android:visibility="invisible"
/>
<Button android:id="@+id/emergencyCallTogether"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lockscreen_emergency_call"
style="@style/Widget.Button.Transparent"
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 7c649ec..9ac0a47 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -23,14 +23,14 @@
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:gravity="center_horizontal"
android:background="#70000000"
>
<RelativeLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
@@ -54,30 +54,33 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="15dip"
- android:layout_marginBottom="6dip"
android:layout_marginLeft="20dip"
+ android:paddingBottom="8dip"
>
<TextView android:id="@+id/timeDisplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="bottom"
+ android:singleLine="true"
+ android:ellipsize="none"
android:textSize="56sp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="3.0"
+ android:layout_marginBottom="6dip"
/>
<TextView android:id="@+id/am_pm"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:gravity="bottom"
- android:textSize="18sp"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/timeDisplay"
+ android:layout_alignBaseline="@id/timeDisplay"
android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="18sp"
android:layout_marginLeft="4dip"
- android:layout_marginBottom="-6dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:shadowColor="#C0000000"
android:shadowDx="0"
@@ -100,7 +103,7 @@
<View
android:id="@+id/divider"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
@@ -111,7 +114,7 @@
status. -->
<LinearLayout
android:orientation="horizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dip"
android:layout_marginLeft="12dip"
@@ -147,22 +150,23 @@
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:layout_marginTop="2dip"
+ android:aspect="@string/lock_pattern_view_aspect"
/>
<!-- footer -->
<FrameLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<!-- option 1: a single emergency call button -->
<RelativeLayout android:id="@+id/footerNormal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<Button android:id="@+id/emergencyCallAlone"
android:layout_width="wrap_content"
@@ -179,13 +183,13 @@
<!-- option 2: an emergency call button, and a 'forgot pattern?' button -->
<LinearLayout android:id="@+id/footerForgotPattern"
android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:gravity="center"
>
<Button android:id="@+id/emergencyCallTogether"
android:layout_width="0dip"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_weight="1.0"
android:layout_marginTop="4dip"
android:layout_marginBottom="4dip"
@@ -198,7 +202,7 @@
/>
<Button android:id="@+id/forgotPattern"
android:layout_width="0dip"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_weight="1.0"
android:layout_marginTop="4dip"
android:layout_marginBottom="4dip"
diff --git a/core/res/res/layout/list_content.xml b/core/res/res/layout/list_content.xml
index a7f3e2d..6f9f1e0 100644
--- a/core/res/res/layout/list_content.xml
+++ b/core/res/res/layout/list_content.xml
@@ -18,7 +18,7 @@
*/
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:drawSelectorOnTop="false"
/>
diff --git a/core/res/res/layout/list_gestures_overlay.xml b/core/res/res/layout/list_gestures_overlay.xml
index 54d72c8..5ebf092 100644
--- a/core/res/res/layout/list_gestures_overlay.xml
+++ b/core/res/res/layout/list_gestures_overlay.xml
@@ -15,5 +15,5 @@
-->
<android.gesture.GestureOverlayView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/core/res/res/layout/list_menu_item_layout.xml b/core/res/res/layout/list_menu_item_layout.xml
index df4958f..39c8872 100644
--- a/core/res/res/layout/list_menu_item_layout.xml
+++ b/core/res/res/layout/list_menu_item_layout.xml
@@ -15,7 +15,7 @@
-->
<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight">
<!-- Icon will be inserted here. -->
@@ -32,7 +32,7 @@
<TextView
android:id="@+id/title"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
diff --git a/core/res/res/layout/media_controller.xml b/core/res/res/layout/media_controller.xml
index 32db60a..f4a701e 100644
--- a/core/res/res/layout/media_controller.xml
+++ b/core/res/res/layout/media_controller.xml
@@ -15,17 +15,17 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#CC666666"
android:orientation="vertical">
- <ImageView android:layout_width="fill_parent"
+ <ImageView android:layout_width="match_parent"
android:layout_height="1px"
android:background="@android:drawable/divider_horizontal_dark" />
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dip"
@@ -40,7 +40,7 @@
</LinearLayout>
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index bbdb31c..9241708 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -19,13 +19,13 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.internal.widget.NumberPickerButton android:id="@+id/increment"
- android:layout_width="fill_parent"
+ <NumberPickerButton android:id="@+id/increment"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_up_btn" />
<EditText android:id="@+id/timepicker_input"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:singleLine="true"
@@ -34,8 +34,8 @@
android:textSize="30sp"
android:background="@drawable/timepicker_input" />
- <com.android.internal.widget.NumberPickerButton android:id="@+id/decrement"
- android:layout_width="fill_parent"
+ <NumberPickerButton android:id="@+id/decrement"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/timepicker_down_btn" />
diff --git a/core/res/res/layout/permissions_account_and_authtokentype.xml b/core/res/res/layout/permissions_account_and_authtokentype.xml
new file mode 100644
index 0000000..8335726
--- /dev/null
+++ b/core/res/res/layout/permissions_account_and_authtokentype.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Defines the layout of an account and authtoken type permission item.
+ Contains an icon, the account type and name and the authtoken type.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:drawable="@drawable/ic_bullet_key_permission"
+ android:layout_alignParentLeft="true"
+ android:scaleType="fitCenter" />
+
+
+ <TextView
+ android:id="@+id/account_type"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/account_name"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_type"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/authtoken_type"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:layout_marginTop="-4dip"
+ android:paddingBottom="8dip"
+ android:paddingLeft="6dip"
+ android:layout_below="@id/account_name"
+ android:layout_toRightOf="@id/permission_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/permissions_package_list_item.xml b/core/res/res/layout/permissions_package_list_item.xml
new file mode 100644
index 0000000..3c9570e
--- /dev/null
+++ b/core/res/res/layout/permissions_package_list_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Defines the layout of a single package item.
+ Contains a bullet point icon and the name of the package.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/package_icon"
+ android:layout_width="30dip"
+ android:layout_height="30dip"
+ android:layout_alignParentLeft="true"
+ android:src="@drawable/ic_text_dot"
+ android:scaleType="fitCenter" />
+
+
+ <TextView
+ android:id="@+id/package_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:paddingLeft="6dip"
+ android:layout_toRightOf="@id/package_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/core/res/res/layout/power_dialog.xml b/core/res/res/layout/power_dialog.xml
index 7c59ab4..60298d2 100644
--- a/core/res/res/layout/power_dialog.xml
+++ b/core/res/res/layout/power_dialog.xml
@@ -18,30 +18,30 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button android:id="@+id/keyguard"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button android:id="@+id/off"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/power_off" />
<Button android:id="@+id/silent"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button android:id="@+id/radio_power"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
diff --git a/core/res/res/layout/preference.xml b/core/res/res/layout/preference.xml
index 00745b4..9bd6f1b 100644
--- a/core/res/res/layout/preference.xml
+++ b/core/res/res/layout/preference.xml
@@ -18,7 +18,7 @@
Preference is able to place a specific widget for its particular
type in the "widget_frame" layout. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
@@ -47,14 +47,14 @@
android:layout_below="@android:id/title"
android:layout_alignLeft="@android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:maxLines="2" />
+ android:maxLines="4" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+android:id/widget_frame"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
diff --git a/core/res/res/layout/preference_child.xml b/core/res/res/layout/preference_child.xml
index 5f8ddd4..8975ed6 100644
--- a/core/res/res/layout/preference_child.xml
+++ b/core/res/res/layout/preference_child.xml
@@ -16,7 +16,7 @@
<!-- Layout for a visually child-like Preference in a PreferenceActivity. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
@@ -53,7 +53,7 @@
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+android:id/widget_frame"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml
index 7d1faac..5be5773 100644
--- a/core/res/res/layout/preference_dialog_edittext.xml
+++ b/core/res/res/layout/preference_dialog_edittext.xml
@@ -15,17 +15,23 @@
-->
<!-- Layout used as the dialog's content View for EditTextPreference. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+android:id/edittext_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="5dip"
- android:orientation="vertical">
-
- <TextView android:id="@+android:id/message"
- style="?android:attr/textAppearanceSmall"
- android:layout_width="fill_parent"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+android:id/edittext_container"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textColor="?android:attr/textColorSecondary" />
-
-</LinearLayout>
+ android:padding="5dip"
+ android:orientation="vertical">
+
+ <TextView android:id="@+android:id/message"
+ style="?android:attr/textAppearanceSmall"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/core/res/res/layout/preference_information.xml b/core/res/res/layout/preference_information.xml
index 8f05a8e..a5cf31a 100644
--- a/core/res/res/layout/preference_information.xml
+++ b/core/res/res/layout/preference_information.xml
@@ -18,7 +18,7 @@
Preference is able to place a specific widget for its particular
type in the "widget_frame" layout. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
@@ -54,7 +54,7 @@
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@+android:id/widget_frame"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" />
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 31113e1..8f86981 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -18,8 +18,8 @@
*/
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:drawSelectorOnTop="false"
android:scrollbarAlwaysDrawVerticalTrack="true"
/>
diff --git a/core/res/res/layout/progress_dialog.xml b/core/res/res/layout/progress_dialog.xml
index 8f66451..298173a 100644
--- a/core/res/res/layout/progress_dialog.xml
+++ b/core/res/res/layout/progress_dialog.xml
@@ -19,13 +19,13 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/body"
android:orientation="horizontal"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:baselineAligned="false"
android:paddingLeft="8dip"
android:paddingTop="10dip"
@@ -40,7 +40,7 @@
android:layout_marginRight="12dip" />
<TextView android:id="@+id/message"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
diff --git a/core/res/res/layout/recent_apps_dialog.xml b/core/res/res/layout/recent_apps_dialog.xml
index c4ee95d..e3492f6 100644
--- a/core/res/res/layout/recent_apps_dialog.xml
+++ b/core/res/res/layout/recent_apps_dialog.xml
@@ -17,22 +17,33 @@
*/
-->
-<LinearLayout
+<com.android.internal.policy.impl.RecentApplicationsBackground
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:padding="3dip"
+ android:background="@drawable/recent_dialog_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:padding="4dip"
android:orientation="vertical">
+ <!-- Title -->
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="40dip"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="#80FFFFFF"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:text="@android:string/recent_tasks_title" />
+
<!-- This is only intended to be visible when all buttons (below) are invisible -->
<TextView
android:id="@+id/no_applications_message"
- android:layout_width="285dip"
- android:layout_height="wrap_content"
- android:layout_marginTop="15dip"
- android:layout_marginBottom="15dip"
+ android:layout_width="320dip"
+ android:layout_height="80dip"
android:gravity="center"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textAppearance="?android:attr/textAppearanceSmall"
android:text="@android:string/no_recent_tasks" />
<!-- The first row has a fixed-width because the UI spec requires the box
@@ -40,9 +51,14 @@
adjust height based on number of rows. -->
<!-- TODO Adjust all sizes, padding, etc. to meet pixel-perfect specs -->
<LinearLayout
- android:layout_width="285dip"
+ android:layout_width="320dip"
android:layout_height="wrap_content"
- android:orientation="horizontal" >
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@android:layout/recent_apps_icon"
+ android:id="@+id/button0" />
<include
layout="@android:layout/recent_apps_icon"
@@ -59,7 +75,7 @@
</LinearLayout>
<LinearLayout
- android:layout_width="wrap_content"
+ android:layout_width="320dp"
android:layout_height="wrap_content"
android:orientation="horizontal" >
@@ -75,5 +91,15 @@
layout="@android:layout/recent_apps_icon"
android:id="@+id/button6" />
+ <include
+ layout="@android:layout/recent_apps_icon"
+ android:id="@+id/button7" />
+
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+
+ <!-- spacer to balance out the title above -->
+ <FrameLayout
+ android:layout_height="40dip"
+ android:layout_width="match_parent"
+ />
+</com.android.internal.policy.impl.RecentApplicationsBackground>
diff --git a/core/res/res/layout/recent_apps_icon.xml b/core/res/res/layout/recent_apps_icon.xml
index d32643c..f73aec3 100644
--- a/core/res/res/layout/recent_apps_icon.xml
+++ b/core/res/res/layout/recent_apps_icon.xml
@@ -22,11 +22,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/label"
style="?android:attr/buttonStyle"
- android:background="@drawable/btn_application_selector"
- android:layout_width="87dip"
- android:layout_height="88dip"
- android:layout_margin="3dip"
- android:textColor="@color/primary_text_dark_focused"
+ android:background="#00000000"
+ android:layout_width="80dip"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dip"
+ android:layout_marginBottom="3dip"
+ android:textColor="@color/bright_foreground_dark"
android:paddingTop="5dip"
android:paddingBottom="2dip"
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 4c5c456..caa82d4 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,7 +22,7 @@
android:orientation="horizontal"
android:minHeight="?android:attr/listPreferredItemHeight"
android:layout_height="wrap_content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:paddingLeft="10dip"
android:paddingRight="15dip">
@@ -42,12 +42,14 @@
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:maxLines="2"
android:paddingLeft="10dip" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
android:textAppearance="?android:attr/textAppearanceMediumInverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:maxLines="2"
android:paddingLeft="10dip" />
</LinearLayout>
</LinearLayout>
diff --git a/core/res/res/layout/screen.xml b/core/res/res/layout/screen.xml
index ded97e2..dfa9731 100644
--- a/core/res/res/layout/screen.xml
+++ b/core/res/res/layout/screen.xml
@@ -22,13 +22,13 @@ This is the basic layout for a screen, with all of its features enabled.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<!-- Title bar -->
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
>
<ImageView android:id="@android:id/left_icon"
@@ -66,7 +66,7 @@ This is the basic layout for a screen, with all of its features enabled.
</LinearLayout>
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="2dip"
android:layout_toLeftOf="@android:id/right_container"
@@ -77,8 +77,8 @@ This is the basic layout for a screen, with all of its features enabled.
/>
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:background="@null"
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
@@ -90,7 +90,7 @@ This is the basic layout for a screen, with all of its features enabled.
<!-- Content -->
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml
index 12ed1d0..34c9de0 100644
--- a/core/res/res/layout/screen_custom_title.xml
+++ b/core/res/res/layout/screen_custom_title.xml
@@ -22,12 +22,12 @@ This is an custom layout for a screen.
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout android:id="@android:id/title_container"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
</FrameLayout>
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/screen_progress.xml b/core/res/res/layout/screen_progress.xml
index 477cadb..6e694c1 100644
--- a/core/res/res/layout/screen_progress.xml
+++ b/core/res/res/layout/screen_progress.xml
@@ -23,12 +23,12 @@ This is the basic layout for a screen, with all of its features enabled.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
>
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
>
<ProgressBar android:id="@+android:id/progress_circular"
@@ -43,7 +43,7 @@ This is the basic layout for a screen, with all of its features enabled.
/>
<ProgressBar android:id="@+android:id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="2dip"
android:layout_alignParentLeft="true"
@@ -54,8 +54,8 @@ This is the basic layout for a screen, with all of its features enabled.
/>
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@android:id/progress_circular"
android:background="@null"
@@ -65,7 +65,7 @@ This is the basic layout for a screen, with all of its features enabled.
/>
</RelativeLayout>
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/screen_title.xml b/core/res/res/layout/screen_title.xml
index 5fcd2dd..39ea131 100644
--- a/core/res/res/layout/screen_title.xml
+++ b/core/res/res/layout/screen_title.xml
@@ -23,7 +23,7 @@ enabled.
android:orientation="vertical"
android:fitsSystemWindows="true">
<FrameLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
@@ -31,11 +31,11 @@ enabled.
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
index 4d7a6c8..62a0228 100644
--- a/core/res/res/layout/screen_title_icons.xml
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -21,11 +21,11 @@ This is the basic layout for a screen, with all of its features enabled.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize">
<!-- The title background has 9px left padding. -->
<ImageView android:id="@android:id/left_icon"
@@ -51,7 +51,7 @@ This is the basic layout for a screen, with all of its features enabled.
the left of the title text left edge. -->
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="-3dip"
android:layout_toLeftOf="@android:id/progress_circular"
@@ -60,33 +60,36 @@ This is the basic layout for a screen, with all of its features enabled.
android:visibility="gone"
android:max="10000" />
<LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_toLeftOf="@id/progress_circular"
android:layout_toRightOf="@android:id/left_icon"
>
+ <TextView android:id="@android:id/title"
+ style="?android:attr/windowTitleStyle"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="@null"
+ android:fadingEdge="horizontal"
+ android:scrollHorizontally="true"
+ android:gravity="center_vertical"
+ android:layout_marginRight="2dip"
+ />
<!-- 2dip between the icon and the title text, if icon is present. -->
<ImageView android:id="@android:id/right_icon"
android:visibility="gone"
android:layout_width="16dip"
android:layout_height="16dip"
+ android:layout_weight="0"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
- android:layout_marginRight="2dip" />
- <TextView android:id="@android:id/title"
- style="?android:attr/windowTitleStyle"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="@null"
- android:fadingEdge="horizontal"
- android:scrollHorizontally="true"
- android:gravity="center_vertical"
/>
</LinearLayout>
</RelativeLayout>
<FrameLayout android:id="@android:id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index 964af9b..7935e2a 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -21,7 +21,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
class="android.app.SearchDialog$SearchBar"
android:id="@+id/search_bar"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="true"
@@ -30,11 +30,11 @@
<!-- Outer layout defines the entire search bar at the top of the screen -->
<LinearLayout
android:id="@+id/search_plate"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="12dip"
- android:paddingRight="12dip"
+ android:paddingRight="6dip"
android:paddingTop="7dip"
android:paddingBottom="16dip"
android:background="@drawable/search_plate_global" >
@@ -52,7 +52,7 @@
<!-- Inner layout contains the app icon, button(s) and EditText -->
<LinearLayout
android:id="@+id/search_edit_frame"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
@@ -76,8 +76,8 @@
android:singleLine="true"
android:ellipsize="end"
android:inputType="text|textAutoComplete"
- android:dropDownWidth="fill_parent"
- android:dropDownHeight="fill_parent"
+ android:dropDownWidth="match_parent"
+ android:dropDownHeight="match_parent"
android:dropDownAnchor="@id/search_plate"
android:dropDownVerticalOffset="-9dip"
android:popupBackground="@android:drawable/search_dropdown_background"
@@ -88,14 +88,17 @@
android:id="@+id/search_go_btn"
android:background="@drawable/btn_search_dialog"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
/>
<ImageButton
android:id="@+id/search_voice_btn"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_marginLeft="8dip"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="0dip"
+ android:layout_marginTop="-6.5dip"
+ android:layout_marginBottom="-7dip"
+ android:layout_marginRight="-5dip"
android:background="@drawable/btn_search_dialog_voice"
android:src="@android:drawable/ic_btn_speak_now"
/>
diff --git a/core/res/res/layout/search_dropdown_item_1line.xml b/core/res/res/layout/search_dropdown_item_1line.xml
index bf3dd48..eed8b7d 100644
--- a/core/res/res/layout/search_dropdown_item_1line.xml
+++ b/core/res/res/layout/search_dropdown_item_1line.xml
@@ -22,5 +22,5 @@
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/searchResultListItemHeight" /> \ No newline at end of file
diff --git a/core/res/res/layout/search_dropdown_item_icons_2line.xml b/core/res/res/layout/search_dropdown_item_icons_2line.xml
index 2710b3b..d71b4f7 100644
--- a/core/res/res/layout/search_dropdown_item_icons_2line.xml
+++ b/core/res/res/layout/search_dropdown_item_icons_2line.xml
@@ -21,7 +21,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingLeft="4dip"
android:paddingRight="2dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/searchResultListItemHeight" >
<!-- Icons come first in the layout, since their placement doesn't depend on
@@ -50,7 +50,7 @@
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceSearchResultSubtitle"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="29dip"
android:paddingBottom="4dip"
android:gravity="top"
@@ -66,7 +66,7 @@
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceSearchResultTitle"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@android:id/icon1"
diff --git a/core/res/res/layout/seekbar_dialog.xml b/core/res/res/layout/seekbar_dialog.xml
index f61f435..84352a5 100644
--- a/core/res/res/layout/seekbar_dialog.xml
+++ b/core/res/res/layout/seekbar_dialog.xml
@@ -15,8 +15,8 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
@@ -26,7 +26,7 @@
android:paddingTop="20dip" />
<SeekBar android:id="@+id/seekbar"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dip" />
diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml
index 249b527..c665f7a 100644
--- a/core/res/res/layout/select_dialog.xml
+++ b/core/res/res/layout/select_dialog.xml
@@ -26,8 +26,8 @@
<view class="com.android.internal.app.AlertController$RecycleListView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+android:id/select_dialog_listview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:layout_marginTop="5px"
android:cacheColorHint="@null"
android:divider="@android:drawable/divider_horizontal_bright"
diff --git a/core/res/res/layout/select_dialog_item.xml b/core/res/res/layout/select_dialog_item.xml
index 60a74a4..30fe02e 100644
--- a/core/res/res/layout/select_dialog_item.xml
+++ b/core/res/res/layout/select_dialog_item.xml
@@ -25,7 +25,7 @@
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/core/res/res/layout/select_dialog_multichoice.xml b/core/res/res/layout/select_dialog_multichoice.xml
index 55fc39b..5785d3b 100644
--- a/core/res/res/layout/select_dialog_multichoice.xml
+++ b/core/res/res/layout/select_dialog_multichoice.xml
@@ -16,7 +16,7 @@
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml
index 220af64..3560fee 100644
--- a/core/res/res/layout/select_dialog_singlechoice.xml
+++ b/core/res/res/layout/select_dialog_singlechoice.xml
@@ -16,7 +16,7 @@
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/core/res/res/layout/simple_dropdown_hint.xml b/core/res/res/layout/simple_dropdown_hint.xml
index 44be46d..df9d720 100644
--- a/core/res/res/layout/simple_dropdown_hint.xml
+++ b/core/res/res/layout/simple_dropdown_hint.xml
@@ -25,5 +25,5 @@
android:layout_marginTop="3dip"
android:layout_marginRight="3dip"
android:layout_marginBottom="3dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/core/res/res/layout/simple_dropdown_item_1line.xml b/core/res/res/layout/simple_dropdown_item_1line.xml
index 5745d15..18e7b88 100644
--- a/core/res/res/layout/simple_dropdown_item_1line.xml
+++ b/core/res/res/layout/simple_dropdown_item_1line.xml
@@ -22,6 +22,6 @@
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:ellipsize="marquee" />
diff --git a/core/res/res/layout/simple_dropdown_item_2line.xml b/core/res/res/layout/simple_dropdown_item_2line.xml
index a04c849..903adb0 100644
--- a/core/res/res/layout/simple_dropdown_item_2line.xml
+++ b/core/res/res/layout/simple_dropdown_item_2line.xml
@@ -19,7 +19,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:gravity="center_vertical"
@@ -39,7 +39,7 @@
style="?android:attr/dropDownItemStyle"
android:textAppearance="?android:attr/textAppearanceLargeInverse"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
@@ -48,7 +48,7 @@
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:textColor="#323232"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_alignLeft="@android:id/text1" />
diff --git a/core/res/res/layout/simple_expandable_list_item_1.xml b/core/res/res/layout/simple_expandable_list_item_1.xml
index 052b353..dc3e58e 100644
--- a/core/res/res/layout/simple_expandable_list_item_1.xml
+++ b/core/res/res/layout/simple_expandable_list_item_1.xml
@@ -16,7 +16,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingLeft="?android:attr/expandableListPreferredItemPaddingLeft"
android:textAppearance="?android:attr/textAppearanceLarge"
diff --git a/core/res/res/layout/simple_expandable_list_item_2.xml b/core/res/res/layout/simple_expandable_list_item_2.xml
index 741f1db..b48b444 100644
--- a/core/res/res/layout/simple_expandable_list_item_2.xml
+++ b/core/res/res/layout/simple_expandable_list_item_2.xml
@@ -15,7 +15,7 @@
-->
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingTop="2dip"
android:paddingBottom="2dip"
@@ -24,14 +24,14 @@
>
<TextView android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dip"
android:textAppearance="?android:attr/textAppearanceLarge"
/>
<TextView android:id="@android:id/text2"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_alignLeft="@android:id/text1"
diff --git a/core/res/res/layout/simple_list_item_1.xml b/core/res/res/layout/simple_list_item_1.xml
index fe617ac..c9c77a5 100644
--- a/core/res/res/layout/simple_list_item_1.xml
+++ b/core/res/res/layout/simple_list_item_1.xml
@@ -16,7 +16,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
diff --git a/core/res/res/layout/simple_list_item_2.xml b/core/res/res/layout/simple_list_item_2.xml
index b5e2385..5eab54e 100644
--- a/core/res/res/layout/simple_list_item_2.xml
+++ b/core/res/res/layout/simple_list_item_2.xml
@@ -17,14 +17,14 @@
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingTop="2dip"
android:paddingBottom="2dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:mode="twoLine"
>
<TextView android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="6dip"
android:layout_marginTop="6dip"
@@ -32,7 +32,7 @@
/>
<TextView android:id="@android:id/text2"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_alignLeft="@android:id/text1"
diff --git a/core/res/res/layout/simple_list_item_checked.xml b/core/res/res/layout/simple_list_item_checked.xml
index 95612f6..5f99044 100644
--- a/core/res/res/layout/simple_list_item_checked.xml
+++ b/core/res/res/layout/simple_list_item_checked.xml
@@ -16,7 +16,7 @@
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
diff --git a/core/res/res/layout/simple_list_item_multiple_choice.xml b/core/res/res/layout/simple_list_item_multiple_choice.xml
index 102e5fc..05c66f3 100644
--- a/core/res/res/layout/simple_list_item_multiple_choice.xml
+++ b/core/res/res/layout/simple_list_item_multiple_choice.xml
@@ -16,7 +16,7 @@
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
diff --git a/core/res/res/layout/simple_list_item_single_choice.xml b/core/res/res/layout/simple_list_item_single_choice.xml
index 326de1d..27afd1d 100644
--- a/core/res/res/layout/simple_list_item_single_choice.xml
+++ b/core/res/res/layout/simple_list_item_single_choice.xml
@@ -16,7 +16,7 @@
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
diff --git a/core/res/res/layout/simple_spinner_dropdown_item.xml b/core/res/res/layout/simple_spinner_dropdown_item.xml
index 7006b09..5fd7a09 100644
--- a/core/res/res/layout/simple_spinner_dropdown_item.xml
+++ b/core/res/res/layout/simple_spinner_dropdown_item.xml
@@ -21,6 +21,6 @@
android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:ellipsize="marquee" />
diff --git a/core/res/res/layout/simple_spinner_item.xml b/core/res/res/layout/simple_spinner_item.xml
index 4dd739f..77929ee 100644
--- a/core/res/res/layout/simple_spinner_item.xml
+++ b/core/res/res/layout/simple_spinner_item.xml
@@ -21,6 +21,6 @@
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee" />
diff --git a/core/res/res/layout/status_bar.xml b/core/res/res/layout/status_bar.xml
index 0bc0dac..e8d8866 100644
--- a/core/res/res/layout/status_bar.xml
+++ b/core/res/res/layout/status_bar.xml
@@ -27,14 +27,14 @@
>
<LinearLayout android:id="@+id/icons"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="horizontal">
<com.android.server.status.IconMerger android:id="@+id/notificationIcons"
android:layout_width="0dip"
android:layout_weight="1"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:paddingLeft="6dip"
android:gravity="center_vertical"
@@ -42,7 +42,7 @@
<LinearLayout android:id="@+id/statusIcons"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingRight="6dip"
android:gravity="center_vertical"
@@ -50,14 +50,14 @@
</LinearLayout>
<LinearLayout android:id="@+id/ticker"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:paddingLeft="6dip"
android:animationCache="false"
android:orientation="horizontal" >
<ImageSwitcher android:id="@+id/tickerIcon"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:layout_marginRight="8dip"
>
<com.android.server.status.AnimatedImageView
@@ -76,12 +76,12 @@
android:paddingTop="2dip"
android:paddingRight="10dip">
<TextView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="#ff000000" />
<TextView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="#ff000000" />
@@ -90,7 +90,7 @@
<com.android.server.status.DateView android:id="@+id/date"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
+ android:layout_height="match_parent"
android:singleLine="true"
android:textSize="16sp"
android:textStyle="bold"
diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml
index fd9d26e..30138a7 100644
--- a/core/res/res/layout/status_bar_expanded.xml
+++ b/core/res/res/layout/status_bar_expanded.xml
@@ -20,12 +20,12 @@
<com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:background="@drawable/status_bar_background"
android:focusable="true"
- android:descendantFocusability="afterDescendants">
+ android:descendantFocusability="afterDescendants"
+ >
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="3dp"
@@ -77,26 +77,25 @@
</LinearLayout>
<FrameLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_weight="1"
>
<ScrollView
android:id="@+id/scroll"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_weight="1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:fadingEdge="none"
>
<com.android.server.status.NotificationLinearLayout
android:id="@+id/notificationLinearLayout"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
android:orientation="vertical"
>
<TextView android:id="@+id/noNotificationsTitle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_portrait"
android:paddingLeft="5dp"
@@ -105,7 +104,7 @@
/>
<TextView android:id="@+id/ongoingTitle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_portrait"
android:paddingLeft="5dp"
@@ -113,13 +112,13 @@
android:text="@string/status_bar_ongoing_events_title"
/>
<LinearLayout android:id="@+id/ongoingItems"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
/>
<TextView android:id="@+id/latestTitle"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_portrait"
android:paddingLeft="5dp"
@@ -127,7 +126,7 @@
android:text="@string/status_bar_latest_events_title"
/>
<LinearLayout android:id="@+id/latestItems"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
/>
@@ -135,8 +134,8 @@
</ScrollView>
<ImageView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:src="@drawable/title_bar_shadow"
android:scaleType="fitXY"
/>
diff --git a/core/res/res/layout/status_bar_icon.xml b/core/res/res/layout/status_bar_icon.xml
index 1516036..0536792 100644
--- a/core/res/res/layout/status_bar_icon.xml
+++ b/core/res/res/layout/status_bar_icon.xml
@@ -26,8 +26,8 @@
>
<com.android.server.status.AnimatedImageView android:id="@+id/image"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
/>
<TextView android:id="@+id/number"
diff --git a/core/res/res/layout/status_bar_latest_event.xml b/core/res/res/layout/status_bar_latest_event.xml
index d524bb6..59cc90d 100644
--- a/core/res/res/layout/status_bar_latest_event.xml
+++ b/core/res/res/layout/status_bar_latest_event.xml
@@ -1,11 +1,11 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="65sp"
android:orientation="vertical"
>
<com.android.server.status.LatestItemView android:id="@+id/content"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="64sp"
android:background="@drawable/status_bar_item_background"
android:focusable="true"
@@ -15,7 +15,7 @@
</com.android.server.status.LatestItemView>
<View
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="1sp"
android:background="@drawable/divider_horizontal_bright"
/>
diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml
index eeb9d9d..c3aa041 100644
--- a/core/res/res/layout/status_bar_latest_event_content.xml
+++ b/core/res/res/layout/status_bar_latest_event_content.xml
@@ -1,13 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="7dp"
android:paddingLeft="5dp"
>
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="3dp"
@@ -18,7 +18,7 @@
android:scaleType="fitCenter"
android:src="@drawable/arrow_down_float"/>
<TextView android:id="@+id/title"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
@@ -30,7 +30,7 @@
android:textColor="#ff000000" />
</LinearLayout>
<LinearLayout
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
@@ -45,7 +45,7 @@
android:textSize="14sp"
android:paddingLeft="4dp"
/>
- <TextView android:id="@+id/time"
+ <android.widget.DateTimeView android:id="@+id/time"
android:layout_marginLeft="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/core/res/res/layout/status_bar_tracking.xml b/core/res/res/layout/status_bar_tracking.xml
index aa3b733..c0a7a97 100644
--- a/core/res/res/layout/status_bar_tracking.xml
+++ b/core/res/res/layout/status_bar_tracking.xml
@@ -26,18 +26,18 @@
>
<com.android.server.status.TrackingPatternView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<com.android.server.status.CloseDragHandle android:id="@+id/close"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<ImageView
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:scaleType="fitXY"
diff --git a/core/res/res/layout/tab_content.xml b/core/res/res/layout/tab_content.xml
index 8f67af0..0ee87ce 100644
--- a/core/res/res/layout/tab_content.xml
+++ b/core/res/res/layout/tab_content.xml
@@ -19,13 +19,13 @@
-->
<TabHost xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/tabhost"
- android:layout_width="fill_parent" android:layout_height="fill_parent">
+ android:layout_width="match_parent" android:layout_height="match_parent">
<LinearLayout android:orientation="vertical"
- android:layout_width="fill_parent" android:layout_height="fill_parent">
- <TabWidget android:id="@android:id/tabs" android:layout_width="fill_parent"
+ android:layout_width="match_parent" android:layout_height="match_parent">
+ <TabWidget android:id="@android:id/tabs" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_weight="0" />
<FrameLayout android:id="@android:id/tabcontent"
- android:layout_width="fill_parent" android:layout_height="0dip"
+ android:layout_width="match_parent" android:layout_height="0dip"
android:layout_weight="1"/>
</LinearLayout>
</TabHost>
diff --git a/core/res/res/layout/test_list_item.xml b/core/res/res/layout/test_list_item.xml
index f4e0d3c..ede32e8 100644
--- a/core/res/res/layout/test_list_item.xml
+++ b/core/res/res/layout/test_list_item.xml
@@ -19,6 +19,6 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:paddingTop="2dip"
android:paddingBottom="3dip"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
diff --git a/core/res/res/layout/textview_hint.xml b/core/res/res/layout/textview_hint.xml
index d69a2f6..4978be5 100644
--- a/core/res/res/layout/textview_hint.xml
+++ b/core/res/res/layout/textview_hint.xml
@@ -15,8 +15,8 @@
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:background="@drawable/popup_inline_error"
android:textAppearance="?android:attr/textAppearanceSmallInverse"
/>
diff --git a/core/res/res/layout/time_picker.xml b/core/res/res/layout/time_picker.xml
index 6ba5e81..6124ea8 100644
--- a/core/res/res/layout/time_picker.xml
+++ b/core/res/res/layout/time_picker.xml
@@ -26,7 +26,7 @@
android:layout_height="wrap_content">
<!-- hour -->
- <com.android.internal.widget.NumberPicker
+ <NumberPicker
android:id="@+id/hour"
android:layout_width="70dip"
android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
/>
<!-- minute -->
- <com.android.internal.widget.NumberPicker
+ <NumberPicker
android:id="@+id/minute"
android:layout_width="70dip"
android:layout_height="wrap_content"
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 1d3be14..12b67f1 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -19,8 +19,8 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/toast_frame">
diff --git a/core/res/res/layout/twelve_key_entry.xml b/core/res/res/layout/twelve_key_entry.xml
new file mode 100644
index 0000000..46301cd
--- /dev/null
+++ b/core/res/res/layout/twelve_key_entry.xml
@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is not a standalone element it can be included into apps that need 12-key input -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/one"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/two"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/three"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/four"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/five"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/six"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/seven"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/eight"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/nine"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="64dip"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:orientation="horizontal">
+
+ <Button android:id="@+id/ok"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/ok"
+ />
+
+ <Button android:id="@+id/zero"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginLeft="2dip"
+ android:layout_marginRight="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/cancel"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/two_line_list_item.xml b/core/res/res/layout/two_line_list_item.xml
index 2a2e759..24ba47a 100644
--- a/core/res/res/layout/two_line_list_item.xml
+++ b/core/res/res/layout/two_line_list_item.xml
@@ -18,19 +18,19 @@
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@android:id/text1"
android:textSize="16sp"
android:textStyle="bold"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:id="@android:id/text2"
android:textSize="16sp"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
diff --git a/core/res/res/layout/usb_storage_activity.xml b/core/res/res/layout/usb_storage_activity.xml
new file mode 100644
index 0000000..76c30fd
--- /dev/null
+++ b/core/res/res/layout/usb_storage_activity.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:padding="18dip"
+ >
+
+ <ImageView android:id="@+id/icon"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentTop="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/usb_android" />
+
+ <TextView android:id="@+id/banner"
+ android:layout_centerHorizontal="true"
+ android:layout_below="@id/icon"
+ android:layout_marginTop="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ android:gravity="center"
+ android:text="@string/usb_storage_title" />
+
+ <TextView android:id="@+id/message"
+ android:layout_below="@id/banner"
+ android:layout_marginTop="10dip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:gravity="center"
+ android:text="@string/usb_storage_message" />
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="20dip"
+ >
+
+ <Button android:id="@+id/mount_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="18dip"
+ android:paddingRight="18dip"
+ android:text="@string/usb_storage_button_mount"
+ />
+ <Button android:id="@+id/unmount_button"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="18dip"
+ android:paddingRight="18dip"
+ android:text="@string/usb_storage_stop_button_mount"
+ />
+ <ProgressBar android:id="@+id/progress"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyle"
+ />
+
+ </RelativeLayout>
+</RelativeLayout>
diff --git a/core/res/res/values-cs/donottranslate-cldr.xml b/core/res/res/values-cs/donottranslate-cldr.xml
index e90e53c..a026734 100644
--- a/core/res/res/values-cs/donottranslate-cldr.xml
+++ b/core/res/res/values-cs/donottranslate-cldr.xml
@@ -144,6 +144,6 @@
<string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string>
<string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s. %2$s - %6$s %8$s. %7$s %9$s</string>
<string name="short_format_month">%b</string>
- <string name="full_wday_month_day_no_year">EEEE MMMM d</string>
+ <string name="full_wday_month_day_no_year">EEEE, d. MMMM</string>
<string name="abbrev_wday_month_day_year">E d. MMMM yyyy</string>
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index cec230a..a033e32 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Omezený přístup byl změněn."</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Datová služba je zablokována."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Tísňová linka je zablokována."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Hlasová služba a služba SMS jsou zablokovány."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Veškeré hlasové služby a služby SMS jsou zablokovány."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Hlasová služba je zablokována."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Veškeré hlasové služby jsou zablokovány."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Služby SMS jsou zablokovány."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Hlasové a datové služby jsou zablokovány."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Hlasové služby a služby SMS jsou zablokovány."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Veškeré hlasové a datové služby a služby SMS jsou zablokovány."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Hlas"</string>
<string name="serviceClassData" msgid="872456782077937893">"Data"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Vypnout"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Vypínání..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Váš telefon bude vypnut."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Nejnovější"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Žádné nedávno použité aplikace."</string>
<string name="global_actions" msgid="2406416831541615258">"Možnosti telefonu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Zámek obrazovky"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Umožňuje aplikaci povolit ladÄ›ní jiné aplikace. Å kodlivé aplikace mohou pomocí tohoto nastavení ukonÄit jiné aplikace."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"změna vašeho nastavení uživatelského rozhraní"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Umožňuje aplikaci zmÄ›nit aktuální konfiguraci, napÅ™. národní prostÅ™edí Äi obecnou velikost písma."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"restartování ostatních aplikací"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Umožňuje aplikaci vynutit restartování jiných aplikací."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"aktivovat režim V autě"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Umožňuje aplikaci aktivovat režim V autě."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"ukonÄit procesy na pozadí"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Umožňuje aplikaci ukonÄit procesy jiných aplikací běžící na pozadí i v případÄ›, kdy je dostatek pamÄ›ti."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"vynutit zastavení jiných aplikací"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Umožňuje aplikaci vynutit zastavení jiných aplikací."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"vynucení zavření aplikace"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Umožňuje aplikaci vynutit zavÅ™ení a pÅ™esunutí libovolné Äinnosti v popÅ™edí na pozadí. Běžné aplikace by toto nastavení nemÄ›ly nikdy využívat."</string>
<string name="permlab_dump" msgid="1681799862438954752">"naÄtení interního stavu systému"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Umožňuje zmÄ›nu shromáždÄ›ných statistických údajů o baterii. Není urÄeno pro běžné aplikace."</string>
<string name="permlab_backup" msgid="470013022865453920">"ovládání zálohování a obnovy systému"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Umožňuje aplikaci ovládat systémový mechanizmus pro zálohování a obnovu dat. Není urÄeno pro běžné aplikace."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"zálohování a obnovení dat aplikace"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Umožňuje aplikaci podílet se na systémovém mechanizmu zálohování a obnovení."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"zobrazení nepovolených oken"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Umožňuje vytvoření oken, která mají být použita interním systémem uživatelského rozhraní. Běžné aplikace toto nastavení nepoužívají."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"zobrazení upozornění systémové úrovně"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Umožňuje držiteli vázat se na nejvyšší úroveň rozhraní pro zadávání dat. Běžné aplikace by toto nastavení nikdy neměly využívat."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"vazba na tapetu"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Umožňuje držiteli navázat se na nejvyšší úroveň rozhraní tapety. Běžné aplikace by toto oprávnění nikdy neměly potřebovat."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"komunikovat se správcem zařízení"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Umožňuje držiteli oprávnění odesílat informace správci zařízení. Běžné aplikace by toto oprávnění nikdy neměly požadovat."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"změna orientace obrazovky"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Umožňuje aplikaci kdykoli změnit orientaci obrazovky. Běžné aplikace by toto nastavení nikdy neměly využívat."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"odeslání signálů Linux aplikacím"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Umožňuje aplikaci nainstalovat nové Äi aktualizované balíÄky systému Android. Å kodlivé aplikace mohou pomocí tohoto nastavení pÅ™idat nové aplikace s libovolnými oprávnÄ›ními."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"smazání všech dat v mezipaměti aplikace"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Umožňuje aplikaci uvolnit paměť telefonu smazáním souborů v adresáři mezipaměti aplikace. Přístup je velmi omezený, většinou pouze pro systémové procesy."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Přesun zdrojů aplikace"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Umožňuje aplikaci pÅ™esunout své zdroje z interní pamÄ›ti na externí médium a opaÄnÄ›."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"Ätení systémových souborů protokolu"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Umožňuje aplikaci Äíst různé systémové soubory protokolů. Toto nastavení aplikaci umožní získat obecné informace o Äinnostech s telefonem, ale nemÄ›ly by obsahovat žádné osobní Äi soukromé informace."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Ätení nebo zápis do prostÅ™edků funkce diag"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Umožňuje aplikaci zmÄ›nit informace o vlastníkovi telefonu uložené v telefonu. Å kodlivé aplikace mohou pomocí tohoto nastavení vymazat Äi pozmÄ›nit informace o vlastníkovi."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"Ätení informací o vlastníkovi"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Umožňuje aplikaci Äíst informace o vlastníkovi telefonu uložená v telefonu. Å kodlivé aplikace mohou pomocí tohoto nastavení naÄíst informace o vlastníkovi."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"Ätení dat kalendáře"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"Čtení událostí v kalendáři"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Umožňuje aplikaci naÄíst vÅ¡echny události kalendáře uložené ve vaÅ¡em telefonu. Å kodlivé aplikace poté mohou dalším lidem odeslat události z vaÅ¡eho kalendáře."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"zápis dat kalendáře"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Umožňuje aplikaci zmÄ›nit události kalendáře uložené v telefonu. Å kodlivé aplikace mohou pomocí tohoto nastavení vymazat Äi pozmÄ›nit vaÅ¡e data v kalendáři."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"Přidávání nebo úprava událostí v kalendáři a odesílání e-mailů hostům"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Umožňuje aplikaci přidávat nebo měnit události v kalendáři, které budou odesílat e-maily hostům. Škodlivé aplikace mohou pomocí tohoto oprávnění mazat nebo upravovat události v kalendáři a odesílat e-maily hostům."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"simulace zdrojů polohy pro úÄely testování"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Vytváří simulované zdroje polohy pro úÄely testování. Å kodlivé aplikace mohou pomocí tohoto nastavení zmÄ›nit polohu Äi stav vrácený zdroji skuteÄné polohy, jako je napÅ™. jednotka GPS Äi poskytovatelé sítÄ›."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"přístup k dalším příkazům poskytovatele polohy"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Umožňuje aplikaci pÅ™ipojit Äi odpojit souborové systémy ve vymÄ›nitelných úložiÅ¡tích."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formátovat externí úložiště"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Umožňuje aplikaci formátovat vyměnitelná úložiště."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"získat informace o zabezpeÄeném úložiÅ¡ti"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Umožňuje aplikaci získat informace o zabezpeÄeném úložiÅ¡ti."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"vytvoÅ™it zabezpeÄené úložiÅ¡tÄ›"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Umožňuje aplikaci vytvoÅ™it zabezpeÄené úložiÅ¡tÄ›."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"zniÄit zabezpeÄené úložiÅ¡tÄ›"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Umožňuje aplikaci zniÄit zabezpeÄené úložiÅ¡tÄ›."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"pÅ™ipojit nebo odpojit zabezpeÄené úložiÅ¡tÄ›"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Umožňuje aplikaci pÅ™ipojit nebo odpojit zabezpeÄené úložiÅ¡tÄ›."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"pÅ™ejmenovat zabezpeÄené úložiÅ¡tÄ›"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Umožňuje aplikaci pÅ™ejmenovat zabezpeÄeného úložiÅ¡tÄ›."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"ovládání vibrací"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Umožňuje aplikaci ovládat vibrace."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Umožňuje aplikaci nastavit nápovědu pro velikost tapety systému."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"obnovení továrního nastavení systému"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Umožňuje aplikaci kompletně obnovit systém do továrního nastavení a vymazat všechna data, konfiguraci a nainstalované aplikace."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"nastavit Äas"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Umožňuje aplikaci zmÄ›nit Äas hodin v telefonu."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"nastavení Äasového pásma"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Umožňuje aplikaci zmÄ›nit Äasové pásmo telefonu."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"role služby AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"zápis nastavení názvu přístupového bodu (APN)"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Umožňuje aplikaci zmÄ›nit nastavení APN, jako je například proxy Äi port APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"změna připojení k síti"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Umožňuje aplikaci změnit stav připojení k síti."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Umožňuje aplikaci změnit stav připojení k síti."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Změna sdíleného datového připojení"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Umožňuje aplikaci změnit stav sdíleného datového připojení k síti."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"změnit nastavení použití dat na pozadí"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Umožňuje aplikaci změnit nastavení použití dat na pozadí."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"zobrazení stavu WiFi"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Umožní aplikaci zapisovat nová slova do uživatelského slovníku."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"změna/smazání obsahu karty SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Umožní aplikaci zápis na kartu SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"přistupovat do souborového systému mezipaměti"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Umožňuje aplikaci Äíst a zapisovat do souborového systému mezipamÄ›ti."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Omezení hesla"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Omezuje typ hesel, která lze použít."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Sledování pokusů o přihlášení"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Sleduje nezdaÅ™ené pokusy o pÅ™ihlášení do zařízení a umožňuje provedení urÄité akce."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Obnovení hesla"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Vynutí nastavení hesla na novou hodnotu, kterou vám před přihlášením musí sdělit správce."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Vynucení uzamÄení"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"UrÄuje, kdy dojde k uzamÄení zařízení a bude požadováno opÄ›tovné zadání hesla."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Vymazání všech dat"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Bez dalšího potvrzení obnoví výchozí nastavení z výroby a smaže všechna data."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Domů"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"pomocí <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> pomocí <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Zadejte kód PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Zadejte heslo pro odblokování"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Nesprávný kód PIN"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Číslo tísňové linky"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Telefon odemknete stisknutím tlaÄítka Menu."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Odblokujte pomocí gesta"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Tísňové volání"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zavolat zpět"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Správně!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Zkuste to prosím znovu"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Nabíjení (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Odemknout"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Zapnout zvuk"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Vypnout zvuk"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Vymazat"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umožňuje aplikaci Äíst vÅ¡echny navÅ¡tívené adresy URL a záložky ProhlížeÄe."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zápis do historie a záložek ProhlížeÄe"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Umožní aplikaci zmÄ›nit historii Äi záložky prohlížeÄe uložené v telefonu. Å kodlivé aplikace mohou pomocí tohoto nastavení vymazat Äi pozmÄ›nit data ProhlížeÄe."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"ZmÄ›nit oprávnÄ›ní prohlížeÄe poskytovat informace o zemÄ›pisné poloze"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Umožňuje aplikaci zmÄ›nit oprávnÄ›ní prohlížeÄe poskytovat informace o zemÄ›pisné poloze. Å kodlivé aplikace mohou toto nastavení použít k odesílání informací o umístÄ›ní na libovolné webové stránky."</string>
<string name="save_password_message" msgid="767344687139195790">"Chcete, aby si prohlížeÄ zapamatoval toto heslo?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nyní ne"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamatovat"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"před 1 hodinou"</item>
<item quantity="other" msgid="2467273239587587569">"před <xliff:g id="COUNT">%d</xliff:g> hod."</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Posledních <xliff:g id="COUNT">%d</xliff:g> dnů"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Poslední měsíc"</string>
+ <string name="older" msgid="5211975022815554840">"Starší"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"vÄera"</item>
<item quantity="other" msgid="2479586466153314633">"před <xliff:g id="COUNT">%d</xliff:g> dny"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"týd."</string>
<string name="year" msgid="4001118221013892076">"rokem"</string>
<string name="years" msgid="6881577717993213522">"lety"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Každý pracovní den (Po – Pá)"</string>
- <string name="daily" msgid="5738949095624133403">"DennÄ›"</string>
- <string name="weekly" msgid="983428358394268344">"Každý týden v <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"MÄ›síÄnÄ›"</string>
- <string name="yearly" msgid="1519577999407493836">"RoÄnÄ›"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Video nelze přehrát"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Omlouváme se, ale toto video nelze přenášet datovým proudem do tohoto zařízení."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Toto video bohužel nelze přehrát."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"UkonÄit aplikaci"</string>
<string name="report" msgid="4060218260984795706">"Nahlásit"</string>
<string name="wait" msgid="7147118217226317732">"PoÄkat"</string>
- <string name="debug" msgid="9103374629678531849">"Ladit"</string>
<string name="sendText" msgid="5132506121645618310">"Vyberte Äinnost s textem"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Hlasitost vyzvánění"</string>
<string name="volume_music" msgid="5421651157138628171">"Hlasitost médií"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Nejsou vyžadována žádná oprávnění"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Skrýt"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Zobrazit vše"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"NaÄítání..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Úložiště USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB připojeno"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"PÅ™ipojili jste svůj telefon k poÄítaÄi pomocí USB. Vyberte možnost PÅ™ipojit, chcete-li zkopírovat soubory mezi vaším poÄítaÄem a kartou SD v telefonu."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Připojit"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Nepřipojovat"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"PÅ™ipojili jste svůj telefon k poÄítaÄi pomocí USB. Chcete-li kopírovat soubory z poÄítaÄe na kartu SD v zařízení Android Äi obrácenÄ›, vyberte následující tlaÄítko."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Zapnout úložiště USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Při používání vaší karty SD jako úložiště USB došlo k problému."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB připojeno"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Vyberte, chcete-li kopírovat soubory do nebo z poÄítaÄe."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Vypnout úložiště USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Vyberte, chcete-li vypnout úložiště USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Vypnout úložiště USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"PÅ™ed vypnutím úložiÅ¡tÄ› USB se pÅ™esvÄ›dÄte, zda byl hostitel USB odpojen. ÚložiÅ¡tÄ› USB vypnete volbou Vypnout."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Vypnout"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Zrušit"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Při vypínání úložiště USB došlo k problémům. Zkontrolujte, zda byl hostitel USB odpojen, a zkuste to znovu."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Úložiště USB je používáno"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"PÅ™ed vypnutím úložiÅ¡tÄ› USB zkontrolujte, zda jste odpojili (vyjmuli) kartu SD zařízení Android z poÄítaÄe."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Vypnout úložiště USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Při vypínání úložiště USB došlo k problémům. Zkontrolujte, zda byl hostitel USB odpojen, a zkuste to znovu."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Zapnout úložiště USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Pokud zapnete úložiště USB, dojde k zastavení některých používaných aplikací. Tyto aplikace pravděpodobně nebudou k dispozici až do vypnutí úložiště USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Chyba operace na rozhraní USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formátovat kartu SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Opravdu chcete kartu SD naformátovat? Všechna data na kartě budou ztracena."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formátovat"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Nebyly nalezeny žádné odpovídající aktivity."</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aktualizovat statistiku použití souÄástí"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Umožňuje zmÄ›nu shromáždÄ›ných statistických údajů o použití souÄástí. Není urÄeno pro běžné aplikace."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Umožňuje volat výchozí službu kontejneru ke zkopírování obsahu. Běžné aplikace by toto oprávnění neměly používat."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Umožňuje volat výchozí službu kontejneru ke zkopírování obsahu. Běžné aplikace by toto oprávnění neměly používat."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Poklepáním můžete ovládat přiblížení"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Chyba při spouštění widgetu"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Přejít"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Vytvořit kontakt"\n"pro <xliff:g id="NUMBER">%s</xliff:g>."</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"Zaškrtnuto"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"Nezaškrtnuto"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Uvedené aplikace od <xliff:g id="APPLICATION">%2$s</xliff:g> požadují oprávnÄ›ní pÅ™istupovat k pÅ™ihlaÅ¡ovacím údajům úÄtu <xliff:g id="ACCOUNT">%1$s</xliff:g>. Chcete toto oprávnÄ›ní udÄ›lit? Pokud je udÄ›líte, vaÅ¡e odpovÄ›Ä se uloží a tato výzva se již nebude zobrazovat."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Uvedené aplikace požadují od <xliff:g id="APPLICATION">%3$s</xliff:g> oprávnÄ›ní pÅ™istupovat k pÅ™ihlaÅ¡ovacím údajům (typ: <xliff:g id="TYPE">%1$s</xliff:g>) úÄtu <xliff:g id="ACCOUNT">%2$s</xliff:g>. Chcete toto oprávnÄ›ní udÄ›lit? Pokud je udÄ›líte, vaÅ¡e odpovÄ›Ä se uloží a tato výzva se již nebude zobrazovat."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Následující aplikace požadují oprávnÄ›ní k přístupu do vaÅ¡eho úÄtu (nyní i v budoucnu)."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Chcete tento požadavek povolit?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Požadavek na přístup"</string>
<string name="allow" msgid="7225948811296386551">"Povolit"</string>
<string name="deny" msgid="2081879885755434506">"Odepřít"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Požadováno oprávnění"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokol L2TP (Layer 2 Tunneling Protocol)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Síť VPN L2TP/IPSec s pÅ™edsdíleným klíÄem"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Síť VPN L2TP/IPSec s certifikátem"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
+ <string name="reset" msgid="2448168080964209908">"Resetovat"</string>
+ <string name="submit" msgid="1602335572089911941">"Odeslat"</string>
+ <string name="description_star" msgid="2654319874908576133">"oblíbené"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Aktivován režim V autě"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vyberte, chcete-li ukonÄit režim Na cestÄ›."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Sdílení je aktivní"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Dotykem zahájíte konfiguraci"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Vysoké využití mobilních dat"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotykem zobrazíte další informace o využití mobilních dat"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Byl pÅ™ekroÄen limit mobilních dat"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Dotykem zobrazíte další informace o využití mobilních dat"</string>
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 0ea959f..ab4a7c7 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1,21 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="byteShort" msgid="8340973892742019101">"B"</string>
+ <string name="byteShort" msgid="8340973892742019101">"b"</string>
<string name="kilobyteShort" msgid="5973789783504771878">"Kb"</string>
<string name="megabyteShort" msgid="6355851576770428922">"Mb"</string>
<string name="gigabyteShort" msgid="3259882455212193214">"Gb"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Begrænset adgang ændret"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Datatjenesten er blokeret."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Nødtjenesten er blokeret."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Stemme-/sms-tjenesten er blokeret."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle stemme-/sms-tjenester er blokerede."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Stemmetjenesten er blokeret."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Alle stemmetjenester er blokerede."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Sms-tjenesten er blokeret."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Stemme-/datatjenester er blokerede."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Stemme-/sms-tjenester er blokerede."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Alle stemme-/data/sms-tjenester er blokerede."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Stemme"</string>
<string name="serviceClassData" msgid="872456782077937893">"Data"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Sluk"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Lukker ned ..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"lydstyrke for opkald"</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Seneste"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Der er ingen nye programmer."</string>
<string name="global_actions" msgid="2406416831541615258">"Indstillinger for telefon"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skærmlås"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillader, at et program slår fejlretning af andet program til. Ondsindede programmer kan bruge dette til at standse andre programmer."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"skift indstillinger for brugergrænsefladen"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillader, at et program ændrer den nuværende konfiguration, f.eks. den lokale eller overordnede skrifttypestørrelse."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"genstart andre programmer"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Tillader, at et program tvangsgenstarter andre programmer."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"aktivere biltilstand"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Tillader et program at aktivere biltilstand."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"standse baggrundsprocesser"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Tillader et program at standse andre programmers baggrundsprocesser, selvom der ikke er mangel på hukommelse."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"tvangsstandse andre programmer"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Tillader, at et program tvangsstopper andre programmer."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"tving programmet til at lukke"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Tillader, at et program tvinger alle programmer, der er i forgrunden, til at lukke og køre i baggrunden. Bør aldrig være nødvendigt til normale programmer."</string>
<string name="permlab_dump" msgid="1681799862438954752">"hent intern systemtilstand"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Tillader ændring af indsamlede batteristatistikker. Ikke til brug for normale programmer."</string>
<string name="permlab_backup" msgid="470013022865453920">"kontroller sikkerhedskopiering af system, og gendan"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Tillader, at et program kontrollerer systemets sikkerhedskopierings- og gendannelsesfunktion. Ikke til brug til normale programmer."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"sikkerhedskopier og gendan programmets data"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillader, at et program deltager i systemets sikkerhedskopierings- og gendannelsesfunktion."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"vis uautoriserede vinduer"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillader oprettelse af vinduer, der er beregnet til at blive brugt af den interne systembrugergrænseflade. Ikke til brug for normale programmer."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"vis underretninger på systemniveau"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Tillader, at brugeren forpligter sig til en inputmetodes grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"forpligt til et tapet"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Tillader, at brugeren forpligter sig til et tapets grænseflade på øverste niveau. Bør aldrig være nødvendig til normale programmer."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"kommunikere med en enhedsadministrator"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Tillader brugeren at sende hensigter til en enhedsadministrator. Bør aldrig være nødvendigt for almindelige programmer."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"skift skærmretning"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Tillader, at et program ændrer rotationen af skærmen når som helst. Bør aldrig være nødvendigt til normale programmer."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"send Linux-signaler til programmer"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Tillader, at et program installerer nye eller opdaterede Android-pakker. Ondsindede programmer kan bruge dette til at tilføje nye programmer med vilkårlige, effektive tilladelser."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"slet alle cachedata for programmet"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillader, at et program frigør plads på telefonen ved at slette filer i programmets cachemappe. Adgang er normalt meget begrænset til systemprocesser."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Flyt programressourcer"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Tillader, at et program flytter programressourcer fra interne til eksterne medier og omvendt."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"læs systemlogfiler"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Tillader, at et program læser fra systemets forskellige logfiler. Dermed kan generelle oplysninger om, hvad du laver med telefonen, registreres, men logfilerne bør ikke indeholde personlige eller private oplysninger."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"læs/skriv til ressourcer ejet af diag"</string>
@@ -273,13 +289,13 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader, at et program ændrer rtelefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"læs ejerdata"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader, at et program læser telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"læs kalenderdata"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"læs kalenderbegivenheder"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader, at et program læser alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"skriv kalenderdata"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Tillader, at et program ændrer de kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kalenderdata."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"tilføj eller rediger kalenderbegivenheder, og send e-mail til gæster"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Tillader, at et program tilføjer eller ændrer begivenhederne i din kalender, hvilket kan sende e-mail til gæster. Ondsindede programmer kan bruge dette til at slette eller ændre dine kalenderbegivenheder eller til at sende e-mail til gæster."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"imiterede placeringskilder til test"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Opret imiterede placeringskilder til testning. Ondsindede programmer kan bruge dette til at tilsidesætte den returnerede placering og/eller status fra rigtige placeringskilder som f.eks. GPS eller netværksudbydere."</string>
- <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få adgang til ekstra kommandoer for placeringsudbyder"</string>
+ <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få adgang til yderligere kommandoer for placeringsudbyder"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"FÃ¥ adgang til ekstra kommandoer fra placeringsudbyder. Ondsindede programmer kan bruge dette til at gribe ind i driften af GPS\'en eller andre placeringskilder."</string>
<string name="permlab_installLocationProvider" msgid="6578101199825193873">"tilladelse til at installere en placeringsudbyder"</string>
<string name="permdesc_installLocationProvider" msgid="5449175116732002106">"Opret imiterede placeringskilder til testning. Ondsindede programmer kan bruge dette til at tilsidesætte den returnerede placering og/eller status fra rigtige placeringskilder som f.eks. GPS eller netværksudbydere eller til at overvåge og rapportere din placering til en ekstern kilde."</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Tillader, at programmet monterer eller demonterer filsystemer til flytbar lagring."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formater ekstern lagring"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Tillader, at et program formaterer flytbart lager."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"hente oplysninger om sikkert lager"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Tillader programmet at få oplysninger om sikkert lager."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"opret sikkert lager"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Tillader programmet at oprette sikkert lager."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"ødelægge sikkert lager"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Tillader programmet at ødelægge sikkert lager."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"montere/demontere sikkert lager"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Tillader programmet at montere/demontere sikkert lager."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"omdøbe sikkert lager"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Tillader programmet at omdøbe sikkert lager."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"kontroller vibrator"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Lader programmet kontrollere vibratoren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Tillader, at programmet opsætter størrelsestip for systemets tapet."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"nulstil system til fabriksstandarder"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Tillader, at et program nulstiller systemet fuldstændig til fabriksindstillingerne, sletter alle data, konfigurationer og installerede programmer."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"angive tid"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Tillader, at et program ændrer telefonens klokkeslæt."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"angiv tidszone"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Tillader, at et program ændrer telefonens tidszone."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"fungerer som kontoadministrationstjeneste"</string>
@@ -353,12 +381,14 @@
<string name="permdesc_useCredentials" msgid="7416570544619546974">"Tillader, at et program anmoder om godkendelsestokens"</string>
<string name="permlab_accessNetworkState" msgid="6865575199464405769">"vis netværkstilstand"</string>
<string name="permdesc_accessNetworkState" msgid="558721128707712766">"Tillader, at et program viser tilstanden for alle netværk."</string>
- <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"Fuld internetadgang"</string>
+ <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"fuld internetadgang"</string>
<string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Tillader, at et program opretter netværks-sockets."</string>
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"skriv indstillinger for adgangspunktnavn"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Tillader, at et program ændrer APN-indstillingerne, f.eks. enhver APNs Proxy og Port."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"skift netværksforbindelse"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Tillader, at et program ændrer netværksforbindelsens tilstand."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Tillader, at et program ændrer netværksforbindelsens tilstand."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Skift tethering-forbindelse"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Tillader, at et program ændrer tilstand for et bundet netværk."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"skift brugerindstilling for baggrundsdata"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Tillader, at et program ændrer brugerindstillingerne for baggrundsdata."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"vis Wi-Fi-tilstand"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillader, at et program skriver nye ord i brugerordbogen."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"ret/slet indholdet på SD-kortet"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillader, at et program skriver til SD-kortet."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"få adgang til cache-filsystemet"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillader, at et program læser og skriver til cache-filsystemet."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Begræns adgangskode"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begræns de adgangskodetyper, du har tilladelse til at bruge."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Vis forsøg på at logge ind"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Overvåg mislykkede forsøg på at logge ind på enheden for at foretage en handling."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Nulstil adgangskode"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Tving din adgangskode til en ny værdi. Dette kræver, at administratoren giver den til dig, før du kan kogge ind."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Tvangslås"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrol når enheden låses, så du skal indtaste adgangskoden igen."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Slet alle data"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Foretag en fabriksnulstilling, der sletter alle dine data uden bekræftelse."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hjem"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Indtast PIN-kode"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Indtast adgangskode for at låse op"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Forkert PIN-kode!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Tryk på Menu og dernæst på 0 for at låse op."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryk på Menu for at låse op."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Tegn oplåsningsmønster"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Nødopkald"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbage til opkald"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Rigtigt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager! Prøv igen"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Oplader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"LÃ¥s op"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Lyd slået til"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Lyd slået fra"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ryd"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillader, at programmet læser alle de webadresser, browseren har besøgt, og alle browserens bogmærker."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriv browserens oversigt og bogmærker"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillader, at et program ændrer browseroversigten eller bogmærker, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre din browsers data."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Skift browsertilladelser for geografisk placering"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Giver et program tilladelse til at ændre browserens tilladelser for geografisk placering. Skadelige programmer kan bruge dette til at tillade, at placeringsoplysninger sendes til vilkårlige websteder."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du, at browseren skal huske denne adgangskode?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"For 1 time siden"</item>
<item quantity="other" msgid="2467273239587587569">"For <xliff:g id="COUNT">%d</xliff:g> timer siden"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Seneste <xliff:g id="COUNT">%d</xliff:g> dage"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Seneste måned"</string>
+ <string name="older" msgid="5211975022815554840">"Ældre"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"i går"</item>
<item quantity="other" msgid="2479586466153314633">"For <xliff:g id="COUNT">%d</xliff:g> dage siden"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"uger"</string>
<string name="year" msgid="4001118221013892076">"Ã¥r"</string>
<string name="years" msgid="6881577717993213522">"Ã¥r"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Hverdage (man.-fre.)"</string>
- <string name="daily" msgid="5738949095624133403">"Dagligt"</string>
- <string name="weekly" msgid="983428358394268344">"Ugentlig hver <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"MÃ¥nedligt"</string>
- <string name="yearly" msgid="1519577999407493836">"Ã…rligt"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Videoen kan ikke afspilles"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Beklager! Denne video er ikke gyldig til streaming på denne enhed."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Beklager! Denne video kan ikke afspilles."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Tving til at lukke"</string>
<string name="report" msgid="4060218260984795706">"Rapporter"</string>
<string name="wait" msgid="7147118217226317732">"Vent"</string>
- <string name="debug" msgid="9103374629678531849">"Fejlretning"</string>
<string name="sendText" msgid="5132506121645618310">"Vælg en handling for teksten"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Lydstyrke for opkald"</string>
<string name="volume_music" msgid="5421651157138628171">"Lydstyrke for medier"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Der kræves ingen tilladelser"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Skjul"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Vis alle"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Indlæser ..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masselagring"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB er tilsluttet"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Du har forbundet din telefon til din computer via USB. Vælg \"Monter\", hvis du ønsker at kopiere filer mellem din computer og din telefons SD-kort."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Monter"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Indsæt ikke"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Du har forbundet din telefon til din computer via USB. Vælg knappen nedenfor, hvis du ønsker at kopiere filer mellem din computer og din Androids SD-kort."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Slå USB-lagringen til"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Der opstod et problem med at bruge dit SD-kort til USB-lagring."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB er tilsluttet"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Vælg for at kopiere filer til/fra din computer."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Slå USB-lagringen fra"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Vælg for at slå USB-lagring fra."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Slå USB-lagring fra"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Inden du slår USB-lagringen fra, skal du sørge for, at USB-værten er demonteret. Vælg \"Slå fra\" for at slå USB-lagringen fra."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Slå fra"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Annuller"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Der opstod et problem med at slå USB-lagringen fra. Sørg for, at du har demonteret USB-værten, og prøv så igen."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lager i brug"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Sørg for, at du har demonteret (\"udskubbet\") din Androids SD-kort fra computeren, før du slår USB-lagring fra."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Slå USB-lagring fra"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Der opstod et problem med at slå USB-lagringen fra. Sørg for, at du har demonteret USB-værten, og prøv så igen."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Slå USB-lagring til"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Hvis du slår USB-lagring til, vil nogle af de programmer, som du bruger, stoppe, og de kan være utilgængelige, indtil du slår USB-lagring til igen."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB-handlingen mislykkedes"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formater SD-kort"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på, du ønsker at formatere SD-kortet? Alle data på kortet mistes."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formater"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Der blev ikke fundet nogen matchende aktiviteter"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"opdater brugerstatistikker for komponenter"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillader ændring af indsamlede brugerstatistikker for komponenter. Ikke til brug til normale programmer."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Tillader kald af standardcontainertjeneste for at kopiere indhold. Ikke til brug for almindelige programmer."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Tillader kald af standardcontainertjeneste for at kopiere indhold. Ikke til brug for almindelige programmer."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tryk to gange for zoomkontrol"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Der opstod en fejl under forøgelsen af widgetten"</string>
<string name="ime_action_go" msgid="8320845651737369027">"GÃ¥"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Opret kontakt"\n"ved hjælp af <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"kontrolleret"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke kontrolleret"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De nævnte programmer beder om tilladelse til at få adgang til loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Ønsker du at give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De nævnte programmer beder om tilladelse til at få adgang til <xliff:g id="TYPE">%1$s</xliff:g>-loginoplysningerne til kontoen <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du give denne tilladelse? Hvis ja, så huskes dit svar, og du vil ikke blive spurgt om det igen."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Følgende programmer anmoder om tilladelse til at få adgang til din konto nu og i fremtiden."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Vil du tillade denne anmodning?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Adgangsanmodning"</string>
<string name="allow" msgid="7225948811296386551">"Tillad"</string>
<string name="deny" msgid="2081879885755434506">"Afvis"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Der er anmodet om tilladelse"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN baseret på forhåndsdelt nøglekodning"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatbaseret L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
+ <string name="reset" msgid="2448168080964209908">"Nulstil"</string>
+ <string name="submit" msgid="1602335572089911941">"Send"</string>
+ <string name="description_star" msgid="2654319874908576133">"favorit"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Biltilstand er aktiveret"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vælg for at afslutte biltilstand."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Tethering aktiveret"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Tryk for at konfigurere"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Højt mobildataforbrug"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryk for oplysninger om brug af mobildata"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Grænsen for mobildata er overskredet"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Tryk for oplysninger om brug af mobildata"</string>
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 353bc3b..8539d51 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Eingeschränkter Zugriff geändert"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Daten-Dienst ist gesperrt."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Notruf ist gesperrt."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Sprach-/SMS-Dienst ist gesperrt."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle Sprach-/SMS-Dienste sind gesperrt."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Sprachdienst ist gesperrt."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Alle Sprachdienste sind gesperrt."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMS-Dienst ist gesperrt."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Sprach-/Datendienste sind gesperrt."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Sprach-/SMS-Dienste sind gesperrt."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Alle Sprach-/Daten-/SMS-Dienste sind gesperrt."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Sprachnotiz"</string>
<string name="serviceClassData" msgid="872456782077937893">"Daten"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,13 +134,14 @@
<string name="power_off" msgid="4266614107412865048">"Ausschalten"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Herunterfahren..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Ihr Telefon wird heruntergefahren."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Neueste"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Keine neuen Anwendungen."</string>
<string name="global_actions" msgid="2406416831541615258">"Telefonoptionen"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bildschirmsperre"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Ausschalten"</string>
<string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Lautlos"</string>
- <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Ton ist bereits AUS"</string>
- <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Ton ist momentan AN"</string>
+ <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Ton ist AUS"</string>
+ <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Ton ist AN"</string>
<string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Flugmodus"</string>
<string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Flugmodus ist AN"</string>
<string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Flugmodus ist AUS"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Ermöglicht einer Anwendung, die Fehlerbeseitigung für eine andere Anwendung zu aktivieren. Schädliche Anwendungen können so andere Anwendungen löschen."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI-Einstellungen ändern"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Ermöglicht einer Anwendung, die aktuelle Konfiguration zu ändern, etwa das Gebietsschema oder die Schriftgröße."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"Andere Anwendungen neu starten"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Ermöglicht einer Anwendung, den Neustart anderer Anwendungen zu erzwingen."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"Automodus aktivieren"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Ermöglicht einer Anwendung, den Automodus zu aktivieren."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"Hintergrundprozesse beenden"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Ermöglicht einer Anwendung, Hintergrundprozesse anderer Anwendungen auch bei ausreichendem Speicher zu beenden."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"Beenden anderer Anwendungen erzwingen"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Ermöglicht einer Anwendung, das Beenden anderer Anwendungen zu erzwingen."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"Schließen von Anwendung erzwingen"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Ermöglicht einer Anwendung, alle Aktivitäten, die im Vordergrund ablaufen, zu beenden und in den Hintergrund zu schieben. Sollte nicht für normale Anwendungen benötigt werden."</string>
<string name="permlab_dump" msgid="1681799862438954752">"Systeminternen Status abrufen"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Ermöglicht die Änderung von gesammelten Akku-Daten. Nicht für normale Anwendungen vorgesehen."</string>
<string name="permlab_backup" msgid="470013022865453920">"Systemsicherung und -wiederherstellung kontrollieren"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Der Anwendung wird die Steuerung des Sicherungs- und Wiederherstellungsmechanismus des Systems ermöglicht. Nicht für normale Anwendungen vorgesehen."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"Anwendungsdaten sichern und wiederherstellen"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Der Anwendung wird die Teilnahme am Sicherungs- und Wiederherstellungsmechanismus des Systems ermöglicht."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"nicht autorisierte Fenster anzeigen"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Ermöglicht die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Anwendungen geeignet."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"Warnungen auf Systemebene anzeigen"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"An ein Hintergrundbild binden"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"Interaktion mit einem Geräteadministrator"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Ermöglicht dem Halter, Intents an einen Geräteadministrator zu senden. Sollte nie für normale Anwendungen benötigt werden."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"Bildschirmausrichtung ändern"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Ermöglicht der Anwendung, die Bildschirmdrehung jederzeit zu ändern. Sollte nicht für normale Anwendungen benötigt werden."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"Linux-Signale an Anwendungen senden"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Ermöglicht einer Anwendung, neue oder aktualisierte Android-Pakete zu installieren. Schädliche Anwendungen können so neue Anwendungen mit beliebig umfangreichen Berechtigungen hinzufügen."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"Alle Cache-Daten der Anwendung löschen"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Ermöglicht einer Anwendung, Telefonspeicher durch das Löschen von Dateien im Cache-Verzeichnis der Anwendung freizugeben. Der Zugriff beschränkt sich in der Regel auf Systemprozesse."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Anwendungsressourcen verschieben"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Ermöglicht einer Anwendung, Anwendungsressourcen von interne auf externe Medien zu verschieben und umgekehrt."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"System-Protokolldateien lesen"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Ermöglicht einer Anwendung, die verschiedenen Protokolldateien des Systems zu lesen. So können allgemeine Informationen zu den auf Ihrem Telefon durchgeführten Aktionen eingesehen werden, diese sollten jedoch keine persönlichen oder geheimen Daten enthalten."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Lese-/Schreibberechtigung für zu Diagnosegruppe gehörige Elemente"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu ändern. Schädliche Anwendungen können so Eigentümerdaten löschen oder verändern."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"Eigentümerdaten lesen"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu lesen. Schädliche Anwendungen können so Eigentümerdaten lesen."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"Kalenderdaten lesen"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"Kalendereinträge lesen"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kalenderereignisse zu lesen. Schädliche Anwendungen können so Ihre Kalenderereignisse an andere Personen senden."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"Kalenderdaten schreiben"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kalenderereignisse zu ändern. Schädliche Anwendungen können so Ihre Kalenderdaten löschen oder verändern."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"Kalendereinträge hinzufügen oder ändern und E-Mails an Gäste senden"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Ermöglicht einer Anwendung, Einträge auf Ihrem Kalender hinzuzufügen oder zu ändern, die E-Mails an Gäste senden können. Schädliche Anwendungen können so Ihre Kalenderdaten löschen oder verändern oder E-Mails versenden."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"Falsche Standortquellen für Testzwecke"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Erstellt falsche Standortquellen für Testzwecke. Schädliche Anwendungen können so den von den echten Standortquellen wie GPS oder Netzwerkanbieter zurückgegebenen Standort und/oder Status überschreiben."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"Auf zusätzliche Dienstanbieterbefehle für Standort zugreifen"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Ermöglicht der Anwendung, Dateisysteme für austauschbare Speicherplätze bereitzustellen oder die Bereitstellung aufzuheben."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"Externen Speicher formatieren"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Erlaubt der Anwendung, austauschbaren Speicher zu formatieren."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"Informationen zum sicheren Speicher abrufen"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Ermöglicht der Anwendung, Informationen zum sicheren Speicher abzurufen."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"Sicheren Speicher erstellen"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Ermöglicht der Anwendung, einen sicheren Speicher zu erstellen."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"Sicheren Speicher entfernen"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Ermöglicht der Anwendung, den sicheren Speicher zu entfernen."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"Sicheren Speicher bereitstellen/trennen"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Ermöglicht der Anwendung, sicheren Speicher bereitzustellen bzw. zu trennen."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"Sicheren Speicher umbenennen"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Ermöglicht der Anwendung, den sicheren Speicher umzubenennen."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"Vibrationsalarm steuern"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Ermöglicht der Anwendung, den Vibrationsalarm zu steuern."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"Lichtanzeige steuern"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für das Hintergrundbild festzulegen."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"System auf Werkseinstellung zurücksetzen"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Ermöglicht einer Anwendung, das System komplett auf Werkseinstellung zurückzusetzen. Hierbei werden alle Daten, Konfigurationen und installierten Anwendungen gelöscht."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"Zeit einstellen"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Ermöglicht einer Anwendung, die Uhrzeit des Telefons zu ändern."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"Zeitzone festlegen"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Ermöglicht einer Anwendung, die Zeitzone des Telefons zu ändern."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"Als Konto-Manager fungieren"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"Einstellungen für Zugriffspunktname schreiben"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Ermöglicht einer Anwendung, die APN-Einstellungen wie Proxy und Port eines Zugriffspunkts zu ändern."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"Netzwerkkonnektivität ändern"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Ermöglicht einer Anwendung, den Status der Netzwerkkonnektivität zu ändern."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Ermöglicht einer Anwendung, den Status der Netzwerkkonnektivität zu ändern."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Tethering-Konnektivität ändern"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Ermöglicht einer Anwendung, den Status der Tethering-Konnektivität zu ändern."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"Einstellung zur Verwendung von Hintergrunddaten ändern"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Ermöglicht einer Anwendung, die Einstellung der Verwendung von Hintergrunddaten zu ändern."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"WLAN-Status anzeigen"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Erlaubt einer Anwendung, neue Wörter in das Wörterbuch des Nutzers zu schreiben."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD-Karten-Inhalt ändern/löschen"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Ermöglicht einer Anwendung, auf die SD-Karte zu schreiben"</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Zugriff auf das Cache-Dateisystem"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Gewährt einer Anwendung Lese- und Schreibzugriff auf das Cache-Dateisystem."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Passwort beschränken"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Beschränken der erlaubten Passworttypen"</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Anmeldeversuche überwachen"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Fehlgeschlagene Versuche·zum Anmelden/Durchführen einer Aktion überwachen"</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Passwort zurücksetzen"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Festlegen eines neuen Werts für Ihr Passwort, sodass der Administrator es Ihnen vor dem Anmelden übermitteln muss."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Sperren erzwingen"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Steuerung der Gerätesperre; erfordert die erneute Passworteingabe"</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Alle Daten löschen"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Zurücksetzen auf die Werkseinstellungen. Dabei werden alle Ihre Daten ohne Nachfrage gelöscht."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Privat"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-Code eingeben"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Passwort zum Entsperren eingeben"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Falscher PIN-Code!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Drücken Sie zum Entsperren die Menütaste und dann auf \"0\"."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Notrufnummer"</string>
@@ -494,14 +537,15 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Zum Entsperren die Menütaste drücken"</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Schema für Entsperrung zeichnen"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Notruf"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zurück zum Anruf"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tut uns leid. Versuchen Sie es noch einmal."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Wird geladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Aufgeladen"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
<string name="lockscreen_low_battery" msgid="1482873981919249740">"Bitte Ladegerät anschließen"</string>
- <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Keine SIM-Karte."</string>
- <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Keine SIM-Karte im Telefon."</string>
+ <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Keine SIM-Karte"</string>
+ <string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Keine SIM-Karte im Telefon"</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"Bitte legen Sie eine SIM-Karte ein."</string>
<string name="emergency_calls_only" msgid="6733978304386365407">"Nur Notrufe"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"Netzwerk gesperrt"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Entsperren"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Ton ein"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Ton aus"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Löschen"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Ermöglicht der Anwendung, alle URLs, die mit dem Browser besucht wurden, sowie alle Lesezeichen des Browsers zu lesen."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Browserverlauf und Lesezeichen schreiben"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Ermöglicht einer Anwendung, den auf Ihrem Telefon gespeicherten Browserverlauf und die Lesezeichen zu ändern. Schädliche Anwendungen können so Ihre Browserdaten löschen oder ändern."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Geolokalisierungsberechtigungen des Browsers ändern"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Ermöglicht einer Anwendung, die Geolokalisierungsberechtigungen des Browsers zu ändern. Schädliche Anwendungen können dies nutzen, um das Senden von Standortinformationen an willkürliche Websites zuzulassen."</string>
<string name="save_password_message" msgid="767344687139195790">"Möchten Sie, dass der Browser dieses Passwort speichert?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nicht jetzt"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Speichern"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"Vor 1 Stunde"</item>
<item quantity="other" msgid="2467273239587587569">"Vor <xliff:g id="COUNT">%d</xliff:g> Stunden"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Letzte <xliff:g id="COUNT">%d</xliff:g> Tage"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Letzter Monat"</string>
+ <string name="older" msgid="5211975022815554840">"Älter"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"Gestern"</item>
<item quantity="other" msgid="2479586466153314633">"Vor <xliff:g id="COUNT">%d</xliff:g> Tagen"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"Wochen"</string>
<string name="year" msgid="4001118221013892076">"Jahr"</string>
<string name="years" msgid="6881577717993213522">"Jahre"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Jeden Wochentag (Mo-Fr)"</string>
- <string name="daily" msgid="5738949095624133403">"Täglich"</string>
- <string name="weekly" msgid="983428358394268344">"Jede Woche am <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Monatlich"</string>
- <string name="yearly" msgid="1519577999407493836">"Jährlich"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Video kann nicht wiedergegeben werden."</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Leider ist dieses Video nicht für Streaming auf diesem Gerät gültig."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Dieses Video kann leider nicht abgespielt werden."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string>
<string name="report" msgid="4060218260984795706">"Bericht"</string>
<string name="wait" msgid="7147118217226317732">"Warten"</string>
- <string name="debug" msgid="9103374629678531849">"Fehler suchen"</string>
<string name="sendText" msgid="5132506121645618310">"Aktion für Text auswählen"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Klingeltonlautstärke"</string>
<string name="volume_music" msgid="5421651157138628171">"Medienlautstärke"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Keine Berechtigungen erforderlich"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ausblenden"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Alle anzeigen"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Ladevorgang läuft..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-Massenspeicher"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB-Verbindung"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Sie haben Ihr Telefon über einen USB-Anschluss mit Ihrem Computer verbunden. Wählen Sie \"Bereitstellen\", wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Telefons kopieren möchten."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Bereitstellen"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Nicht bereitstellen"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Sie haben Ihr Telefon über USB mit Ihrem Computer verbunden. Wählen Sie die Schaltfläche unten aus, wenn Sie Dateien auf Ihren Computer oder die SD-Karte Ihres Android-Geräts kopieren möchten."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB-Speicher aktivieren"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Bei der Verwendung Ihrer SD-Karte als USB-Speicher ist ein Problem aufgetreten."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-Verbindung"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Wählen Sie die Dateien aus, die von Ihrem oder auf Ihren Computer kopiert werden sollen."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB-Speicher deaktivieren"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Auswählen, um USB-Speicher zu deaktivieren."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"USB-Speicher deaktivieren"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Bevor Sie den USB-Speicher deaktivieren, stellen Sie sicher, dass Sie Ihn vom USB-Host getrennt haben. Wählen Sie \"Deaktivieren\", um den USB-Speicher zu deaktivieren."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Ausschalten"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Abbrechen"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Beim Deaktivieren des USB-Speichers wurde ein Problem festgestellt. Überprüfen Sie, ob Sie den USB-Host getrennt haben, und versuchen Sie es erneut."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-Speicher in Verwendung"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Stellen Sie vor dem Deaktivieren des USB-Speichers sicher, dass Sie Ihre Android-SD-Karte von Ihrem Computer getrennt (\"ausgeworfen\") haben."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB-Speicher deaktivieren"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Beim Deaktivieren des USB-Speichers ist ein Problem aufgetreten. Überprüfen Sie, ob Sie den USB-Host getrennt haben, und versuchen Sie es erneut."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB-Speicher aktivieren"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Wenn Sie den USB-Speicher aktivieren, werden einige von Ihnen verwendete Anwendungen angehalten und sind möglicherweise nicht verfügbar, bis Sie den USB-Speicher wieder deaktivieren."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB-Vorgang fehlgeschlagen"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"SD-Karte formatieren"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Möchten Sie die SD-Karte wirklich formatieren? Alle Daten auf Ihrer Karte gehen dann verloren."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Keine passenden Aktivitäten gefunden"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"Nutzungsstatistik der Komponente aktualisieren"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Ermöglicht die Änderung von gesammelten Nutzungsstatistiken der Komponente. Nicht für normale Anwendungen vorgesehen."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Ermöglicht das Aufrufen des Standard-Containerdienstes zum Kopieren von Inhalt. Keine Verwendung bei normalen Anwendungen."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Ermöglicht das Aufrufen des Standard-Containerdienstes zum Kopieren von Inhalt. Keine Verwendung bei normalen Anwendungen."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Für Zoomeinstellung zweimal berühren"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Fehler beim Vergrößern des Widgets"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Los"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Neuer Kontakt"\n"mit <xliff:g id="NUMBER">%s</xliff:g> erstellen"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aktiviert"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"nicht aktiviert"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die Anmeldeinformationen für das Konto <xliff:g id="ACCOUNT">%1$s</xliff:g> von <xliff:g id="APPLICATION">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Die aufgelisteten Anwendungen fordern eine Berechtigung zum Zugriff auf die <xliff:g id="TYPE">%1$s</xliff:g>-Anmeldeinformationen für das Konto <xliff:g id="APPLICATION">%3$s</xliff:g> von <xliff:g id="ACCOUNT">%2$s</xliff:g> an. Möchten Sie diese Berechtigung erteilen? Wenn ja, wird Ihre Antwort gespeichert und Sie erhalten keine erneute Aufforderung."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Die folgenden Anwendungen benötigen die Berechtigung zum aktuellen und zukünftigen Zugriff auf Ihr Konto."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Möchten Sie diese Anfrage zulassen?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Zugriffsanfrage"</string>
<string name="allow" msgid="7225948811296386551">"Zulassen"</string>
<string name="deny" msgid="2081879885755434506">"Ablehnen"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Berechtigung angefordert"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Zertifikat mit vorinstalliertem Schlüssel"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
+ <string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
+ <string name="submit" msgid="1602335572089911941">"Senden"</string>
+ <string name="description_star" msgid="2654319874908576133">"Favorit"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus aktiviert"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Zum Beenden des Automodus auswählen"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Tethering aktiv"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Zum Konfigurieren berühren"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hohe Mobildatennutzung"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Mobildatenlimit überschritten"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string>
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 65c6676..8838eb2 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Η πεÏιοÏισμένη Ï€Ïόσβαση άλλαξε"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Η υπηÏεσία δεδομένων είναι αποκλεισμένη."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Η υπηÏεσία έκτακτης ανάγκης είναι αποκλεισμένη."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Η φωνητική υπηÏεσία/υπηÏεσία SMS είναι αποκλεισμένη."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Όλες οι φωνητικές υπηÏεσίες/υπηÏεσίες SMS έχουν αποκλειστεί."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Η υπηÏεσία φωνής έχει αποκλειστεί."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Όλες οι υπηÏεσίες φωνής έχουν αποκλειστεί."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Η υπηÏεσία SMS έχει αποκλειστεί."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Οι υπηÏεσίες φωνής/δεδομένων έχουν αποκλειστεί."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Όλες οι υπηÏεσίες φωνής/SMS έχουν αποκλειστεί."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Όλες οι υπηÏεσίες φωνής/δεδομένων/SMS έχουν αποκλειστεί."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Φωνή"</string>
<string name="serviceClassData" msgid="872456782077937893">"Δεδομένα"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"ΦΑΞ"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"ΑπενεÏγοποίηση"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"ΑπενεÏγοποίηση..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Το τηλέφωνό σας θα απενεÏγοποιηθεί."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"ΠÏόσφατα"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Δεν υπάÏχουν Ï€Ïόσφατες εφαÏμογές."</string>
<string name="global_actions" msgid="2406416831541615258">"Επιλογές τηλεφώνου"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Κλείδωμα οθόνης"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"ΕπιτÏέπει σε μια εφαÏμογή να ενεÏγοποιήσει τον εντοπισμό σφαλμάτων για μια άλλη εφαÏμογή. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να τεÏματίσουν άλλες εφαÏμογές."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"αλλαγή των Ïυθμίσεων του UI"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της Ï„Ïέχουσας διαμόÏφωσης, όπως οι τοπικές Ïυθμίσεις ή το μέγεθος γÏαμματοσειÏάς γενικά."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"επανεκκίνηση άλλων εφαÏμογών"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"ΕπιτÏέπει σε μια εφαÏμογή να Ï€Ïαγματοποιήσει αναγκαστική επανεκκίνηση άλλων εφαÏμογών."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"ενεÏγοποίηση λειτουÏγίας αυτοκινήτου"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"ΕπιτÏέπει σε μια εφαÏμογή την ενεÏγοποίηση της λειτουÏγίας αυτοκινήτου."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"τεÏματισμός διεÏγασιών παÏασκηνίου"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"ΕπιτÏέπει σε μια εφαÏμογή τον τεÏματισμό των διεÏγασιών παÏασκηνίου άλλων εφαÏμογών, ακόμα και αν η μνήμη είναι επαÏκής."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"επιβολή διακοπής άλλων εφαÏμογών"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"ΕπιτÏέπει σε μια εφαÏμογή να Ï€Ïαγματοποιεί αναγκαστική διακοπή άλλων εφαÏμογών."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"αναγκαστικός τεÏματισμός εφαÏμογής"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"ΕπιτÏέπει σε μια εφαÏμογή να εξαναγκάσει οποιαδήποτε δÏαστηÏιότητα που βÏίσκεται στο Ï€Ïοσκήνιο να κλείσει και να μεταβεί στο φόντο. Δεν είναι απαÏαίτητο για κανονικές εφαÏμογές."</string>
<string name="permlab_dump" msgid="1681799862438954752">"ανάκτηση εσωτεÏικής κατάστασης συστήματος"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"ΕπιτÏέπει την Ï„Ïοποποίηση στατιστικών μπαταÏίας που έχουν συλλεχθεί. Δεν Ï€Ïέπει να χÏησιμοποιείται από συνήθεις εφαÏμογές."</string>
<string name="permlab_backup" msgid="470013022865453920">"αντίγÏαφο ασφαλείας και επαναφοÏά συστήματος"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"ΕπιτÏέπει στην εφαÏμογή τον έλεγχο του Î¼Î·Ï‡Î±Î½Î¹ÏƒÎ¼Î¿Ï Î´Î·Î¼Î¹Î¿Ï…Ïγίας αντιγÏάφων ασφαλείας και επαναφοÏάς του συστήματος. Δεν Ï€ÏοοÏίζεται για χÏήση από κανονικές εφαÏμογές."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"δημιουÏγία αντιγÏάφων ασφαλείας και επαναφοÏά δεδομένων εφαÏμογής"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"ΕπιτÏέπει στην εφαÏμογή να συμμετέχει στον μηχανισμό δημιουÏγίας αντιγÏάφων ασφαλείας και επαναφοÏάς του συστήματος."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Ï€Ïοβολή μη εξουσιοδοτημένων παÏαθÏÏων"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"ΕπιτÏέπει τη δημιουÏγία παÏαθÏÏων που Ï€Ïόκειται να χÏησιμοποιηθοÏν από την εσωτεÏική διεπαφή χÏήστη του συστήματος. Δεν Ï€Ïέπει να χÏησιμοποιείται από κανονικές εφαÏμογές."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"εμφάνιση ειδοποιήσεων επιπέδου συστήματος"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"ΕπιτÏέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας μεθόδου εισόδου. Δεν είναι απαÏαίτητο για συνήθεις εφαÏμογές."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"δέσμευση σε ταπετσαÏία"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"ΕπιτÏέπει στον κάτοχο τη δέσμευση στη διεπαφή ανωτάτου επιπέδου μιας ταπετσαÏίας. Δεν είναι απαÏαίτητο για συνήθεις εφαÏμογές."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"επικοινωνία με έναν διαχειÏιστή συσκευής"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"ΕπιτÏέπει στον κάτοχο την αποστολή στόχων σε έναν διαχειÏιστή συσκευής. Δεν θα χÏειαστεί ποτέ για κανονικές εφαÏμογές."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"αλλαγή Ï€ÏÎ¿ÏƒÎ±Î½Î±Ï„Î¿Î»Î¹ÏƒÎ¼Î¿Ï Î¿Î¸ÏŒÎ½Î·Ï‚"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της πεÏιστÏοφής της οθόνης οποιαδήποτε στιγμή. Δεν είναι απαÏαίτητο για κανονικές εφαÏμογές."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"αποστολή σημάτων Linux σε εφαÏμογές"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"ΕπιτÏέπει σε μια εφαÏμογή την εγκατάσταση νέων ή ενημεÏωμένων πακέτων Android. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να Ï€Ïοσθέσουν νέες εφαÏμογές με πολλές αυθαίÏετες άδειες."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"διαγÏαφή όλων των δεδομένων Ï€ÏοσωÏινής μνήμης εφαÏμογής"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"ΕπιτÏέπει σε μια εφαÏμογή να αυξήσει τον ελεÏθεÏο χώÏο αποθήκευσης του τηλεφώνου διαγÏάφοντας αÏχεία από τον κατάλογο Ï€ÏοσωÏινής μνήμης της εφαÏμογής. Η Ï€Ïόσβαση είναι συνήθως Ï€Î¿Î»Ï Ï€ÎµÏιοÏισμένη στη διαδικασία συστήματος."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Μετακίνηση πόÏων εφαÏμογής"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"ΕπιτÏέπει σε μια εφαÏμογή τη μετακίνηση πόÏων εφαÏμογής από ένα εσωτεÏικό σε ένα εξωτεÏικό μέσο και αντίστÏοφα."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"ανάγνωση αÏχείων καταγÏαφής συστήματος"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"ΕπιτÏέπει σε μια εφαÏμογή να αναγνώσει τα αÏχεία καταγÏαφής του συστήματος. Έτσι μποÏεί να ανακαλÏψει γενικές πληÏοφοÏίες σχετικά με τις δÏαστηÏιότητές σας στο τηλέφωνο, όμως δεν θα Ï€Ïέπει να πεÏιέχουν Ï€Ïοσωπικές ή ιδιωτικές πληÏοφοÏίες."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ανάγνωση/εγγÏαφή σε πόÏους που ανήκουν στο διαγνωστικό"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"ΕπιτÏέπει σε μια εφαÏμογή να Ï„Ïοποποιήσει τα δεδομένα κατόχου τηλεφώνου στο τηλέφωνό σας. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να Ï„Ïοποποιήσουν τα δεδομένα κατόχου."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"ανάγνωση δεδομένων κατόχου"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"ΕπιτÏέπει σε μια εφαÏμογή την ανάγνωση των δεδομένων κατόχου τηλεφώνου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για την ανάγνωση δεδομένων κατόχου τηλεφώνου."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"ανάγνωση δεδομένων ημεÏολογίου"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"ανάγνωση συμβάντων ημεÏολογίου"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"ΕπιτÏέπει σε μια εφαÏμογή να αναγνώσει όλα τα συμβάντα ημεÏολογίου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να αποστείλουν συμβάντα ημεÏολογίου σε άλλους χÏήστες."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"εγγÏαφή δεδομένων ημεÏολογίου"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"ΕπιτÏέπει σε μια εφαÏμογή την Ï„Ïοποποίηση των συμβάντων ημεÏολογίου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να διαγÏάψουν ή για να Ï„Ïοποποιήσουν τα δεδομένα ημεÏολογίου σας."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"Ï€Ïοσθήκη ή Ï„Ïοποποίηση συμβάντων του ημεÏολογίου και αποστολή μηνυμάτων ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου στους Ï€Ïοσκεκλημένους"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"ΕπιτÏέπει σε μια εφαÏμογή την Ï€Ïοσθήκη ή την αλλαγή συμβάντων στο ημεÏολόγιο σας, και την ενδεχόμενη αποστολή μηνυμάτων ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου στους Ï€Ïοσκεκλημένους. Οι κακόβουλες εφαÏμογές μποÏοÏν να χÏησιμοποιήσουν αυτή τη λειτουÏγία για να διαγÏάψουν ή να Ï„Ïοποποιήσουν τα συμβάντα του ημεÏολογίου σας ή για να στείλουν μηνÏματα ηλεκτÏÎ¿Î½Î¹ÎºÎ¿Ï Ï„Î±Ï‡Ï…Î´Ïομείου στους Ï€Ïοσκεκλημένους."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"δημιουÏγία ψευδών πηγών τοποθεσίας για δοκιμή"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"ΔημιουÏγία εικονικών πηγών τοποθεσίας για δοκιμή. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να παÏακάμψουν την τοποθεσία και/ή την κατάσταση που βÏίσκουν Ï€Ïαγματικές πηγές τοποθεσίας, όπως πάÏοχοι GPS ή πάÏοχοι δικτÏου."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"Ï€Ïόσβαση σε επιπλέον εντολές παÏόχου τοποθεσίας"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"ΕπιτÏέπει στην εφαÏμογή την Ï€ÏοσάÏτηση και αποπÏοσάÏτηση συστημάτων αÏχείων για αφαιÏοÏμενο αποθηκευτικό χώÏο."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"διαμόÏφωση εξωτεÏÎ¹ÎºÎ¿Ï Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"ΕπιτÏέπει στην εφαÏμογή τη διαμόÏφωση αφαιÏοÏμενου Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"λήψη πληÏοφοÏιών στον ασφαλή χώÏο αποθήκευσης"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"ΕπιτÏέπει στην εφαÏμογή τη λήψη πληÏοφοÏιών στον ασφαλή χώÏο αποθήκευσης."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"δημιουÏγία ασφαλοÏÏ‚ χώÏου αποθήκευσης"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"ΕπιτÏέπει στην εφαÏμογή τη δημιουÏγία ασφαλοÏÏ‚ χώÏου αποθήκευσης."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"καταστÏοφή ασφαλοÏÏ‚ χώÏου αποθήκευσης"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"ΕπιτÏέπει στην εφαÏμογή την καταστÏοφή του ασφαλοÏÏ‚ χώÏου αποθήκευσης."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"σÏνδεση / αποσÏνδεση ασφαλοÏÏ‚ χώÏου αποθήκευσης"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"ΕπιτÏέπει στην εφαÏμογή τη σÏνδεση / αποσÏνδεση του ασφαλοÏÏ‚ χώÏου αποθήκευσης."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"μετονομασία ασφαλοÏÏ‚ χώÏου αποθήκευσης"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"ΕπιτÏέπει στην εφαÏμογή τη μετονομασία του ασφαλοÏÏ‚ χώÏου αποθήκευσης."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"έλεγχος δόνησης"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"ΕπιτÏέπει στην εφαÏμογή τον έλεγχο του δονητή."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"έλεγχος φακοÏ"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"ΕπιτÏέπει στην εφαÏμογή τον οÏισμό συμβουλών μεγέθους ταπετσαÏίας συστήματος."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"επαναφοÏά συστήματος στις εÏγοστασιακές Ï€Ïοεπιλογές"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"ΕπιτÏέπει σε μια εφαÏμογή να επαναφέÏει πλήÏως το σÏστημα στις εÏγοστασιακές Ïυθμίσεις, διαγÏάφοντας όλα τα δεδομένα, τις διαμοÏφώσεις και τις εγκατεστημένες εφαÏμογές."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"ÏÏθμιση ÏŽÏας"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή ÏŽÏας ÏÎ¿Î»Î¿Î³Î¹Î¿Ï Ï„Î¿Ï… τηλεφώνου."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"οÏισμός ζώνης ÏŽÏας"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της ζώνης ÏŽÏας του τηλεφώνου."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"ενεÏγεί ως AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"εγγÏαφή Ïυθμίσεων Ονόματος σημείου Ï€Ïόσβασης (APN)"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"ΕπιτÏέπει σε μια εφαÏμογή να Ï„Ïοποποιήσει τις Ïυθμίσεις APN, όπως Διακομιστής μεσολάβησης και ΘÏÏα για ένα APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"αλλαγή συνδεσιμότητας δικτÏου"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της κατάστασης συνδεσιμότητας δικτÏου."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της κατάστασης συνδεσιμότητας δικτÏου."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"αλλαγή συνδεσιμότητας της σÏνδεσης μέσω κινητής συσκευής"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της κατάστασης συνδεσιμότητας δικτÏου μέσω σÏνδεσης με κινητή συσκευή."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"αλλαγή ÏÏθμισης της χÏήσης δεδομένων στο παÏασκήνιο"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"ΕπιτÏέπει σε μια εφαÏμογή την αλλαγή της ÏÏθμισης χÏήσης δεδομένων φόντου."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Ï€Ïοβολή κατάστασης Wi-Fi"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"ΕπιτÏέπει σε μια εφαÏμογή την εγγÏαφή νέων λέξεων στο λεξικό χÏήστη."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"Ï„Ïοποποίηση/διαγÏαφή πεÏιεχομένων κάÏτας SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"ΕπιτÏέπει στην εφαÏμογή την εγγÏαφή στην κάÏτα SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Ï€Ïόσβαση στο σÏστημα αÏχείων Ï€ÏοσωÏινής μνήμης"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"ΕπιτÏέπει σε μια εφαÏμογή την ανάγνωση και την εγγÏαφή του συστήματος αÏχείων Ï€ÏοσωÏινής μνήμης."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"ΠεÏιοÏισμός επιλογών ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"ΠεÏιοÏισμός των Ï„Ïπων ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης που επιτÏέπεται να χÏησιμοποιείτε."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"ΠαÏακολοÏθηση Ï€Ïοσπαθειών σÏνδεσης"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"ΠαÏακολοÏθηση αποτυχημένων Ï€Ïοσπαθειών σÏνδεσης με τη συσκευή, για την εκτέλεσης κάποιας ενέÏγειας."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"ΕπαναφοÏά ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασης"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"ΕφαÏμογή του ÎºÏ‰Î´Î¹ÎºÎ¿Ï Ï€Ïόσβασής σας σε μια νέα τιμή, με την Ï€Ïοϋπόθεση ότι σας παÏέχεται από τον διαχειÏιστή για να μποÏείτε να συνδεθείτε."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"ΕφαÏμογή κλειδώματος"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Ελέγχει πότε κλειδώνει η συσκευή, απαιτώντας κωδικό Ï€Ïόσβασης για επανείσοδο."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"ΔιαγÏαφή όλων των δεδομένων"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"ΠÏαγματοποιείται επαναφοÏά εÏγοστασιακών Ïυθμίσεων, με τη διαγÏαφή όλων των δεδομένων σας χωÏίς επιβεβαίωση."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Οικία"</item>
<item msgid="869923650527136615">"Κινητό"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"ΠληκτÏολογήστε τον κωδικό αÏιθμό PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Εισαγάγετε τον κωδικό Ï€Ïόσβασης για ξεκλείδωμα"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Εσφαλμένος κωδικός αÏιθμός PIN!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Για ξεκλείδωμα, πατήστε το πλήκτÏο Menu και, στη συνέχεια, το πλήκτÏο 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ΑÏιθμός έκτακτης ανάγκης"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Πατήστε \"ΜενοÏ\" για ξεκλείδωμα."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Σχεδιασμός μοτίβου για ξεκλείδωμα"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Κλήση έκτακτης ανάγκης"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"ΕπιστÏοφή στην κλήση"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Σωστό!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"ΠÏοσπαθήστε αÏγότεÏα"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"ΦόÏτιση (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Ξεκλείδωμα"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"ΕνεÏγοποίηση ήχου"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"ΑπενεÏγοποίηση ήχου"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ΑΒΓ"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"ΕκκαθάÏιση"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"ΕπιτÏέπει στην εφαÏμογή την ανάγνωση όλων των διευθÏνσεων URL που το Ï€ÏόγÏαμμα πεÏιήγησης έχει επισκεφθεί και όλων των σελιδοδεικτών του Ï€ÏογÏάμματος πεÏιήγησης."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"εγγÏαφή ιστοÏÎ¹ÎºÎ¿Ï ÎºÎ±Î¹ σελιδοδεικτών Ï€ÏογÏάμματος πεÏιήγησης"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"ΕπιτÏέπει σε μια εφαÏμογή να Ï„Ïοποποιήσει το ιστοÏικό ή τους σελιδοδείκτες του Ï€ÏογÏάμματος πεÏιήγησης που βÏίσκονται αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να διαγÏάψουν ή να Ï„Ïοποποιήσουν τα δεδομένα του Ï€ÏογÏάμματος πεÏιήγησης."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"ΤÏοποποίηση δικαιωμάτων γεωγÏαφικής θέσης Ï€ÏογÏάμματος πεÏιήγησης"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"ΕπιτÏέπει σε μια εφαÏμογή την Ï„Ïοποποίηση των δικαιωμάτων γεωγÏαφικής θέσης του Ï€ÏογÏάμματος πεÏιήγησης. Οι κακόβουλες εφαÏμογές μποÏοÏν να το χÏησιμοποιήσουν για να επιτÏέψουν την αποστολή στοιχείων τοποθεσίας σε αυθαίÏετους ιστότοπους."</string>
<string name="save_password_message" msgid="767344687139195790">"Θέλετε το Ï€ÏόγÏαμμα πεÏιήγησης να διατηÏήσει αυτόν τον κωδικό Ï€Ïόσβασης;"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Îα μην γίνει Ï„ÏŽÏα"</string>
<string name="save_password_remember" msgid="6491879678996749466">"ΔιατήÏηση"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"Ï€Ïιν από 1 ÏŽÏα"</item>
<item quantity="other" msgid="2467273239587587569">"Ï€Ïιν από <xliff:g id="COUNT">%d</xliff:g> ÏŽÏες"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Τελευταίες <xliff:g id="COUNT">%d</xliff:g> ημέÏες"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Τελευταίος μήνας"</string>
+ <string name="older" msgid="5211975022815554840">"ΠαλαιότεÏα"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"χθες"</item>
<item quantity="other" msgid="2479586466153314633">"Ï€Ïιν από <xliff:g id="COUNT">%d</xliff:g> ημέÏες"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"εβδομάδες"</string>
<string name="year" msgid="4001118221013892076">"έτος"</string>
<string name="years" msgid="6881577717993213522">"έτη"</string>
- <string name="every_weekday" msgid="8777593878457748503">"ΚαθημεÏινές (Δευ-ΠαÏ)"</string>
- <string name="daily" msgid="5738949095624133403">"ΚαθημεÏινά"</string>
- <string name="weekly" msgid="983428358394268344">"Κάθε εβδομάδα στο <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Μηνιαία"</string>
- <string name="yearly" msgid="1519577999407493836">"Ετήσια"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Δεν είναι δυνατή η αναπαÏαγωγή βίντεο"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Αυτό το βίντεο δεν είναι έγκυÏο για Ïοή σε αυτή τη συσκευή."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Δεν είναι δυνατή η Ï€Ïοβολή Î±Ï…Ï„Î¿Ï Ï„Î¿Ï… βίντεο."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string>
<string name="report" msgid="4060218260984795706">"ΑναφοÏά"</string>
<string name="wait" msgid="7147118217226317732">"Αναμονή"</string>
- <string name="debug" msgid="9103374629678531849">"Εντοπισμός σφαλμάτων"</string>
<string name="sendText" msgid="5132506121645618310">"Επιλέξτε μια ενέÏγεια για το κείμενο"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ένταση ειδοποίησης ήχου"</string>
<string name="volume_music" msgid="5421651157138628171">"Ένταση ήχου πολυμέσων"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Δεν απαιτοÏνται άδειες"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"ΑπόκÏυψη"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Εμφάνιση όλων"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"ΦόÏτωση..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Μαζική αποθήκευση USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Το USB είναι συνδεδεμένο"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Συνδέσατε το τηλέφωνό σας στον υπολογιστή μέσω USB. Επιλέξτε \"ΠÏοσάÏτηση\" αν θέλετε να αντιγÏάψετε αÏχεία Î¼ÎµÏ„Î±Î¾Ï Ï„Î¿Ï… υπολογιστή και της κάÏτας SD του τηλεφώνου."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"ΠÏοσάÏτηση"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Îα μην γίνει Ï€ÏοσάÏτηση"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Συνδέσατε το τηλέφωνό σας στον υπολογιστή μέσω USB. Επιλέξτε το παÏακάτω κουμπί αν θέλετε να αντιγÏάψετε αÏχεία Î¼ÎµÏ„Î±Î¾Ï Ï„Î¿Ï… υπολογιστή και της κάÏτας SD του Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"ΕνεÏγοποίηση Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"ΠαÏουσιάστηκε ένα Ï€Ïόβλημα στη χÏήση της κάÏτας SD ως αποθηκευτικό χώÏο USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Το USB είναι συνδεδεμένο"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Επιλέξτε για αντιγÏαφή Ï€Ïος/από τον υπολογιστή σας."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"ΑπενεÏγοποίηση Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Επιλογή για απενεÏγοποίηση Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"ΑπενεÏγοποίηση Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"ΠÏιν από την απενεÏγοποίηση του Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB, βεβαιωθείτε ότι έχετε αποπÏοσαÏτήσει την υποδοχή USB. Επιλέξτε \"ΑπενεÏγοποίηση\" για να απενεÏγοποιήσετε τον αποθηκευτικό χώÏο USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"ΑπενεÏγοποίηση"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"ΑκÏÏωση"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"ΠαÏουσιάστηκε ένα Ï€Ïόβλημα κατά την απενεÏγοποίηση του Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB. Βεβαιωθείτε ότι έχετε αποσυνδέσει την υποδοχή USB και Ï€Ïοσπαθήστε ξανά."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"ΧώÏος αποθήκευσης USB σε χÏήση"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"ΠÏÎ¿Ï„Î¿Ï Î±Ï€ÎµÎ½ÎµÏγοποιήσετε το χώÏο αποθήκευσης USB, βεβαιωθείτε ότι έχετε αποσυνδέσει (“αφαιÏέσειâ€) την κάÏτα SD του Android από τον υπολογιστή σας."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"ΑπενεÏγοποίηση χώÏου αποθήκευσης USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"ΠαÏουσιάστηκε Ï€Ïόβλημα κατά την απενεÏγοποίηση του Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB. Βεβαιωθείτε ότι έχετε αφαιÏέσει την υποδοχή USB και Ï€Ïοσπαθήστε ξανά."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"ΕνεÏγοποίηση Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Ï‡ÏŽÏου USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Εάν ενεÏγοποιήσετε τον αποθηκευτικό χώÏο USB, οÏισμένες από τις εφαÏμογές που χÏησιμοποιείτε θα σταματήσουν και ενδέχεται να μην είναι διαθέσιμες μέχÏι να απενεÏγοποιήσετε τον αποθηκευτικό χώÏο USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Απέτυχε η λειτουÏγία USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"ΟΚ"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"ΔιαμόÏφωση κάÏτας SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Είστε βέβαιοι ότι θέλετε να διαμοÏφώσετε την κάÏτα SD; Όλα τα δεδομένα στην κάÏτα σας θα χαθοÏν."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"ΔιαμόÏφωση"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Δεν βÏέθηκαν δÏαστηÏιότητες που να αντιστοιχοÏν"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"ενημέÏωση στατιστικών χÏήσης στοιχείου"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"ΕπιτÏέπει την Ï„Ïοποποίηση στατιστικών χÏήσης στοιχείων που έχουν συλλεχθεί. Δεν Ï€Ïέπει να χÏησιμοποιείται από κανονικές εφαÏμογές."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"ΕπιτÏέπει την κλήση της Ï€Ïοεπιλεγμένης υπηÏεσίας ÎºÎ¿Î½Ï„Î­Î¹Î½ÎµÏ Î³Î¹Î± την αντιγÏαφή πεÏιεχομένου. Δεν χÏησιμοποιείται από κανονικές εφαÏμογές."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"ΕπιτÏέπει την κλήση της Ï€Ïοεπιλεγμένης υπηÏεσίας ÎºÎ¿Î½Ï„Î­Î¹Î½ÎµÏ Î³Î¹Î± την αντιγÏαφή πεÏιεχομένου. Δεν χÏησιμοποιείται από κανονικές εφαÏμογές."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Πατήστε δÏο φοÏές για έλεγχο εστίασης"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Σφάλμα αÏξησης μεγέθους γÏÎ±Ï†Î¹ÎºÎ¿Ï ÏƒÏ„Î¿Î¹Ï‡ÎµÎ¯Î¿Ï…"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Μετάβαση"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"ΔημιουÏγία επαφής"\n"με τη χÏήση του <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"επιλεγμένο"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"δεν ελέγχθηκε"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Οι εφαÏμογές που παÏαθέτονται στη λίστα ζητοÏν άδεια για να αποκτήσουν Ï€Ïόσβαση στα διαπιστευτήÏια σÏνδεσης για τον λογαÏιασμό <xliff:g id="ACCOUNT">%1$s</xliff:g> από <xliff:g id="APPLICATION">%2$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα εÏωτηθείτε ξανά."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Οι εφαÏμογές που παÏαθέτονται στη λίστα ζητοÏν άδεια για να αποκτήσουν Ï€Ïόσβαση στα διαπιστευτήÏια σÏνδεσης <xliff:g id="TYPE">%1$s</xliff:g> για τον λογαÏιασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> από <xliff:g id="APPLICATION">%3$s</xliff:g>. Θα αποδεχτείτε το αίτημα; Εάν το αποδεχτείτε, η απάντησή σας θα αποθηκευτεί και δεν θα εÏωτηθείτε ξανά."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Από την εφαÏμογή ή τις εφαÏμογές που ακολουθοÏν ζητοÏνται δικαιώματα Ï€Ïόσβασης στο λογαÏιασμό σας, Ï„ÏŽÏα και στο μέλλον."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Θέλετε να επιτÏέψετε αυτή την αίτηση;"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Αίτημα Ï€Ïόσβασης"</string>
<string name="allow" msgid="7225948811296386551">"Îα επιτÏέπεται"</string>
<string name="deny" msgid="2081879885755434506">"ΆÏνηση"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Απαιτείται άδεια"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"ΠÏωτόκολλο Layer 2 Tunneling Protocol (L2TP)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Κλειδί pre-shared βάσει L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Πιστοποιητικό βάσει L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Επιλογή αÏχείου"</string>
+ <string name="reset" msgid="2448168080964209908">"ΕπαναφοÏά"</string>
+ <string name="submit" msgid="1602335572089911941">"Υποβολή"</string>
+ <string name="description_star" msgid="2654319874908576133">"αγαπημένο"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Η λειτουÏγία αυτοκινήτου είναι ενεÏγοποιημένη"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Επιλέξτε για έξοδο από τη λειτουÏγία αυτοκινήτου."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Η σÏνδεση μέσω κινητής συσκευής είναι ενεÏγή"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Αγγίξτε για να γίνει διαμόÏφωση"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Υψηλή χÏήση δεδομένων κινητής τηλεφωνίας"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Αγγίξτε για να μάθετε πεÏισσότεÏα σχετικά με τη χÏήση δεδομένων κινητής τηλεφωνίας"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"ΞεπεÏάστηκε το ÏŒÏιο δεδομένων κινητής τηλεφωνίας"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Αγγίξτε για να μάθετε πεÏισσότεÏα σχετικά με τη χÏήση δεδομένων κινητής τηλεφωνίας"</string>
</resources>
diff --git a/core/res/res/values-en-rUS/donottranslate-names.xml b/core/res/res/values-en-rUS/donottranslate-names.xml
index ae38ddf..aa0abe3 100644
--- a/core/res/res/values-en-rUS/donottranslate-names.xml
+++ b/core/res/res/values-en-rUS/donottranslate-names.xml
@@ -4,35 +4,51 @@
<!-- various string resources for Contacts -->
<string-array name="common_nicknames">
- <item>Abigail, Abbie, Gail, Gayle</item>
+ <item>Abigail, Abbie</item>
+ <item>Abigail, Gail, Gayle</item>
<item>Abe, Abraham</item>
- <item>Aggie, Agatha, Agnes</item>
- <item>Albert, Al, Bert, Bertie</item>
- <item>Alexander, Al, Alec, Alex, Lex, Sasha</item>
- <item>Alexandra, Al, Allie, Ally, Lex, Lexie, Sandra, Sandy, Sasha</item>
+ <item>Agatha, Aggie</item>
+ <item>Agatha, Agnes</item>
+ <item>Albert, Al</item>
+ <item>Albert, Bert, Bertie</item>
+ <item>Alexander, Al, Alex</item>
+ <item>Alexander, Al, Alec</item>
+ <item>Alexander, Alex, Lex</item>
+ <item>Alexander, Alex, Sasha</item>
+ <item>Alexandra, Al, Allie, Ally</item>
+ <item>Alexandra, Lex, Lexie</item>
+ <item>Alexandra, Sandra, Sandy</item>
+ <item>Alexandra, Sasha</item>
<item>Alf, Alfred, Alfredo, Alfie</item>
<item>Alice, Allie, Ally</item>
<item>Alison, Allie, Ally</item>
<item>Allison, Allie, Ally</item>
<item>Amanda, Mandi, Mandy</item>
<item>Andrea, Andie</item>
- <item>Andrew, Andy, Drew</item>
+ <item>Andrew, Andy</item>
+ <item>Andrew, Drew</item>
<item>Anne, Annie, Annette</item>
<item>Anthony, Tony, Toni, Tone</item>
<item>Arthur, Art, Arty</item>
- <item>Barbara, Babs, Barb, Barbie</item>
- <item>Benjamin, Ben, Benji, Benny</item>
- <item>Bernard, Bern, Bernie, Barnie</item>
+ <item>Barbara, Barb, Barbie</item>
+ <item>Benjamin, Ben, Benny</item>
+ <item>Benjamin, Benji</item>
+ <item>Bernard, Barnie</item>
+ <item>Bernard, Bern, Bernie</item>
<item>Bertram, Bert, Bertie</item>
<item>Bradly, Brad</item>
<item>Calvin, Cal</item>
<item>Catherine, Cat, Cate, Cath, Catie, Cathy, Kat, Kate, Katie, Kathy</item>
<item>Carrie, Caroline, Carolyn</item>
- <item>Charles, Chuck, Chaz, Charlie, Buck</item>
+ <item>Charles, Charlie</item>
+ <item>Charles, Chuck</item>
+ <item>Charles, Chaz</item>
+ <item>Charles, Buck</item>
<item>Christine, Chrissy, Chrissie</item>
<item>Christopher, Chris</item>
<item>Clinton, Clint</item>
- <item>Cynthia, Cindy, Cynth</item>
+ <item>Cynthia, Cindy</item>
+ <item>Cynthia, Cynth</item>
<item>Daniel, Dan, Danny</item>
<item>David, Dave</item>
<item>Deborah, Deb, Debbie</item>
@@ -43,30 +59,46 @@
<item>Dorothea, Dot, Dotty</item>
<item>Dorothy, Dot, Dotty</item>
<item>Douglas, Doug</item>
- <item>Edward, Ed, Eddie, Ned, Neddie, Neddy, Ted, Teddy, Teddie</item>
- <item>Eleanor, Ella, Ellie, Elle</item>
+ <item>Edward, Ed, Eddie</item>
+ <item>Edward, Ned, Neddie, Neddy</item>
+ <item>Edward, Ted, Teddy, Teddie</item>
+ <item>Eleanor, Ella</item>
+ <item>Eleanor, Ellie, Elle</item>
<item>Elisabetta, Betta</item>
- <item>Elizabeth, Beth, Bess, Bessie, Betsy, Betty, Bette, Eliza, Lisa, Liza, Liz</item>
+ <item>Elizabeth, Beth</item>
+ <item>Elizabeth, Bess, Bessie</item>
+ <item>Elizabeth, Betsy</item>
+ <item>Elizabeth, Betty, Bette</item>
+ <item>Elizabeth, Eliza</item>
+ <item>Elizabeth, Lisa, Liza, Liz</item>
<item>Emily, Em, Ems, Emmy</item>
<item>Emma, Em, Ems, Emmy</item>
<item>Eugene, Gene</item>
<item>Fannie, Fanny</item>
<item>Florence, Flo</item>
- <item>Frances, Fran, Francie</item>
- <item>Francis, Fran, Frank, Frankie</item>
+ <item>Frances, Fran</item>
+ <item>Frances, Francie</item>
+ <item>Francis, Fran</item>
+ <item>Francis, Frank, Frankie</item>
<item>Frederick, Fred, Freddy</item>
<item>Gabriel, Gabe</item>
<item>Gerald, Gerry</item>
<item>Gerard, Gerry</item>
<item>Gregory, Greg, Gregg</item>
- <item>Harold, Hal, Harry</item>
- <item>Henry, Hal, Hank, Harry</item>
+ <item>Harold, Hal</item>
+ <item>Harold, Harry</item>
+ <item>Henry, Hal</item>
+ <item>Henry, Hank</item>
+ <item>Henry, Harry</item>
<item>Herbert, Bert, Bertie</item>
<item>Irving, Irv</item>
- <item>Isabella, Isa, Izzy, Bella</item>
+ <item>Isabella, Isa</item>
+ <item>Isabella, Izzy</item>
+ <item>Isabella, Bella</item>
<item>Jacob, Jake</item>
<item>Jacqueline, Jackie</item>
- <item>James, Jim, Jimmy, Jamie, Jock</item>
+ <item>James, Jim, Jimmy</item>
+ <item>James, Jamie</item>
<item>Janet, Jan</item>
<item>Janice, Jan</item>
<item>Jason, Jay</item>
@@ -90,10 +122,15 @@
<item>Laura, Lauri, Laurie</item>
<item>Lauren, Lauri, Laurie</item>
<item>Lawrence, Larry</item>
- <item>Leonard, Leo, Len, Lenny</item>
- <item>Leopold, Leo, Len, Lenny</item>
+ <item>Leonard, Leo</item>
+ <item>Leonard, Len, Lenny</item>
+ <item>Leopold, Leo</item>
<item>Madeline, Maddie, Maddy</item>
- <item>Margaret, Marge, Marg, Maggie, Mags, Meg, Peggy, Greta, Gretchen</item>
+ <item>Margaret, Marge, Marg</item>
+ <item>Margaret, Maggie, Mags</item>
+ <item>Margaret, Meg</item>
+ <item>Margaret, Peggy</item>
+ <item>Margaret, Greta, Gretchen</item>
<item>Martin, Martie, Marty</item>
<item>Matthew, Matt, Mattie</item>
<item>Maureen, Mo</item>
@@ -101,27 +138,36 @@
<item>Maxwell, Max</item>
<item>Maximilian, Maxim, Max</item>
<item>Megan, Meg</item>
- <item>Michael, Mickey, Mick, Mike, Mikey</item>
+ <item>Michael, Mickey, Mick</item>
+ <item>Michael, Mike, Mikey</item>
<item>Morris, Mo</item>
<item>Nancy, Nan</item>
- <item>Nathan, Nat, Nate</item>
- <item>Nathaniel, Nat, Nate</item>
+ <item>Nathan, Nat</item>
+ <item>Nathan, Nate</item>
+ <item>Nathaniel, Nat</item>
+ <item>Nathaniel, Nate</item>
<item>Nicholas, Nick</item>
<item>Nicole, Nicky, Nickie, Nikky</item>
<item>Pamela, Pam</item>
- <item>Patricia, Pat, Patsy, Patty, Trish, Tricia</item>
- <item>Patrick, Pat, Patter</item>
+ <item>Patricia, Pat, Patty</item>
+ <item>Patricia, Patsy</item>
+ <item>Patricia, Trish, Tricia</item>
+ <item>Patrick, Pat</item>
<item>Penelope, Penny</item>
<item>Peter, Pete</item>
<item>Raymond, Ray</item>
<item>Philip, Phil</item>
<item>Rebecca, Becca, Becky</item>
- <item>Richard, Rick, Rich, Dick</item>
- <item>Robert, Bob, Rob, Robbie, Bobby, Rab</item>
+ <item>Richard, Rick</item>
+ <item>Richard, Rich</item>
+ <item>Richard, Dick</item>
+ <item>Robert, Bob, Bobby</item>
+ <item>Robert, Rob, Robbie</item>
<item>Rodney. Rod</item>
<item>Ronald, Ron, Ronnie</item>
<item>Rosemary, Rosie, Rose</item>
- <item>Russell, Russ, Rusty</item>
+ <item>Russell, Russ</item>
+ <item>Russell, Rusty</item>
<item>Ryan, Ry</item>
<item>Samantha, Sam</item>
<item>Samuel, Sam, Sammy</item>
@@ -130,23 +176,34 @@
<item>Stephen, Steve</item>
<item>Steven, Steve</item>
<item>Stuart, Stu</item>
- <item>Susan, Sue, Susie, Suzie</item>
- <item>Suzanne, Sue, Susie, Suzie</item>
+ <item>Susan, Sue</item>
+ <item>Susan, Susie, Suzie</item>
+ <item>Suzanne, Sue</item>
+ <item>Suzanne, Susie, Suzie</item>
<item>Tamara, Tammy</item>
<item>Theresa, Teresa</item>
- <item>Theodora, Teddie, Thea, Theo</item>
- <item>Theodore, Ted, Teddy, Theo</item>
+ <item>Theodora, Teddie</item>
+ <item>Theodora, Thea</item>
+ <item>Theodore, Ted, Teddy</item>
+ <item>Theodore, Theo</item>
<item>Thomas, Tom, Thom, Tommy</item>
<item>Timothy, Tim, Timmy</item>
<item>Valerie, Val</item>
- <item>Veronica, Ronnie, Roni, Nica, Nikki, Nikka</item>
+ <item>Veronica, Ronnie, Roni</item>
+ <item>Veronica, Nica, Nikki, Nikka</item>
<item>Victor, Vic</item>
- <item>Victoria, Vicky, Vicki, Vickie, Tori</item>
- <item>Vincent, Vince, Vin, Vinnie</item>
+ <item>Victoria, Vicky, Vicki, Vickie</item>
+ <item>Victoria, Tori</item>
+ <item>Vincent, Vince</item>
+ <item>Vincent, Vin, Vinnie</item>
<item>Vivian, Vivi</item>
- <item>Walter, Walt, Wally</item>
- <item>Wendy, Wen, Wendel</item>
- <item>William, Bill, Billy, Will, Willy, Liam</item>
+ <item>Walter, Wally</item>
+ <item>Walter, Walt</item>
+ <item>Wendy, Wen</item>
+ <item>Wendy, Wendel</item>
+ <item>William, Bill, Billy</item>
+ <item>William, Will, Willy</item>
+ <item>William, Liam</item>
<item>Yvonna, Vonna</item>
<item>Zachary, Zach, Zack, Zac</item>
</string-array>
@@ -156,8 +213,8 @@
MRS, MS, PASTOR, PROF, REP, REVEREND, REV, SEN, ST
</string>
<string name="common_name_suffixes">
- B.A., BA, D.D.S., DDS, I, II, III, IV, IX, JR, M.A., M.D, MA,
- MD, MS, PH.D., PHD, SR, V, VI, VII, VIII, X
+ B.A., BA, D.D.S., DDS, I, II, III, IV, IX, JR., M.A., M.D., MA,
+ MD, MS, PH.D., PHD, SR., V, VI, VII, VIII, X
</string>
<string name="common_last_name_prefixes">
D\', DE, DEL, DI, LA, LE, MC, SAN, ST, TER, VAN, VON
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 5f980e0..cdd686d 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Se ha cambiado el acceso restringido"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"El servicio de datos está bloqueado."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"El servicio de emergencias está bloqueado."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"El servicio de voz/SMS está bloqueado."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Todos los servicios de voz/SMS están bloqueados."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"El servicio de voz está bloqueado."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Todos los servicios de voz están bloqueados."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"El servicio de SMS está bloqueado."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Los servicios de voz/datos están bloqueados."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Los servicios de voz/SMS están bloqueados."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Todos los servicios de voz/datos/SMS están bloqueados."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voz"</string>
<string name="serviceClassData" msgid="872456782077937893">"Datos"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Apagar"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Apagando…"</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Tu teléfono se apagará."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Reciente"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"No hay aplicaciones recientes."</string>
<string name="global_actions" msgid="2406416831541615258">"Opciones de teléfono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueo de pantalla"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Admite una aplicación que activa la depuración en otra aplicación. Las aplicaciones maliciosas pueden utilizarlo para suprimir otras aplicaciones."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar tu configuración de la interfaz de usuario"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Admite una aplicación para cambiar la configuración actual, como el tamaño de fuente local o general."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"reiniciar otras aplicaciones"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Admite una aplicación que reinicia otras aplicaciones por la fuerza."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"habilitar el modo de auto"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Permite que una aplicación habilite el modo auto."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"eliminar los procesos de fondo"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Permite que una aplicación elimine los procesos de fondo de otras aplicaciones, aun si la memoria no es baja."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"provocar la detención de otras aplicaciones"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Permite que una aplicación provoque la detención de otras aplicaciones."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"provocar que la aplicación se acerque"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Admite una aplicación que provoca que cualquier actividad del fondo se acerque y vuelva a alejarse. Se debe evitar utilizarlo en aplicaciones normales."</string>
<string name="permlab_dump" msgid="1681799862438954752">"recuperar el estado interno del sistema"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Admite la modificación de estadísticas recopiladas sobre la batería. Las aplicaciones normales no deben utilizarlo."</string>
<string name="permlab_backup" msgid="470013022865453920">"copia de seguridad y restauración del sistema de control"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Permite a la aplicación controlar el mecanismo de restauración y copia de seguridad de los sistemas. No es para uso de las aplicaciones normales."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"hacer una copia de seguridad y restaurar los datos de aplicación"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Permite a la aplicación participar en la copia de seguridad del sistema y restaurar el mecanismo."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"mostrar ventanas no autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Permite la creación de ventanas que la interfaz interna del usuario del sistema pretenda utilizar. Las aplicaciones normales no deben utilizarlo."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"mostrar alertas a nivel del sistema"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Permite al propietario vincularse a la interfaz de nivel superior de un método de entrada. Se debe evitar utilizarlo en aplicaciones normales."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"vincular a un fondo de pantalla"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Permite al propietario vincularse a la interfaz de nivel superior de un fondo de pantalla. Se debe evitar utilizarlo en aplicaciones normales."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interactuar con un administrador de dispositivo"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Permite que el propietario envíe sus intentos a un administrador de dispositivos. No se necesita para las aplicaciones normales."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"cambiar la orientación de la pantalla"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Admite una aplicación que cambia la rotación de la pantalla en cualquier momento. Se debe evitar utilizarlo en aplicaciones normales."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"enviar señales de Linux a las aplicaciones"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Admite una aplicación que instala paquetes de Android nuevos o actualizados. Las aplicaciones maliciosas pueden utilizarlo para agregar aplicaciones nuevas con permisos arbitrariamente potentes."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"eliminar todos los datos de memoria caché de la aplicación"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Admite una aplicación que libera espacio de almacenamiento en el teléfono al eliminar archivos del directorio de memoria caché de la aplicación. En general, el acceso es muy restringido para el proceso del sistema."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de la aplicación"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite a una aplicación mover recursos de aplicación de medios internos a externos y viceversa."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"leer archivos de registro del sistema"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Admite una aplicación que lee diversos archivos de registro del sistema. Esto le permite descubrir información general sobre lo que haces con el teléfono, pero no debe contener información personal ni privada."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"leer y escribir a recursos dentro del grupo de diagnóstico"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Admite una aplicación que modifica los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos del propietario."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Admite una aplicación que lee los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para leer los datos del propietario del teléfono."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"leer datos de calendario"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"Leer eventos del calendario"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Admite que una aplicación lea todos los eventos de calendario almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"escribir datos de calendario"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Admite una aplicación que modifica los eventos de calendario guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar tus datos de calendario."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"Agregar o cambiar eventos del calendario y enviar un correo electrónico a los invitados"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Permite a una aplicación agregar o cambiar eventos en tu calendario, los cuales pueden enviar un correo electrónico a los invitados. Las aplicaciones malintencionadas pueden usar esto para borrar o modificar tus eventos del calendario o para enviar un correo electrónico a los invitados."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"crear fuentes de ubicación de prueba"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Crea fuentes de ubicación de prueba. Las aplicaciones maliciosas pueden utilizarlo para invalidar la ubicación o el estado que arrojen las fuentes de ubicación real, como GPS o proveedores de red."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"acceder a comandos adicionales del proveedor del lugar"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Admite que la aplicación monte y desmonte filesystems para obtener almacenamiento extraíble."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"espacio de almacenamiento externo del formato"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Admite que la aplicación formatee el espacio de almacenamiento extraíble."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"obtener información sobre el almacenamiento seguro"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Permite que una aplicación obtenga información en el almacenamiento seguro."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"crear almacenamiento seguro"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Permite que la aplicación cree un almacenamiento seguro."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"destruir el almacenamiento seguro"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Permite que una aplicación destruya el almacenamiento seguro."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"montar o desmontar almacenamiento seguro"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Permite que una aplicación monte o desmonte el almacenamiento seguro."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"cambie el nombre del almacenamiento seguro"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Permite que una aplicación cambie el nombre de un almacenamiento seguro."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"vibrador de control"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Admite que la aplicación controle el vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Admite que la aplicación establezca las sugerencias de tamaño del papel tapiz del sistema."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"restablecer el sistema a las configuraciones predeterminadas de fábrica"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Admite una aplicación que restablece el sistema completamente con su configuración de fábrica, y borra todos los datos, las configuraciones y las aplicaciones instaladas."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"establecer la hora"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Permite a una aplicación cambiar la hora del teléfono."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"establecer zona horaria"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Admite una aplicación que cambia la zona horaria del teléfono."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"actuar como cuenta, administrador o servicio"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"escribir configuración del Nombre del punto de acceso"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Admite una aplicación que modifica la configuración de APN, como el proxy y el puerto de cualquier APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"cambiar la conectividad de la red"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Admite una aplicación que cambia la conectividad de red del estado."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Permite que una aplicación cambie el estado de la conectividad de red."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Cambiar la conectividad de anclaje a red"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Permite que una aplicación cambie el estado de la conectividad de red del anclaje."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiar la configuración del uso de datos del fondo"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Admite una aplicación que cambia la configuración del uso de datos del fondo."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"ver el estado de Wi-Fi"</string>
@@ -389,8 +419,20 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Admite una aplicación que escribe palabras nuevas en el diccionario del usuario."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/suprimir el contenido de la tarjeta SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Admite que una aplicación escriba en la tarjeta SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Acceder al sistema de archivos caché"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que una aplicación lea y escriba el sistema de archivos caché."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar la contraseña"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restringe los tipos de contraseñas que puedes utilizar."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Observar los intentos de acceso"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Intentos fallidos del control para acceder al dispositivo para realizar alguna acción."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Restablecer contraseña"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Forzar un nuevo valor para tu contraseña, el administrador deberá enviártelo antes de poder acceder."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Provocar el bloqueo"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Controlar cuando se bloquee el dispositivo, requiere que vuelvas a ingresar tu contraseña."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Realizar un reestablecimiento de fábrica y borrar todos tus datos sin ninguna confirmación."</string>
<string-array name="phoneTypes">
- <item msgid="8901098336658710359">"Página principal"</item>
+ <item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Celular"</item>
<item msgid="7897544654242874543">"Trabajo"</item>
<item msgid="1103601433382158155">"Fax laboral"</item>
@@ -400,19 +442,19 @@
<item msgid="9192514806975898961">"Personalización"</item>
</string-array>
<string-array name="emailAddressTypes">
- <item msgid="8073994352956129127">"Página principal"</item>
+ <item msgid="8073994352956129127">"Casa"</item>
<item msgid="7084237356602625604">"Trabajo"</item>
<item msgid="1112044410659011023">"Otros"</item>
<item msgid="2374913952870110618">"Personalización"</item>
</string-array>
<string-array name="postalAddressTypes">
- <item msgid="6880257626740047286">"Página principal"</item>
+ <item msgid="6880257626740047286">"Casa"</item>
<item msgid="5629153956045109251">"Trabajo"</item>
<item msgid="4966604264500343469">"Otros"</item>
<item msgid="4932682847595299369">"Personalización"</item>
</string-array>
<string-array name="imAddressTypes">
- <item msgid="1738585194601476694">"Pág. ppal."</item>
+ <item msgid="1738585194601476694">"Casa"</item>
<item msgid="1359644565647383708">"Trabajo"</item>
<item msgid="7868549401053615677">"Otros"</item>
<item msgid="3145118944639869809">"Personalización"</item>
@@ -433,7 +475,7 @@
<item msgid="1648797903785279353">"Jabber"</item>
</string-array>
<string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizado"</string>
- <string name="phoneTypeHome" msgid="2570923463033985887">"Página principal"</string>
+ <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string>
<string name="phoneTypeMobile" msgid="6501463557754751037">"Celular"</string>
<string name="phoneTypeWork" msgid="8863939667059911633">"Trabajo"</string>
<string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax laboral"</string>
@@ -457,16 +499,16 @@
<string name="eventTypeAnniversary" msgid="3876779744518284000">"Aniversario"</string>
<string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string>
<string name="emailTypeCustom" msgid="8525960257804213846">"Personalizado"</string>
- <string name="emailTypeHome" msgid="449227236140433919">"Página principal"</string>
+ <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string>
<string name="emailTypeWork" msgid="3548058059601149973">"Trabajo"</string>
<string name="emailTypeOther" msgid="2923008695272639549">"Otro"</string>
<string name="emailTypeMobile" msgid="119919005321166205">"Celular"</string>
<string name="postalTypeCustom" msgid="8903206903060479902">"Personalizado"</string>
- <string name="postalTypeHome" msgid="8165756977184483097">"Página principal"</string>
+ <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string>
<string name="postalTypeWork" msgid="5268172772387694495">"Trabajo"</string>
<string name="postalTypeOther" msgid="2726111966623584341">"Otro"</string>
<string name="imTypeCustom" msgid="2074028755527826046">"Personalizado"</string>
- <string name="imTypeHome" msgid="6241181032954263892">"Página principal"</string>
+ <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string>
<string name="imTypeWork" msgid="1371489290242433090">"Trabajo"</string>
<string name="imTypeOther" msgid="5377007495735915478">"Otro"</string>
<string name="imProtocolCustom" msgid="6919453836618749992">"Personalizado"</string>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ingresar el código de PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ingresar la contraseña para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"¡Código de PIN incorrecto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, presiona el menú y luego 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Presionar Menú para desbloquear."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Extraer el patrón para desbloquear"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Llamada de emergencia"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regresar a llamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lo sentimos, vuelve a intentarlo"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Sonido encendido"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Sonido apagado"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite a la aplicación leer todas las URL que ha visitado el navegador y todos los marcadores del navegador."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir historial y marcadores del navegador"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite a una aplicación modificar el historial y los marcadores del navegador almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar tus datos."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar los permisos de ubicación geográfica del navegador"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que una aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizarlos para permitir el envío de información sobre la ubicación a sitos web de forma arbitraria."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Quieres recordar esta contraseña en el navegador?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no."</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recuerda"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"hace 1 hora"</item>
<item quantity="other" msgid="2467273239587587569">"hace <xliff:g id="COUNT">%d</xliff:g> horas"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Últimos <xliff:g id="COUNT">%d</xliff:g> días"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Último mes"</string>
+ <string name="older" msgid="5211975022815554840">"Antiguos"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"ayer"</item>
<item quantity="other" msgid="2479586466153314633">"hace <xliff:g id="COUNT">%d</xliff:g> días"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"semanas"</string>
<string name="year" msgid="4001118221013892076">"año"</string>
<string name="years" msgid="6881577717993213522">"años"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Los días de semana (lunes a viernes)"</string>
- <string name="daily" msgid="5738949095624133403">"Diariamente"</string>
- <string name="weekly" msgid="983428358394268344">"Semanalmente el día <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Mensual"</string>
- <string name="yearly" msgid="1519577999407493836">"Anual"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"No se puede reproducir el video"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Lo sentimos, este video no es válido para las transmisiones a este dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Lo sentimos, no se puede reproducir este video."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string>
<string name="report" msgid="4060218260984795706">"Notificar"</string>
<string name="wait" msgid="7147118217226317732">"Espera"</string>
- <string name="debug" msgid="9103374629678531849">"Depurar"</string>
<string name="sendText" msgid="5132506121645618310">"Selecciona una acción para el texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
<string name="volume_music" msgid="5421651157138628171">"Volumen de los medios"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"No se requieren permisos"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ocultar"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todos"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Cargando…"</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Almacenamiento masivo USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"conectado al USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Has conectado tu teléfono a tu computadora a través de USB. Selecciona \"Montar\" si deseas copiar archivos entre tu computadora y la tarjeta SD de tu teléfono."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Montar"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"No montar"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Has conectado tu teléfono a tu computadora mediante USB. Selecciona el botón a continuación si deseas copiar los archivos entre tu computadora y la tarjeta SD de Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar el almacenamiento USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Hay un problema para utilizar tu tarjeta SD en el almacenamiento USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"conectado al USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleccionar para copiar archivos desde o hacia tu computadora."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Apagar el almacenamiento USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleccionar para desactivar el almacenamiento USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Apagar el almacenamiento USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Antes de desactivar el almacenamiento USB, asegúrate de haberlo desmontado en el servidor USB al seleccionar \"Desactivar\"."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Apagar"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Cancelar"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Se ha producido un problema al desactivar el almacenamiento USB. Verifica para asegurarte de haber desmontado el servidor USB, luego vuelve a intentarlo."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Almacenamiento USB en uso"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"expulsado\") la tarjeta SD de Android de tu computadora."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar el almacenamiento USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de haber desmontado el host USB, luego vuelve a intentarlo."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar el almacenamiento USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si activas el almacenamiento USB, algunas aplicaciones que estás usando se detendrán y es posible que no estén disponibles hasta que desactives el almacenamiento USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Error en el funcionamiento del USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"Aceptar"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de tu tarjeta."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"No se encontraron actividades coincidentes"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar la estadística de uso de los componentes"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite la modificación de estadísticas recopiladas sobre el uso de componentes. Las aplicaciones normales no deben utilizarlo."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Permite invocar el servicio de contenedor predeterminado para copiar el contenido. No se utiliza con las aplicaciones normales."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Permite invocar el servicio de contenedor predeterminado para copiar el contenido. No se utiliza con las aplicaciones normales."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Presiona dos veces para obtener el control del zoom"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Error al aumentar el control"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Ir"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Crear contacto "\n"con <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no verificado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones enumeradas requieren permiso para acceder a las credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g> ¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones enumeradas requieren permiso para acceder a las <xliff:g id="TYPE">%1$s</xliff:g> credenciales de inicio de sesión para la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>.¿Deseas otorgar este permiso? Si es así, el sistema recordará tu respuesta y no volverá a solicitarla."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Las siguientes aplicaciones requieren autorización para acceder a tu cuenta ahora y en el futuro."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"¿Deseas permitir esta solicitud?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Solicitud de acceso"</string>
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Denegar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clave previamente compartida según L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificado según L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
+ <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
+ <string name="submit" msgid="1602335572089911941">"Enviar"</string>
+ <string name="description_star" msgid="2654319874908576133">"favorito"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo auto habilitado"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para salir del modo auto"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Conexión de anclaje a red activa"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Amplia utilización de datos móviles"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Límite de datos móviles excedido "</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 1dd9b31..df651f2 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"El acceso restringido se ha modificado."</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"El servicio de datos está bloqueado."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"El servicio de emergencia está bloqueado."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"El servicio de voz y SMS está bloqueado."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Todos los servicios de voz y SMS están bloqueados."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"El servicio de voz está bloqueado."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Todos los servicios de voz están bloqueados."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"El servicio de SMS está bloqueado."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Los servicios de voz y de datos están bloqueados."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Todos los servicios de voz y de SMS están bloqueados."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Todos los servicios de voz, de datos y de SMS están bloqueados."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voz"</string>
<string name="serviceClassData" msgid="872456782077937893">"Datos"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Apagar"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Apagando..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"El teléfono se apagará."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Reciente"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"No hay aplicaciones recientes"</string>
<string name="global_actions" msgid="2406416831541615258">"Opciones del teléfono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueo de pantalla"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite que una aplicación active la depuración de otra aplicación. Las aplicaciones malintencionadas pueden utilizar este permiso para desactivar otras aplicaciones."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"cambiar la configuración de la interfaz de usuario"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Permite que una aplicación cambie la configuración actual como, por ejemplo, la configuración local o el tamaño de fuente general."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"reiniciar otras aplicaciones"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Permite que una aplicación reinicie de forma forzosa otras aplicaciones."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"habilitar modo coche"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Permite que una aplicación habilite el modo coche."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"interrumpir procesos en segundo plano"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Permite interrumpir los procesos en segundo plano de otras aplicaciones, aunque no exista poco espacio en la memoria."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"forzar la detención de otras aplicaciones"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Permite que una aplicación detenga de forma forzosa otras aplicaciones."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"forzar el cierre de la aplicación"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Permite que una aplicación fuerce a cualquier actividad en segundo plano a cerrarse y volver a la pantalla anterior. No debería ser necesario nunca para las aplicaciones normales."</string>
<string name="permlab_dump" msgid="1681799862438954752">"recuperar estado interno del sistema"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Permite la modificación de estadísticas recopiladas sobre la batería. No está destinado al uso por parte de aplicaciones normales."</string>
<string name="permlab_backup" msgid="470013022865453920">"controlar las copias de seguridad y las restauraciones del sistema"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Permite que la aplicación controle el mecanismo de copia de seguridad y restauración del sistema. Este permiso no está destinado a aplicaciones normales."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"crear una copia de seguridad de los datos de la aplicación y restaurarlos"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Permite que la aplicación participe en el mecanismo de copia de seguridad y restauración del sistema."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"mostrar ventanas no autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Permite la creación de ventanas destinadas al uso por parte de la interfaz de usuario interna del sistema. No está destinado al uso por parte de aplicaciones normales."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"mostrar alertas de nivel del sistema"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Permite enlazar con la interfaz de nivel superior de un método de introducción de texto. No debe ser necesario para las aplicaciones normales."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"enlazar con un fondo de pantalla"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Permite enlazar con la interfaz de nivel superior de un fondo de pantalla. No debe ser necesario para las aplicaciones normales."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interactuar con el administrador de un dispositivo"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Permite enviar intentos a un administrador de dispositivos. Este permiso nunca debería ser necesario para las aplicaciones normales."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"cambiar orientación de la pantalla"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Permite que una aplicación cambie la rotación de la pantalla en cualquier momento. No debería ser necesario nunca para las aplicaciones normales."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"enviar señales Linux a aplicaciones"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Permite que una aplicación instale paquetes Android nuevos o actualizados. Las aplicaciones malintencionadas pueden utilizar este permiso para añadir aplicaciones nuevas con permisos arbitrariamente potentes."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"eliminar todos los datos de caché de la aplicación"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que una aplicación libere espacio de almacenamiento en el teléfono mediante la eliminación de archivos en el directorio de caché de la aplicación. El acceso al proceso del sistema suele estar muy restringido."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicaciones"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que una aplicación mueva los recursos de aplicaciones de un medio interno a otro externo y viceversa."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"leer archivos de registro del sistema"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Permite que una aplicación lea los distintos archivos de registro del sistema. Con este permiso, la aplicación puede ver información general sobre las acciones que realiza el usuario con el teléfono, pero los registros no deberían contener información personal o privada."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"leer/escribir en los recursos propiedad del grupo de diagnóstico"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que una aplicación modifique los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del propietario."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que una aplicación lea los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para leer los datos del propietario del teléfono."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"leer datos de calendario"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"leer eventos de calendario"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que una aplicación lea todos los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus eventos de calendario a otras personas."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"escribir datos de calendario"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Permite que una aplicación modifique los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de calendario."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"añadir o modificar eventos de calendario y enviar mensajes de correo electrónico a los invitados"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Permite que una aplicación añada o modifique los eventos de tu calendario, que puede enviar mensajes de correo electrónico a los invitados. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus eventos de calendario o para enviar mensajes a los invitados."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"simular fuentes de ubicación para prueba"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Crear fuentes de origen simuladas para realizar pruebas. Las aplicaciones malintencionadas pueden utilizar este permiso para sobrescribir la ubicación o el estado devueltos por orígenes de ubicación reales, tales como los proveedores de red o GPS."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"acceder a comandos de proveedor de ubicación adicional"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Permite que las aplicaciones activen y desactiven sistemas de archivos para un almacenamiento extraíble."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatear almacenamiento externo"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Permite a la aplicación formatear un almacenamiento extraíble."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"obtener información sobre el almacenamiento seguro"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Permite que la aplicación obtenga información sobre el almacenamiento seguro."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"crear almacenamiento seguro"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Permite que la aplicación cree un almacenamiento seguro."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"destruir almacenamiento seguro"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Permite que la aplicación destruya el almacenamiento seguro."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"activar/desactivar almacenamiento seguro"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Permite que la aplicación active o desactive el almacenamiento seguro."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"cambiar nombre de almacenamiento seguro"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Permite que la aplicación cambie el nombre del almacenamiento seguro."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"controlar vibración"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que la aplicación controle la función de vibración."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Permite que la aplicación establezca el tamaño del fondo de pantalla del sistema."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"restablecer el sistema a los valores predeterminados de fábrica"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Permite que una aplicación restablezca por completo el sistema a su configuración de fábrica, borrando todos los datos, la configuración y las aplicaciones instaladas."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"establecer hora"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Permite que una aplicación cambie la hora del reloj del teléfono."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"establecer zona horaria"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Permite que una aplicación cambie la zona horaria del teléfono."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"actuar como servicio de administrador de cuentas"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"escribir la configuración de nombre de punto de acceso"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Permite que una aplicación modifique los valores de configuración de un APN como, por ejemplo, el proxy y el puerto de cualquier APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"cambiar la conectividad de red"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Permite que una aplicación cambie la conectividad de red de estado."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Permite que una aplicación cambie el estado de la conectividad de red."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"cambiar conectividad de anclaje a red"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Permite que una aplicación cambie el estado de la conectividad de red de anclaje."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiar configuración de uso de datos de referencia"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Permite a una aplicación cambiar la configuración de uso de los datos de referencia."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"ver estado de la conectividad Wi-Fi"</string>
@@ -386,9 +416,21 @@
<string name="permlab_readDictionary" msgid="432535716804748781">"leer diccionario definido por el usuario"</string>
<string name="permdesc_readDictionary" msgid="1082972603576360690">"Permite a una aplicación leer cualquier frase, palabra o nombre privado que el usuario haya almacenado en su diccionario."</string>
<string name="permlab_writeDictionary" msgid="6703109511836343341">"escribir en el diccionario definido por el usuario"</string>
- <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite a una aplicación escribir palabras nuevas en el diccionario del usuario."</string>
+ <string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite a una aplicación escribir palabras nuevas en el diccionario de usuario."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/eliminar contenido de la tarjeta SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que una aplicación escriba en la tarjeta SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"acceder al sistema de archivos almacenado en caché"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que una aplicación lea y escriba el sistema de archivos almacenado en caché."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar opciones de contraseña"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Permite restringir los tipos de contraseñas que puede utilizar el usuario."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Controlar intentos de acceso"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Se ha producido un error al intentar controlar el acceso al dispositivo para realizar una acción."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Restablecer contraseña"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Permite forzar la contraseña para establecer un valor nuevo que el administrador deberá proporcionar al usuario para poder acceder."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Forzar bloqueo"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Permite controlar el momento de bloqueo del dispositivo solicitando al usuario que vuelva a introducir la contraseña."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Permite realizar un restablecimiento de fábrica eliminando todos los datos sin confirmación."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Móvil"</item>
@@ -406,7 +448,7 @@
<item msgid="2374913952870110618">"Personalizar"</item>
</string-array>
<string-array name="postalAddressTypes">
- <item msgid="6880257626740047286">"Página principal"</item>
+ <item msgid="6880257626740047286">"Casa"</item>
<item msgid="5629153956045109251">"Trabajo"</item>
<item msgid="4966604264500343469">"Otra"</item>
<item msgid="4932682847595299369">"Personalizar"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduce el código PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introducir contraseña para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"El código PIN es incorrecto."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pulsa la tecla de menú para desbloquear la pantalla."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Dibujar patrón de desbloqueo"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Llamada de emergencia"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Volver a llamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Inténtalo de nuevo"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,7 +547,7 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Falta la tarjeta SIM"</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"No se ha insertado ninguna tarjeta SIM en el teléfono."</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"Inserta una tarjeta SIM."</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"Sólo llamadas de emergencia"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"Solo llamadas de emergencia"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"Bloqueada para la red"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"La tarjeta SIM está bloqueada con el código PUK."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"Consulta la guía del usuario o ponte en contacto con el servicio de atención al cliente."</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Activar sonido"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Desactivar sonido"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que la aplicación lea todas las URL que ha visitado el navegador y todos sus marcadores."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"escribir en marcadores y en el historial del navegador"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que una aplicación modifique la información de los marcadores o del historial del navegador almacenada en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del navegador."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar los permisos de ubicación geográfica del navegador"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que una aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones malintencionadas pueden utilizar este permiso para permitir el envío de información sobre la ubicación a sitios web arbitrarios."</string>
<string name="save_password_message" msgid="767344687139195790">"¿Deseas que el navegador recuerde esta contraseña?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ahora no"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Recordar"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"Hace 1 hora"</item>
<item quantity="other" msgid="2467273239587587569">"Hace <xliff:g id="COUNT">%d</xliff:g> horas"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Últimos <xliff:g id="COUNT">%d</xliff:g> días"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"El mes pasado"</string>
+ <string name="older" msgid="5211975022815554840">"Anterior"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"ayer"</item>
<item quantity="other" msgid="2479586466153314633">"Hace <xliff:g id="COUNT">%d</xliff:g> días"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"semanas"</string>
<string name="year" msgid="4001118221013892076">"año"</string>
<string name="years" msgid="6881577717993213522">"años"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Todos los días laborables (Lun-Vie)"</string>
- <string name="daily" msgid="5738949095624133403">"Diariamente"</string>
- <string name="weekly" msgid="983428358394268344">"Semanalmente, el <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Mensualmente"</string>
- <string name="yearly" msgid="1519577999407493836">"Anualmente"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"No se puede reproducir el vídeo."</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Este vídeo no se puede transmitir al dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Este vídeo no se puede reproducir."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string>
<string name="report" msgid="4060218260984795706">"Informe"</string>
<string name="wait" msgid="7147118217226317732">"Esperar"</string>
- <string name="debug" msgid="9103374629678531849">"Depurar"</string>
<string name="sendText" msgid="5132506121645618310">"Seleccionar la opción para compartir"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
<string name="volume_music" msgid="5421651157138628171">"Volumen multimedia"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"No es necesario ningún permiso"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ocultar"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todos"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Cargando..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Almacenamiento USB masivo"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Conectado por USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Has conectado el teléfono al equipo mediante USB. Selecciona \"Activar\" si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Activar"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"No activar"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Has conectado el teléfono al equipo mediante USB. Selecciona el botón situado debajo si deseas copiar archivos entre el equipo y la tarjeta SD del teléfono con Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar almacenamiento USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Se ha producido un problema al intentar utilizar la tarjeta SD para el almacenamiento USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Conectado por USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Para copiar archivos al/desde el equipo"</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desactivar almacenamiento USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleccionar para desactivar USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Desactivar almacenamiento USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Antes de desactivar el almacenamiento USB, asegúrate de haber desactivado el host USB. Selecciona \"Desactivar\" para desactivar el almacenamiento USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Desactivar"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Cancelar"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de que has desactivado el host USB e inténtalo de nuevo."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"El almacenamiento USB está en uso."</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar el almacenamiento USB, asegúrate de haber desmontado (\"retirado\") la tarjeta SD del teléfono con Android del equipo."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar almacenamiento USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Se ha producido un problema al desactivar el almacenamiento USB. Asegúrate de haber desactivado el host USB y, a continuación, vuelve a intentarlo."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar almacenamiento USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si activas el almacenamiento USB, se detendrán algunas aplicaciones que estás utilizando y estas no estarán disponibles hasta que lo desactives."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"No se ha podido realizar la operación USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"Aceptar"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatear tarjeta SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"¿Estás seguro de que quieres formatear la tarjeta SD? Se perderán todos los datos de la tarjeta."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formato"</string>
@@ -769,20 +819,23 @@
<string name="activity_list_empty" msgid="4168820609403385789">"No se ha encontrado ninguna actividad coincidente."</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar estadísticas de uso de componentes"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite la modificación de estadísticas recopiladas sobre el uso de componentes. No está destinado al uso por parte de aplicaciones normales."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Permite invocar el servicio de contenedor predeterminado para copiar contenido. Este permiso no está destinado al uso por parte de aplicaciones normales."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Permite invocar el servicio de contenedor predeterminado para copiar contenido. Este permiso no está destinado al uso por parte de aplicaciones normales."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Da dos toques para acceder al control de zoom."</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Error al aumentar el widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Ir"</string>
<string name="ime_action_search" msgid="658110271822807811">"Buscar"</string>
<string name="ime_action_send" msgid="2316166556349314424">"Enviar"</string>
<string name="ime_action_next" msgid="3138843904009813834">"Siguiente"</string>
- <string name="ime_action_done" msgid="8971516117910934605">"Hecho"</string>
+ <string name="ime_action_done" msgid="8971516117910934605">"Listo"</string>
<string name="ime_action_default" msgid="2840921885558045721">"Ejecutar"</string>
<string name="dial_number_using" msgid="5789176425167573586">"Marcar número"\n"con <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="create_contact_using" msgid="4947405226788104538">"Crear un contacto"\n"a partir de <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seleccionado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"no seleccionado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso de la cuenta <xliff:g id="ACCOUNT">%1$s</xliff:g> desde <xliff:g id="APPLICATION">%2$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Las aplicaciones de la lista están solicitando permiso para acceder a las credenciales de acceso (<xliff:g id="TYPE">%1$s</xliff:g>) de la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> desde <xliff:g id="APPLICATION">%3$s</xliff:g>. ¿Deseas conceder este permiso? En tal caso, se recordará tu respuesta y no se te volverá a preguntar."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Las siguientes aplicaciones solicitan permiso para acceder a tu cuenta ahora y en el futuro."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"¿Quieres permitir esta solicitud?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Solicitud de acceso"</string>
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Denegar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Permiso solicitado"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de nivel 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Red privada virtual L2TP/IPSec basada en clave compartida previamente"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Red privada virtual L2TP/IPSec basada en certificado"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
+ <string name="reset" msgid="2448168080964209908">"Restablecer"</string>
+ <string name="submit" msgid="1602335572089911941">"Enviar"</string>
+ <string name="description_star" msgid="2654319874908576133">"favoritos"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Se ha habilitado el modo coche."</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecciona esta opción para salir del modo coche."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Anclaje a red activo"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Toca para iniciar la configuración."</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Uso elevado datos móv"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toca para más inform sobre uso de datos móv"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Límite datos móv superado"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Toca para más inform sobre uso de datos móv"</string>
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index a11e00d..d672b1e 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"O"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"L\'accès limité a été modifié."</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Le service de données est bloqué."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Le service d\'appel d\'urgence est bloqué."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Le service vocal/SMS est bloqué."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Tous les services vocaux/SMS sont bloqués."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Le service vocal est bloqué."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Tous les services vocaux sont bloqués."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Le service SMS est bloqué."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Les services vocaux/de données sont bloqués."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Les services vocaux/SMS sont bloqués."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Tous les services vocaux/de données/SMS sont bloqués."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voix"</string>
<string name="serviceClassData" msgid="872456782077937893">"Données"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"Télécopie"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Éteindre"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Arrêt en cours..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Votre téléphone va s\'éteindre."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Récentes"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Aucune application récente"</string>
<string name="global_actions" msgid="2406416831541615258">"Options du téléphone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Verrouillage de l\'écran"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permet à une application d\'activer le mode de débogage d\'une autre application. Des applications malveillantes peuvent utiliser cette fonctionnalité pour interrompre d\'autres applications de façon inopinée."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"Modification des paramètres de l\'IU"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Permet à une application de modifier la configuration actuelle (par ex. : la taille de la police générale ou des paramètres régionaux)."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"Démarrage d\'autres applications"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Permet à une application de forcer le lancement d\'autres applications."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"activer le mode voiture"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Permet à une application d\'activer le mode voiture."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"arrêter les processus en arrière-plan"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Permet à une application d\'arrêter les processus en arrière-plan d\'autres d\'applications, même lorsqu\'il n\'y a pas de problème de mémoire."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"forcer l\'arrêt d\'autres applications"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Permet à une application de forcer l\'arrêt d\'autres applications."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"Fermeture forcée de l\'application"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Permet à une application de forcer une autre application exécutée au premier plan à se fermer et à passer en arrière-plan. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_dump" msgid="1681799862438954752">"Vérification de l\'état interne du système"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Autoriser la modification des statistiques de la batterie. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
<string name="permlab_backup" msgid="470013022865453920">"contrôler la sauvegarde et la restauration du système"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Autorise l\'application à contrôler le mécanisme de sauvegarde et de restauration du système. Ne pas utiliser pour les applications standard."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"sauvegarder et rétablir les données de l\'application"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Autorise l\'application à participer au mécanisme de sauvegarde et de restauration du système."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Affichage de fenêtres non autorisées"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Permet de créer des fenêtres conçues pour l\'interface utilisateur du système interne. Les applications normales n\'utilisent pas cette fonctionnalité."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"Affichage d\'alertes système"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Permet au support de se connecter à l\'interface de plus haut niveau d\'un mode de saisie. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"Se fixer sur un fond d\'écran"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Permet au support de se fixer sur l\'interface de plus haut niveau d\'un fond d\'écran. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interagir avec l\'administrateur du périphérique"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Permet à l\'application d\'envoyer des intentions à l\'administrateur du périphérique. Les applications standard ne devraient jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"Changement d\'orientation de l\'écran"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Permet à une application de modifier la rotation de l\'écran à tout moment. Les applications normales ne devraient jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"Envoi de signaux Linux aux applications"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Permet à une application d\'installer des nouveaux paquets de données ou des mises à jour Android. Des applications malveillantes peuvent utiliser cette fonctionnalité pour ajouter de nouvelles applications disposant d\'autorisations anormalement élevées."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"Suppression des données du cache de toutes les applications"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permet à une application de libérer de l\'espace dans la mémoire du téléphone en supprimant des fichiers du répertoire du cache des applications. Cet accès est en général limité aux processus système."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Déplacer des ressources d\'application"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Autorise l\'application à déplacer des ressources d\'application d\'un support interne à un support externe et inversement."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"Lecture des fichiers journaux du système"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Permet à une application de lire les différents fichiers journaux du système afin d\'obtenir des informations générales sur la façon dont vous utilisez votre téléphone, sans pour autant récupérer des informations d\'ordre personnel ou privé."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Lecture/écriture dans les ressources appartenant aux diagnostics"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permet à une application de modifier les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier ces données."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"Lecture des données du propriétaire"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permet à une application de lire les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour lire ces données."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"Lecture des données de l\'agenda"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"lire des événements de l\'agenda"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Permet à une application de lire tous les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer les événements de votre agenda à d\'autres personnes."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"Écriture des données de l\'agenda"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Permet à une application de modifier les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier les données de votre agenda."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"ajouter ou modifier des événements d\'agenda et envoyer des e-mails aux invités"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Autorise les applications à ajouter ou à modifier des événements dans votre agenda, qui pourra envoyer des e-mails aux invités. Des logiciels malveillants peuvent utiliser cette fonctionnalité pour supprimer ou modifier des événements de l\'agenda ou envoyer des e-mails aux invités."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"Création de sources de localisation fictives à des fins de test"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Permet de créer des sources de localisation fictives à des fins de test. Des applications malveillantes peuvent utiliser cette fonctionnalité pour remplacer la position géographique et/ou l\'état fournis par des sources réelles comme le GPS ou les fournisseurs d\'accès."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"Accès aux commandes de fournisseur de position géographique supplémentaires"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Permet à l\'application de monter et démonter des systèmes de fichiers pour des périphériques de stockage amovibles."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"Formatage du périphérique de stockage externe"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Permet à l\'application de formater le périphérique de stockage amovible."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"obtenir des informations sur le stockage sécurisé"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Permet à l\'application d\'obtenir des informations sur le stockage sécurisé."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"créer un stockage sécurisé"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Permet à l\'application de créer un stockage sécurisé."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"détruire le stockage sécurisé"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Permet à l\'application de détruire le stockage sécurisé."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"monter/démonter le stockage sécurisé"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Permet à l\'application de monter/démonter le stockage sécurisé."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"renommer le stockage sécurisé"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Permet à l\'application de renommer le stockage sécurisé."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"Contrôle du vibreur"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permet à l\'application de contrôler le vibreur."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"Contrôle de la lampe de poche"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Permet à une application de définir la taille du fond d\'écran."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"Réinitialisation du système à ses paramètres d\'usine"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Permet à une application de réinitialiser entièrement le système afin de rétablir ses valeurs d\'usine et d\'effacer toutes les données, configurations et applications installées."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"définir l\'heure"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Permet à une application de modifier l\'heure du téléphone."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"Sélection du fuseau horaire"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Permet à l\'application de modifier le fuseau horaire du téléphone."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"Agir en tant que service AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"Écriture des paramètres \"Nom des points d\'accès\""</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Permet à une application de modifier les paramètres APN (Nom des points d\'accès), comme le proxy ou le port de tout APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"Modification de la connectivité du réseau"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Permet à une application de modifier la connectivité du réseau."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Permet à une application de modifier l\'état de la connectivité réseau."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Changer la connectivité du partage de connexion"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Permet à une application de modifier l\'état de la connectivité du partage de connexion."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"modifier le paramètre d\'utilisation des données en arrière-plan"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Permet à une application de modifier le paramètre d\'utilisation des données en arrière-plan."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Affichage de l\'état du Wi-Fi"</string>
@@ -387,8 +417,20 @@
<string name="permdesc_readDictionary" msgid="1082972603576360690">"Permet à une application de lire tous les mots, noms et expressions que l\'utilisateur a pu enregistrer dans son dictionnaire personnel."</string>
<string name="permlab_writeDictionary" msgid="6703109511836343341">"Enregistrement dans le dictionnaire défini par l\'utilisateur"</string>
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permet à une application d\'enregistrer de nouveaux mots dans le dictionnaire personnel de l\'utilisateur."</string>
- <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modifier/supprimer le contenu de la carte SD"</string>
+ <string name="permlab_sdcardWrite" msgid="8079403759001777291">"Modifier/supprimer le contenu de la carte SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Autorise une application à écrire sur la carte SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"accéder au système de fichiers en cache"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permet à une application de lire et d\'écrire dans le système de fichiers en cache."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limiter le mot de passe"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restreint les types de mots de passe que vous êtes autorisé à utiliser."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Surveiller les tentatives de connexion"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Surveille les échecs de connexion au périphérique pour effectuer une action."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Réinitialiser le mot de passe"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Force l\'utilisation d\'un nouveau mot de passe, qui doit vous être communiqué par l\'administrateur pour que vous puissiez vous connecter."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Forcer le verrouillage"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Contrôle le verrouillage du périphérique, avec obligation de saisir à nouveau le mot de passe."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Effacer toutes les données"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Rétablit les paramètres d\'usine, supprimant toutes vos données sans demande de confirmation."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Domicile"</item>
<item msgid="869923650527136615">"Portable"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Saisissez le code PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Saisissez le mot de passe pour procéder au déverrouillage."</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Le code PIN est incorrect !"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Pour débloquer le clavier, appuyez sur \"Menu\" puis sur 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numéro d\'urgence"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Appuyez sur \"Menu\" pour déverrouiller le téléphone."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Dessinez un schéma pour déverrouiller le téléphone"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Appel d\'urgence"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retour à l\'appel"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Combinaison correcte !"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Incorrect. Merci de réessayer."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Chargement (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Débloquer"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Son activé"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Son désactivé"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Effacer"</string>
@@ -549,9 +596,11 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Autorise l\'application à lire toutes les URL auxquelles le navigateur a accédé et tous ses favoris."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"écrire dans l\'historique et les favoris du navigateur"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Autorise une application à modifier l\'historique du navigateur ou les favoris enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonction pour effacer ou modifier les données de votre navigateur."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifier les autorisations de géolocalisation du navigateur"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permet à une application de modifier les autorisations de géolocalisation du navigateur. Les applications malveillantes peuvent se servir de cette fonctionnalité pour envoyer des informations de lieu à des sites Web arbitraires."</string>
<string name="save_password_message" msgid="767344687139195790">"Voulez-vous que le navigateur se souvienne de ce mot de passe ?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Pas maintenant"</string>
- <string name="save_password_remember" msgid="6491879678996749466">"Se souvenir du mot de passe"</string>
+ <string name="save_password_remember" msgid="6491879678996749466">"Mémoriser"</string>
<string name="save_password_never" msgid="8274330296785855105">"Jamais"</string>
<string name="open_permission_deny" msgid="5661861460947222274">"Vous n\'êtes pas autorisé à ouvrir cette page."</string>
<string name="text_copied" msgid="4985729524670131385">"Le texte a été copié dans le presse-papier."</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"il y a 1 heure"</item>
<item quantity="other" msgid="2467273239587587569">"Il y a <xliff:g id="COUNT">%d</xliff:g> heures"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Les <xliff:g id="COUNT">%d</xliff:g> derniers jours"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Le mois dernier"</string>
+ <string name="older" msgid="5211975022815554840">"Préc."</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"hier"</item>
<item quantity="other" msgid="2479586466153314633">"Il y a <xliff:g id="COUNT">%d</xliff:g> jours"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"semaines"</string>
<string name="year" msgid="4001118221013892076">"année"</string>
<string name="years" msgid="6881577717993213522">"années"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Tous les jours ouvrés (lun.- ven.)"</string>
- <string name="daily" msgid="5738949095624133403">"Tous les jours"</string>
- <string name="weekly" msgid="983428358394268344">"Toutes les semaines le <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Tous les mois"</string>
- <string name="yearly" msgid="1519577999407493836">"Tous les ans"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Échec de la lecture de la vidéo"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Désolé, cette vidéo ne peut être lue sur cet appareil."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Désolé, impossible de lire cette vidéo."</string>
@@ -695,14 +744,13 @@
<string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string>
<string name="report" msgid="4060218260984795706">"Rapport"</string>
<string name="wait" msgid="7147118217226317732">"Attendre"</string>
- <string name="debug" msgid="9103374629678531849">"Débogage"</string>
<string name="sendText" msgid="5132506121645618310">"Sélectionner une action pour le texte"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume de la sonnerie"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume"</string>
- <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"Lecture via Bluetooth"</string>
+ <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"Lecture via le Bluetooth"</string>
<string name="volume_music_hint_silent_ringtone_selected" msgid="6158339745293431194">"Sonnerie silencieuse sélectionnée"</string>
<string name="volume_call" msgid="3941680041282788711">"Volume des appels entrants"</string>
- <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volume d\'appels entrants sur Bluetooth"</string>
+ <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volume d\'appels entrants sur le Bluetooth"</string>
<string name="volume_alarm" msgid="1985191616042689100">"Volume"</string>
<string name="volume_notification" msgid="2422265656744276715">"Volume des notifications"</string>
<string name="volume_unknown" msgid="1400219669770445902">"Volume"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Aucune autorisation requise"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Masquer"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Tout afficher"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Chargement..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Stockage de masse USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Connecté à l\'aide d\'un câble USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez Monter pour copier des fichiers de votre ordinateur vers votre carte SD, ou inversement."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Monter"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Ne pas monter"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Vous avez connecté votre téléphone à votre ordinateur à l\'aide d\'un câble USB. Sélectionnez le bouton ci-dessous pour copier des fichiers de votre ordinateur vers la carte SD de votre Android, ou inversement."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activer le périphérique de stockage USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Un problème est survenu lors de l\'utilisation de votre carte SD en tant que périphérique de stockage USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Connecté avec un câble USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Activez pour copier des fichiers vers/de votre ordinateur."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Éteindre le périphérique de stockage USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Sélectionner pour éteindre le périphérique de stockage USB"</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Éteindre le périphérique de stockage USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Avant d\'éteindre le périphérique de stockage USB, assurez-vous d\'avoir désactivé l\'hôte USB. Sélectionnez \"Éteindre\" pour éteindre le périphérique de stockage USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Éteindre"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Annuler"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Un problème est survenu lors de la mise hors tension du périphérique de stockage USB. Assurez-vous que l\'hôte USB a bien été désactivé, puis essayez à nouveau."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Stockage USB en cours d\'utilisation"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Avant de mettre hors tension le stockage USB, assurez-vous d\'avoir désactivé (\"éjecté\") la carte SD de votre téléphone Android à partir de votre ordinateur."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Désactiver le périphérique de stockage USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Un problème est survenu lors de la mise hors tension du périphérique de stockage USB. Assurez-vous que l\'hôte USB a bien été désactivé, puis essayez à nouveau."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activer le périphérique de stockage USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Si vous activez le périphérique de stockage USB, certaines applications que vous utilisez se fermeront et risquent de n\'être de nouveau disponibles qu\'après la désactivation du périphérique."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Échec du fonctionnement USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formater la carte SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Voulez-vous vraiment formater la carte SD ? Toutes les données de cette carte seront perdues."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Aucune activité correspondante trouvée"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"mettre à jour les données statistiques du composant"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permet de modifier les données statistiques collectées du composant. Cette option n\'est pas utilisée par les applications standard."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Permet d\'appeler le service de conteneur par défaut pour copier le contenu. Ne pas utiliser pour les applications standard."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Permet d\'appeler le service de conteneur par défaut pour copier le contenu. Ne pas utiliser pour les applications standard."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Appuyer deux fois pour régler le zoom"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Erreur lors de l\'agrandissement du widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"OK"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Ajouter un contact"\n"en utilisant <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"sélectionné"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non sélectionné"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification du compte <xliff:g id="ACCOUNT">%1$s</xliff:g> depuis <xliff:g id="APPLICATION">%2$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Les applications répertoriées demandent l\'autorisation d\'accéder aux informations d\'identification des <xliff:g id="TYPE">%1$s</xliff:g> associés au compte <xliff:g id="ACCOUNT">%2$s</xliff:g> depuis <xliff:g id="APPLICATION">%3$s</xliff:g>. Souhaitez-vous accorder cette autorisation ? Si vous acceptez, votre choix sera enregistré et cette question ne vous sera plus posée."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Les applications suivantes demandent l\'autorisation d\'accéder à votre compte à partir de maintenant."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Voulez-vous autoriser cette demande ?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Demande d\'accès"</string>
<string name="allow" msgid="7225948811296386551">"Autoriser"</string>
<string name="deny" msgid="2081879885755434506">"Refuser"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorisation demandée"</string>
@@ -794,6 +847,18 @@
<string name="chooser_wallpaper" msgid="7873476199295190279">"Changer de fond d\'écran"</string>
<string name="pptp_vpn_description" msgid="2688045385181439401">"Protocole de tunnelisation point-à-point"</string>
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocole de tunnelisation de niveau 2"</string>
- <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clé pré-partagée basée sur L2TP/IPSec VPN"</string>
- <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificat basé sur L2TP/IPSec VPN"</string>
+ <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basé sur une clé pré-partagée"</string>
+ <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basé sur un certificat"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
+ <string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
+ <string name="submit" msgid="1602335572089911941">"Envoyer"</string>
+ <string name="description_star" msgid="2654319874908576133">"favori"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mode Voiture activé"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Sélectionnez cette option pour quitter le mode Voiture."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Partage de connexion activé"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Toucher pour configurer"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilisation importante des données mobiles"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Touchez pour en savoir plus sur l\'utilisation des données mobiles."</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Plafond d\'utilisation des données mobiles dépassé"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Touchez pour en savoir plus sur l\'utilisation des données mobiles."</string>
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index dd93286..dd081ec 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Accesso limitato modificato"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Il servizio dati è bloccato."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Il servizio di emergenza è bloccato."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Il servizio vocale/SMS è bloccato."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Tutti i servizi vocali/SMS sono bloccati."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Il servizio vocale è bloccato."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Tutti i servizi vocali sono bloccati."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Il servizio SMS è bloccato."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"I servizi vocali/dati sono bloccati."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"I servizi vocali/SMS sono bloccati."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Tutti i servizi vocali/dati/SMS sono bloccati."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voce"</string>
<string name="serviceClassData" msgid="872456782077937893">"Dati"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Spegni"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Spegnimento..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Il telefono verrà spento."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Recenti"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Nessuna applicazione recente."</string>
<string name="global_actions" msgid="2406416831541615258">"Opzioni telefono"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Blocco schermo"</string>
@@ -133,8 +143,8 @@
<string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Audio non attivo"</string>
<string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Audio attivo"</string>
<string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modalità aereo"</string>
- <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Disattiva modalità aereo"</string>
- <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Disattiva modalità aeereo"</string>
+ <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modalità aereo attiva"</string>
+ <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modalità aereo non attiva"</string>
<string name="safeMode" msgid="2788228061547930246">"Modalità provvisoria"</string>
<string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string>
<string name="permgrouplab_costMoney" msgid="5429808217861460401">"Servizi che prevedono un costo"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Consente a un\'applicazione di attivare il debug per un\'altra applicazione. Le applicazioni dannose possono sfruttare questa possibilità per interrompere altre applicazioni."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"modifica impostazioni UI"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Consente a un\'applicazione di modificare la configurazione corrente, come le dimensioni dei caratteri locali o complessive."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"riavvio altre applicazioni"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Consente a un\'applicazione di riavviare forzatamente altre applicazioni."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"abilitazione modalità auto"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Consente a un\'applicazione di abilitare la modalità auto."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"interruzione processi in background"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Consente a un\'applicazione di terminare i processi in background di altre applicazioni, anche se la memoria non è insufficiente."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"interruzione forzata di altre applicazioni"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Consente a un\'applicazione di interrompere forzatamente altre applicazioni."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"chiusura forzata dell\'applicazione"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Consente a un\'applicazione di forzare la chiusura di attività in primo piano. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
<string name="permlab_dump" msgid="1681799862438954752">"recupero stato interno del sistema"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Consente la modifica delle statistiche sulla batteria raccolte. Da non usare per normali applicazioni."</string>
<string name="permlab_backup" msgid="470013022865453920">"controllo del backup di sistema e ripristino"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Consente all\'applicazione di controllare il meccanismo di backup e ripristino del sistema. Da non usare per normali applicazioni."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"effettuare il backup e il ripristino dei dati dell\'applicazione"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Consente all\'applicazione di partecipare al meccanismo di backup e ripristino del sistema."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"visualizzazione finestre non autorizzate"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Consente la creazione di finestre destinate all\'uso nell\'interfaccia utente di sistema interna. Da non usare per normali applicazioni."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"visualizzazione avvisi di sistema"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Consente l\'associazione all\'interfaccia principale di un metodo di inserimento. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"associazione a sfondo"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Consente l\'associazione di uno sfondo all\'interfaccia principale. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interazione con un amministratore dispositivo"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Consente l\'invio di intent a un amministratore del dispositivo. L\'autorizzazione non deve mai essere necessaria per le normali applicazioni."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"modifica orientamento dello schermo"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Consente a un\'applicazione di cambiare la rotazione dello schermo in qualsiasi momento. Non dovrebbe essere mai necessario per le normali applicazioni."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"invio segnali Linuz alle applicazioni"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Consente a un\'applicazione di installare nuovi pacchetti Android o aggiornamenti. Le applicazioni dannose possono sfruttare questa possibilità per aggiungere nuove applicazioni con potenti autorizzazioni arbitrarie."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"eliminazione dati della cache applicazioni"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Consente a un\'applicazione di liberare spazio sul telefono eliminando file nella directory della cache dell\'applicazione. L\'accesso è generalmente limitato a processi di sistema."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Spostare risorse dell\'applicazione"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Consente a un\'applicazione di spostare risorse applicative da supporti interni a esterni e viceversa."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"lettura file di registro sistema"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Consente a un\'applicazione di leggere vari file di registro del sistema per trovare informazioni generali sulle operazioni effettuate con il telefono. Tali file non dovrebbero contenere informazioni personali o riservate."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lettura/scrittura risorse di proprietà di diag"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Consente a un\'applicazione di modificare i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare tali dati."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"lettura dati proprietario"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Consente a un\'applicazione di leggere i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per leggere tali dati."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"lettura dati di calendario"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"leggere eventi di calendario"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Consente la lettura da parte di un\'applicazione di tutti gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i tuoi eventi di calendario ad altre persone."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"scrittura dati di calendario"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Consente a un\'applicazione di modificare gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati del calendario."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"aggiungere o modificare eventi di calendario e inviare email agli invitati"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Consente a un\'applicazione di aggiungere o modificare gli eventi nel tuo calendario, eventualmente inviando email agli invitati. Le applicazioni dannose possono utilizzare questa autorizzazione per cancellare o modificare i tuoi eventi di calendario per inviare email agli invitati."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"fonti di localizzazione fittizie per test"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Creare fonti di localizzazione fittizie per test. Le applicazioni dannose possono sfruttare questa possibilità per sostituire la posizione e/o lo stato restituito da reali fonti di localizzazione come GPS o provider di rete."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"accesso a comandi aggiuntivi del provider di localizz."</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Consente montaggio e smontaggio da parte dell\'applicazione dei filesystem degli archivi rimovibili."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formattazione archivio esterno"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Consente all\'applicazione di formattare l\'archivio rimovibile."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"recupero di informazioni sull\'archiviazione protetta"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Consente all\'applicazione di ottenere informazioni sull\'archiviazione protetta."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"creazione archiviazione protetta"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Consente all\'applicazione di creare un\'archiviazione protetta."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"eliminazione archiviazione protetta"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Consente all\'applicazione di eliminare l\'archiviazione protetta."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"montaggio/smontaggio archiviazione protetta"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Consente all\'applicazione di montare/smontare l\'archiviazione protetta."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"rinominazione archiviazione protetta"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Consente all\'applicazione di rinominare l\'archiviazione protetta."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"controllo vibrazione"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Consente all\'applicazione di controllare la vibrazione."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controllo flash"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Consente all\'applicazione di impostare i suggerimenti per le dimensioni dello sfondo del sistema."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"ripristino impostazioni predef. di fabbrica"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Consente a un\'applicazione di ripristinare le impostazioni di fabbrica del sistema, eliminando tutti i dati, le configurazioni e le applicazioni installate."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"impostazione ora"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Consente a un\'applicazione di modificare l\'ora dell\'orologio del telefono."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"impostazione fuso orario"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Consente a un\'applicazione di modificare il fuso orario del telefono."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"agire da AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"scrittura impostazioni APN"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Consente a un\'applicazione di modificare le impostazioni APN, come proxy e porta di qualsiasi APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"modifica connettività di rete"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Consente a un\'applicazione di modificare lo stato di connettività di rete."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Consente a un\'applicazione di modificare lo stato di connettività di rete."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"modificare la connettività tethering"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Consente a un\'applicazione di modificare lo stato di connettività di rete tethering."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"cambiare l\'impostazione di utilizzo dei dati in background"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Consente a un\'applicazione di cambiare l\'impostazione di utilizzo dei dati in background."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"visualizzazione stato Wi-Fi"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Consente a un\'applicazione di scrivere nuove parole nel dizionario utente."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificare/eliminare i contenuti della scheda SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Consente a un\'applicazione di scrivere sulla scheda SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"accesso al filesystem nella cache"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Consente a un\'applicazione di leggere e scrivere il filesystem nella cache."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limita password"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Limita i tipi di password che sei autorizzato a utilizzare."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Controlla i tentativi di accesso"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitora i tentativi non riusciti di accedere al dispositivo per eseguire un\'azione."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Reimposta password"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Forza un nuovo valore per la password, chiedendo all\'amministratore di concedertelo prima di poter eseguire l\'accesso."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Forza blocco"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Controlla quando il dispositivo si blocca, chiedendoti di reinserire la password."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Cancella tutti i dati"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Esegui un ripristino di fabbrica, eliminando tutti i tuoi dati senza conferma."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Cellulare"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Inserisci password per sbloccare"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Codice PIN errato."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Per sbloccare, premi Menu, poi 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numero di emergenza"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Premi Menu per sbloccare."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Traccia la sequenza di sblocco"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Chiamata di emergenza"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Torna a chiamata"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Corretta."</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Riprova"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Sblocca"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Audio attivato"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Audio disattivato"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Cancella"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Consente all\'applicazione di leggere tutti gli URL visitati e tutti i segnalibri del browser."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"creazione cronologia e segnalibri del browser"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Consente a un\'applicazione di modificare la cronologia o i segnalibri del browser memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati del browser."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifica le autorizzazioni di localizzazione geografica del browser"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Consente a un\'applicazione di modificare le autorizzazioni di localizzazione geografica del browser. Le applicazioni dannose possono utilizzare questa autorizzazione per consentire l\'invio di informazioni sulla posizione a siti web arbitrari."</string>
<string name="save_password_message" msgid="767344687139195790">"Memorizzare la password nel browser?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Non ora"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Memorizza"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 ora fa"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> ore fa"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Ultimi <xliff:g id="COUNT">%d</xliff:g> giorni"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Ultimo mese"</string>
+ <string name="older" msgid="5211975022815554840">"Precedente"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"ieri"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> giorni fa"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"settimane"</string>
<string name="year" msgid="4001118221013892076">"anno"</string>
<string name="years" msgid="6881577717993213522">"anni"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Ogni giorno feriale (lun-ven)"</string>
- <string name="daily" msgid="5738949095624133403">"Quotidianamente"</string>
- <string name="weekly" msgid="983428358394268344">"Ogni settimana il <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Mensilmente"</string>
- <string name="yearly" msgid="1519577999407493836">"Annualmente"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Impossibile riprodurre il video"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Spiacenti, questo video non è valido per lo streaming su questo dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Spiacenti. Impossibile riprodurre il video."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Termina"</string>
<string name="report" msgid="4060218260984795706">"Segnala"</string>
<string name="wait" msgid="7147118217226317732">"Attendi"</string>
- <string name="debug" msgid="9103374629678531849">"Debug"</string>
<string name="sendText" msgid="5132506121645618310">"Selezione un\'opzione di invio"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume suoneria"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume app. multimediali"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Nessuna autorizzazione richiesta"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Nascondi"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Mostra tutto"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Caricamento..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Archiviazione di massa USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB collegata"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Il telefono è stato collegato al computer tramite USB. Seleziona \"Collega\" se desideri copiare file tra il computer e la scheda SD del telefono."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Collega"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Non collegare"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Il telefono è stato collegato al computer tramite USB. Seleziona il pulsante sottostante se desideri copiare file tra il computer e la scheda SD di Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Attiva archivio USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Problema di utilizzo della scheda SD per l\'archiviazione USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB collegata"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleziona per copiare file sul/dal tuo computer."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Disattiva archivio USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Seleziona per disattivare archivio USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Disattiva archivio USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Prima di disattivare l\'archivio USB, verifica di aver smontato l\'host USB. A tale scopo, seleziona \"Disattiva\"."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Disattiva"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Annulla"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Abbiamo riscontrato un problema durante la disattivazione dell\'archivio USB. Verifica di aver smontato l\'host USB e riprova."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Archiviazione USB in uso"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Prima di disattivare l\'archiviazione USB, assicurati di avere smontato (\"espulso\") la scheda SD di Android dal computer."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Disattiva archiviazione USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Si è verificato un problema durante la disattivazione dell\'archiviazione USB. Verifica di avere smontato l\'host USB e riprova."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Attiva archivio USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se attivi l\'archivio USB, alcune applicazioni in uso si bloccheranno e potrebbero risultare non disponibili finché non disattiverai l\'archivio USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Operazione USB non riuscita"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatta scheda SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Formattare la scheda SD? Tutti i dati sulla scheda verranno persi."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatta"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Nessuna attività corrispondente trovata"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aggiornare le statistiche di utilizzo dei componenti"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Consente la modifica delle statistiche di utilizzo dei componenti raccolte. Da non usare per normali applicazioni."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Consente di invocare il servizio contenitore predefinito per la copia di contenuti. Da non usare per normali applicazioni."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Consente di invocare il servizio contenitore predefinito per la copia di contenuti. Da non usare per normali applicazioni."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tocca due volte per il comando dello zoom"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Errore durante l\'ampliamento del widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Vai"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Crea contatto"\n"utilizzando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selezionato"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"non selezionato"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso per l\'account <xliff:g id="ACCOUNT">%1$s</xliff:g> da <xliff:g id="APPLICATION">%2$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Le applicazioni elencate richiedono l\'autorizzazione per accedere alle credenziali di accesso <xliff:g id="TYPE">%1$s</xliff:g> per l\'account <xliff:g id="ACCOUNT">%2$s</xliff:g> da <xliff:g id="APPLICATION">%3$s</xliff:g>. Concedere questa autorizzazione? In tal caso la tua risposta verrà memorizzata e questa domanda non ti verrà più posta."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Le seguenti applicazioni richiedono l\'autorizzazione per accedere al tuo account, adesso e in futuro."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Accettare la richiesta?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Richiesta di accesso"</string>
<string name="allow" msgid="7225948811296386551">"Consenti"</string>
<string name="deny" msgid="2081879885755434506">"Nega"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorizzazione richiesta"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocollo di tunneling livello 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basata su chiave precondivisa"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basata su certificato"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
+ <string name="reset" msgid="2448168080964209908">"Reimposta"</string>
+ <string name="submit" msgid="1602335572089911941">"Invia"</string>
+ <string name="description_star" msgid="2654319874908576133">"preferiti"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modalità automobile attivata"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleziona per uscire dalla modalità automobile."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Tethering attivo"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Tocca per configurare"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilizzo dati cell. elevato"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tocca per informazioni sull\'utilizzo dati cell."</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Limite dati cell. superato"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Tocca per informazioni sull\'utilizzo dati cell."</string>
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 9625068..7fcef92 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"アクセス制é™ãŒå¤‰æ›´ã•ã‚Œã¾ã—ãŸ"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"データサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"緊急サービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"音声/SMSサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"ã™ã¹ã¦ã®éŸ³å£°/SMSサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"音声サービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"ã™ã¹ã¦ã®éŸ³å£°ã‚µãƒ¼ãƒ“スãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMSサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"音声/データサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"音声/SMSサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"ã™ã¹ã¦ã®éŸ³å£°/データ/SMSサービスãŒãƒ–ロックã•ã‚Œã¦ã„ã¾ã™ã€‚"</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"音声"</string>
<string name="serviceClassData" msgid="872456782077937893">"データ"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"é›»æºã‚’切る"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"シャットダウン中..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"æºå¸¯é›»è©±ã®é›»æºã‚’切りã¾ã™ã€‚"</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"æ–°ç€"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"最近使ã£ãŸã‚¢ãƒ—リケーションã¯ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="global_actions" msgid="2406416831541615258">"æºå¸¯é›»è©±ã‚ªãƒ—ション"</string>
<string name="global_action_lock" msgid="2844945191792119712">"ç”»é¢ãƒ­ãƒƒã‚¯"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"別ã®ã‚¢ãƒ—リケーションをデãƒãƒƒã‚°ãƒ¢ãƒ¼ãƒ‰ã«ã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒåˆ¥ã®ã‚¢ãƒ—リケーションを終了ã•ã›ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI設定ã®å¤‰æ›´"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"地域/言語やフォントã®ã‚µã‚¤ã‚ºãªã©ã€ç¾åœ¨ã®è¨­å®šã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"ä»–ã®ã‚¢ãƒ—リケーションã®å†èµ·å‹•"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"ä»–ã®ã‚¢ãƒ—リケーションã®å¼·åˆ¶çš„ãªå†èµ·å‹•ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"é‹è»¢ãƒ¢ãƒ¼ãƒ‰ã®æœ‰åŠ¹åŒ–"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"é‹è»¢ãƒ¢ãƒ¼ãƒ‰ã‚’有効ã«ã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ãƒ—ロセスã®çµ‚了"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"メモリãŒä¸è¶³ã—ã¦ã„ãªãã¦ã‚‚ã€åˆ¥ã®ã‚¢ãƒ—リケーションã®ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ãƒ—ロセスを終了ã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"別ã®ã‚¢ãƒ—リケーションã®å¼·åˆ¶åœæ­¢"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"別ã®ã‚¢ãƒ—リケーションã®å¼·åˆ¶åœæ­¢ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"アプリケーションã®å¼·åˆ¶çµ‚了"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"フォアグラウンドã§å®Ÿè¡Œã•ã‚Œã¦ã„ã‚‹æ“作を強制終了ã—ã¦æˆ»ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ã¾ã£ãŸãå¿…è¦ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="permlab_dump" msgid="1681799862438954752">"システムã®å†…部状態ã®å–å¾—"</string>
@@ -207,12 +221,10 @@
<string name="permdesc_setProcessLimit" msgid="7824786028557379539">"実行ã™ã‚‹ãƒ—ロセス数ã®ä¸Šé™ã®åˆ¶å¾¡ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã«ã¯ã¾ã£ãŸãå¿…è¦ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="permlab_setAlwaysFinish" msgid="5342837862439543783">"ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã‚¢ãƒ—リケーションをã™ã¹ã¦çµ‚了ã™ã‚‹"</string>
<string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ã«ãªã‚Šæ¬¡ç¬¬å¿…ãšæ“作を終了ã•ã›ã‚‹ã‹ã©ã†ã‹ã®åˆ¶å¾¡ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ã¾ã£ãŸãå¿…è¦ã‚ã‚Šã¾ã›ã‚“。"</string>
- <string name="permlab_batteryStats" msgid="7863923071360031652">"電池統計情報ã®å¤‰å›½"</string>
+ <string name="permlab_batteryStats" msgid="7863923071360031652">"電池統計情報ã®å¤‰æ›´"</string>
<string name="permdesc_batteryStats" msgid="5847319823772230560">"åŽé›†ã—ãŸé›»æ± çµ±è¨ˆæƒ…å ±ã®å¤‰æ›´ã‚’許å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
<string name="permlab_backup" msgid="470013022865453920">"システムã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã¨å¾©å…ƒã‚’制御ã™ã‚‹"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"システムã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã¨å¾©å…ƒãƒ¡ã‚«ãƒ‹ã‚ºãƒ ã®åˆ¶å¾¡ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"アプリケーションデータã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã¨å¾©å…ƒ"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"システムã®ãƒãƒƒã‚¯ã‚¢ãƒƒãƒ—ã¨å¾©å…ƒãƒ¡ã‚«ãƒ‹ã‚ºãƒ ã¸ã®ã‚¢ãƒ—リケーションã®å‚加を許å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"未許å¯ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã®è¡¨ç¤º"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"内部システムã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ãƒ¼ã‚¹ã§ä½¿ç”¨ã™ã‚‹ãŸã‚ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ä½œæˆã‚’許å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"システムレベルã®è­¦å‘Šã®è¡¨ç¤º"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"入力方法ã®ãƒˆãƒƒãƒ—レベルインターフェースã«é–¢é€£ä»˜ã‘ã‚‹ã“ã¨ã‚’所有者ã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã«ã¯ã¾ã£ãŸãå¿…è¦ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"å£ç´™ã«ãƒã‚¤ãƒ³ãƒ‰"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"å£ç´™ã®ãƒˆãƒƒãƒ—レベルインターフェースã¸ã®ãƒã‚¤ãƒ³ãƒ‰ã‚’所有者ã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä¸è¦ã§ã™ã€‚"</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"デãƒã‚¤ã‚¹ç®¡ç†è€…ã¨ã®é€šä¿¡"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"デãƒã‚¤ã‚¹ç®¡ç†è€…ã¸ã®intentã®é€ä¿¡ã‚’所有者ã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä¸è¦ã§ã™ã€‚"</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"ç”»é¢ã®å‘ãã®å¤‰æ›´"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"ã„ã¤ã§ã‚‚ç”»é¢ã®å›žè»¢ã‚’変更ã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã«ã¯ã¾ã£ãŸãå¿…è¦ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"Linuxã®ã‚·ã‚°ãƒŠãƒ«ã‚’アプリケーションã«é€ä¿¡"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Androidパッケージã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«/更新をアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒã€å‹æ‰‹ã«å¼·åŠ›ãªæ¨©é™ã‚’æŒã¤æ–°ã—ã„アプリケーションを追加ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"アプリケーションキャッシュデータã®å‰Šé™¤"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"アプリケーションã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‹ã‚‰ãƒ•ã‚¡ã‚¤ãƒ«ã‚’削除ã—ã¦æºå¸¯é›»è©±ã®ãƒ¡ãƒ¢ãƒªã‚’解放ã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã€ã‚¢ã‚¯ã‚»ã‚¹ã¯ã‚·ã‚¹ãƒ†ãƒ ãƒ—ロセスã®ã¿ã«åˆ¶é™ã•ã‚Œã¾ã™ã€‚"</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"アプリケーションリソースã®ç§»å‹•"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"内部ã¨å¤–部ã®ãƒ¡ãƒ‡ã‚£ã‚¢é–“ã§ã®ã‚¢ãƒ—リケーションリソースã®ç§»å‹•ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"システムログファイルã®èª­ã¿å–ã‚Š"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"システムã®ã•ã¾ã–ã¾ãªãƒ­ã‚°ãƒ•ã‚¡ã‚¤ãƒ«ã®èª­ã¿å–りをアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šæºå¸¯é›»è©±ã®ä½¿ç”¨çŠ¶æ³ã«é–¢ã™ã‚‹å…¨èˆ¬æƒ…å ±ãŒå–å¾—ã•ã‚Œã¾ã™ãŒã€å€‹äººæƒ…報やéžå…¬é–‹æƒ…å ±ãŒå«ã¾ã‚Œã‚‹ã“ã¨ã¯ã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"diagãŒæ‰€æœ‰ã™ã‚‹ãƒªã‚½ãƒ¼ã‚¹ã®èª­ã¿æ›¸ã"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"端末ã«ä¿å­˜ã—ãŸæ‰€æœ‰è€…ã®ãƒ‡ãƒ¼ã‚¿ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒæ‰€æœ‰è€…ã®ãƒ‡ãƒ¼ã‚¿ã‚’消去/変更ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"所有者データã®èª­ã¿å–ã‚Š"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"æºå¸¯é›»è©±ã«ä¿å­˜ã—ãŸæ‰€æœ‰è€…データã®èª­ã¿å–りをアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒæ‰€æœ‰è€…データを読ã¿å–ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"カレンダーデータã®èª­ã¿å–ã‚Š"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"カレンダーã®äºˆå®šã®èª­ã¿å–ã‚Š"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"端末ã«ä¿å­˜ã—ãŸã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã®äºˆå®šã®èª­ã¿å–りをアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã®äºˆå®šã‚’他人ã«é€ä¿¡ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"カレンダーデータã®æ›¸ãè¾¼ã¿"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"端末ã«ä¿å­˜ã—ãŸã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã®äºˆå®šã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒã€ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ãƒ‡ãƒ¼ã‚¿ã‚’消去/変更ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"カレンダーã®äºˆå®šã®è¿½åŠ ã‚„変更を行ã„ã€ã‚²ã‚¹ãƒˆã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã™ã‚‹"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"カレンダーã®äºˆå®šã®è¿½åŠ ã‚„変更をアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚ゲストã«ãƒ¡ãƒ¼ãƒ«ãŒé€ä¿¡ã•ã‚Œã‚‹å ´åˆã‚‚ã‚ã‚Šã¾ã™ã€‚悪æ„ã®ã‚るアプリケーションãŒã“ã®æ©Ÿèƒ½ã‚’利用ã—ã€ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã¾ãŸã¯å¤‰æ›´ã—ãŸã‚Šã‚²ã‚¹ãƒˆã«ãƒ¡ãƒ¼ãƒ«ã‚’é€ä¿¡ã—ãŸã‚Šã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"ä»®ã®ä½ç½®æƒ…å ±ã§ãƒ†ã‚¹ãƒˆ"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"テスト用ã«ä»®ã®ä½ç½®æƒ…å ±æºã‚’作æˆã—ã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šæ‚ªæ„ã®ã‚るアプリケーションãŒã€GPSã€ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ãƒ—ロãƒã‚¤ãƒ€ãªã©ã‹ã‚‰è¿”ã•ã‚Œã‚‹æœ¬å½“ã®ä½ç½®æƒ…報や状æ³ã‚’改ã–ã‚“ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"ä½ç½®æƒ…å ±æ供者ã®è¿½åŠ ã‚³ãƒžãƒ³ãƒ‰ã‚¢ã‚¯ã‚»ã‚¹"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"リムーãƒãƒ–ルメモリã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ ã®ãƒžã‚¦ãƒ³ãƒˆã¨ãƒžã‚¦ãƒ³ãƒˆè§£é™¤ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"外部ストレージã®ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆ"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"アプリケーションãŒãƒªãƒ ãƒ¼ãƒãƒ–ルストレージをフォーマットã™ã‚‹ã“ã¨ã‚’許å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"セキュアストレージ上ã®æƒ…å ±ã®å–å¾—"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"セキュアストレージ上ã®æƒ…å ±ã®å–得をアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"セキュアストレージã®ä½œæˆ"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"セキュアストレージã®ä½œæˆã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"セキュアストレージã®ç ´æ£„"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"セキュアストレージã®ç ´æ£„をアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"セキュアストレージã®ãƒžã‚¦ãƒ³ãƒˆ/マウント解除"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"セキュアストレージã®ãƒžã‚¦ãƒ³ãƒˆ/マウント解除をアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"セキュアストレージåã®å¤‰æ›´"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"セキュアストレージåã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"ãƒã‚¤ãƒ–レーション制御"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"ãƒã‚¤ãƒ–レーションã®åˆ¶å¾¡ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ライトã®ã‚³ãƒ³ãƒˆãƒ­ãƒ¼ãƒ«"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"システムã®å£ç´™ã‚µã‚¤ã‚ºã®ãƒ’ントã®è¨­å®šã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"システムを出è·æ™‚設定ã«ãƒªã‚»ãƒƒãƒˆ"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"データã€è¨­å®šã€ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã—ãŸã‚¢ãƒ—リケーションをã™ã¹ã¦æ¶ˆåŽ»ã—ã¦ã€å®Œå…¨ã«å‡ºè·æ™‚ã®è¨­å®šã«ã‚·ã‚¹ãƒ†ãƒ ã‚’リセットã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"時刻ã®è¨­å®š"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"æºå¸¯é›»è©±ã®æ™‚刻ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"タイムゾーンã®è¨­å®š"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"端末ã®ã‚¿ã‚¤ãƒ ã‚¾ãƒ¼ãƒ³ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"AccountManagerServiceã¨ã—ã¦æ©Ÿèƒ½"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"アクセスãƒã‚¤ãƒ³ãƒˆå設定ã®æ›¸ãè¾¼ã¿"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"APNã®ãƒ—ロキシやãƒãƒ¼ãƒˆãªã©ã®APN設定ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯æŽ¥ç¶šã®å¤‰æ›´"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®æŽ¥ç¶šçŠ¶æ…‹ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®æŽ¥ç¶šçŠ¶æ…‹ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"テザリング接続ã®å¤‰æ›´"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®ãƒ†ã‚¶ãƒªãƒ³ã‚°æŽ¥ç¶šçŠ¶æ…‹ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ãƒ‡ãƒ¼ã‚¿ä½¿ç”¨è¨­å®šã®å¤‰æ›´"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"ãƒãƒƒã‚¯ã‚°ãƒ©ã‚¦ãƒ³ãƒ‰ãƒ‡ãƒ¼ã‚¿ä½¿ç”¨ã®è¨­å®šã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Wi-Fi状態ã®è¡¨ç¤º"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"アプリケーションãŒãƒ¦ãƒ¼ã‚¶ãƒ¼è¾žæ›¸ã«æ–°ã—ã„語å¥ã‚’書ã込むã“ã¨ã‚’許å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"SDカードã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を修正/削除ã™ã‚‹"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"SDカードã¸ã®æ›¸ãè¾¼ã¿ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"キャッシュファイルシステムã«ã‚¢ã‚¯ã‚»ã‚¹"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"キャッシュファイルシステムã¸ã®èª­ã¿æ›¸ãをアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"パスワードã®åˆ¶é™"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"使用ã§ãるパスワードã®ç¨®é¡žã‚’制é™ã—ã¾ã™ã€‚"</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"ログインã®ç›£è¦–"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"æºå¸¯é›»è©±ã¸ã®ãƒ­ã‚°ã‚¤ãƒ³ã®å¤±æ•—を監視ã—ã€ä½•ã‚‰ã‹ã®å‡¦ç½®ã‚’ã¨ã‚Šã¾ã™ã€‚"</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"パスワードã®ãƒªã‚»ãƒƒãƒˆ"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"パスワードを強制的ã«æ–°ã—ã„値ã«å¤‰æ›´ã—ã¾ã™ã€‚ログインã™ã‚‹ã«ã¯ç®¡ç†è€…ã‹ã‚‰ãã®å€¤ã‚’通知ã—ã¦ã‚‚らã†å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚"</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"強制ロック"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"æºå¸¯é›»è©±ã®ãƒ­ãƒƒã‚¯æ™‚を管ç†ã—ã¾ã™ã€‚パスワードã®å†å…¥åŠ›ãŒå¿…è¦ã¨ãªã‚Šã¾ã™ã€‚"</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ã‚’消去"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"出è·æ™‚設定ã«ãƒªã‚»ãƒƒãƒˆã—ã¾ã™ã€‚確èªãªã—ã§ãƒ‡ãƒ¼ã‚¿ãŒã™ã¹ã¦å‰Šé™¤ã•ã‚Œã¾ã™ã€‚"</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"自宅"</item>
<item msgid="869923650527136615">"æºå¸¯"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>ã€æ›´æ–°å…ƒ: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PINコードを入力"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"ロックを解除ã™ã‚‹ã«ã¯ãƒ‘スワードを入力"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PINコードãŒæ­£ã—ãã‚ã‚Šã¾ã›ã‚“。"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"MENUã€0キーã§ãƒ­ãƒƒã‚¯è§£é™¤"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急通報番å·"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"MENUキーã§ãƒ­ãƒƒã‚¯è§£é™¤"</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"ロックを解除ã™ã‚‹ãƒ‘ターンを入力"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"緊急通報"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"通話ã«æˆ»ã‚‹"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"一致ã—ã¾ã—ãŸ"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"充電中(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"ロック解除"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"サウンドON"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"サウンドOFF"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"通知を消去"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"ブラウザã§ã‚¢ã‚¯ã‚»ã‚¹ã—ãŸã™ã¹ã¦ã®URLãŠã‚ˆã³ãƒ–ラウザã®ã™ã¹ã¦ã®ãƒ–ックマークã®èª­ã¿å–りをアプリケーションã«è¨±å¯ã—ã¾ã™ã€‚"</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"ブラウザã®å±¥æ­´ã¨ãƒ–ックマークを書ã込む"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"æºå¸¯é›»è©±ã«ä¿å­˜ã•ã‚Œã¦ã„るブラウザã®å±¥æ­´ã‚„ブックマークã®ä¿®æ­£ã‚’アプリケーショã«è¨±å¯ã—ã¾ã™ã€‚ã“ã‚Œã«ã‚ˆã‚Šæ‚ªæ„ã®ã‚るアプリケーションãŒã€ãƒ–ラウザã®ãƒ‡ãƒ¼ã‚¿ã‚’消去ã¾ãŸã¯å¤‰æ›´ã™ã‚‹æã‚ŒãŒã‚ã‚Šã¾ã™ã€‚"</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"ブラウザã®ä½ç½®æƒ…å ±ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹æ¨©ã‚’変更"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"ブラウザã®ä½ç½®æƒ…å ±ã«å¯¾ã™ã‚‹ã‚¢ã‚¯ã‚»ã‚¹æ¨©ã®å¤‰æ›´ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚ã“ã®è¨­å®šã§ã¯ã€æ‚ªæ„ã®ã‚るアプリケーションãŒä»»æ„ã®ã‚¦ã‚§ãƒ–サイトã«ä½ç½®æƒ…報をé€ä¿¡ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚"</string>
<string name="save_password_message" msgid="767344687139195790">"ã“ã®ãƒ‘スワードをブラウザã§ä¿å­˜ã—ã¾ã™ã‹ï¼Ÿ"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"今ã¯ä¿å­˜ã—ãªã„"</string>
<string name="save_password_remember" msgid="6491879678996749466">"ä¿å­˜"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1時間å‰"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g>時間å‰"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"éŽåŽ»<xliff:g id="COUNT">%d</xliff:g>日間"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"先月"</string>
+ <string name="older" msgid="5211975022815554840">"ã‚‚ã£ã¨å‰"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"昨日"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g>æ—¥å‰"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"週間"</string>
<string name="year" msgid="4001118221013892076">"å¹´"</string>
<string name="years" msgid="6881577717993213522">"å¹´"</string>
- <string name="every_weekday" msgid="8777593878457748503">"平日(月~金)"</string>
- <string name="daily" msgid="5738949095624133403">"毎日"</string>
- <string name="weekly" msgid="983428358394268344">"毎週<xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"毎月"</string>
- <string name="yearly" msgid="1519577999407493836">"毎年"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"動画をå†ç”Ÿã§ãã¾ã›ã‚“"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"ã“ã®å‹•ç”»ã¯ã”使用ã®ç«¯æœ«ã§ã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°ã§ãã¾ã›ã‚“。"</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"ã“ã®å‹•ç”»ã¯å†ç”Ÿã§ãã¾ã›ã‚“。"</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"強制終了"</string>
<string name="report" msgid="4060218260984795706">"レãƒãƒ¼ãƒˆ"</string>
<string name="wait" msgid="7147118217226317732">"å¾…æ©Ÿ"</string>
- <string name="debug" msgid="9103374629678531849">"デãƒãƒƒã‚°"</string>
<string name="sendText" msgid="5132506121645618310">"アプリケーションをé¸æŠž"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"ç€ä¿¡éŸ³é‡"</string>
<string name="volume_music" msgid="5421651157138628171">"メディアã®éŸ³é‡"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"権é™ã®è¨±å¯ã¯å¿…è¦ã‚ã‚Šã¾ã›ã‚“"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"éš ã™"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"ã™ã¹ã¦è¡¨ç¤º"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"読ã¿è¾¼ã¿ä¸­..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USBマスストレージ"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB接続"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"USB経由ã§æºå¸¯é›»è©±ã‚’コンピュータã«æŽ¥ç¶šã—ã¾ã—ãŸã€‚コンピュータã¨æºå¸¯é›»è©±ã®SDカード間ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’コピーã™ã‚‹ã«ã¯ã€[マウント]ã‚’é¸æŠžã—ã¾ã™ã€‚"</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"マウント"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"マウントã—ãªã„"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"æºå¸¯é›»è©±ã‚’USBã§ãƒ‘ソコンã«æŽ¥ç¶šã—ã¦ã„ã¾ã™ã€‚パソコンã¨Androidã®SDカード間ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’コピーã™ã‚‹ã«ã¯ã€ä¸‹ã®ãƒœã‚¿ãƒ³ã‚’é¸æŠžã—ã¾ã™ã€‚"</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"USBストレージをONã«ã™ã‚‹"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"USBメモリã«SDカードを使用ã™ã‚‹éš›ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB接続"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"パソコンã¨ã®é–“ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’コピーã—ã¾ã™ã€‚"</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USBストレージをOFFã«ã™ã‚‹"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USBストレージをOFFã«ã™ã‚‹å ´åˆã«é¸æŠžã—ã¾ã™ã€‚"</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"USBストレージをOFFã«ã™ã‚‹"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"USBストレージをOFFã«ã™ã‚‹å‰ã«USBホストã®ãƒžã‚¦ãƒ³ãƒˆã‚’解除ã—ãŸã“ã¨ã‚’確èªã—ã¦ãã ã•ã„。USBストレージをOFFã«ã™ã‚‹ã«ã¯[OFF]ã‚’é¸æŠžã—ã¾ã™ã€‚"</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"OFF"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"キャンセル"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"USBストレージをOFFã«ã™ã‚‹éš›ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚USBホストã®ãƒžã‚¦ãƒ³ãƒˆãŒè§£é™¤ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。"</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USBストレージを使用中"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"USBストレージをOFFã«ã™ã‚‹å‰ã«ã€ãƒ‘ソコンã§å¿…ãšAndroidã®SDカードã®ãƒžã‚¦ãƒ³ãƒˆã‚’解除ã—ã¦ï¼ˆã‚«ãƒ¼ãƒ‰ã‚’å–り出ã—ã¦ï¼‰ãã ã•ã„。"</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USBストレージをOFFã«ã™ã‚‹"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USBストレージをOFFã«ã™ã‚‹éš›ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚USBホストã®ãƒžã‚¦ãƒ³ãƒˆãŒè§£é™¤ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ã‹ã‚‰ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。"</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USBストレージをONã«ã™ã‚‹"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USBストレージをONã«ã™ã‚‹ã¨ã€ä½¿ç”¨ä¸­ã®ã‚¢ãƒ—リケーションã®ä¸€éƒ¨ãŒåœæ­¢ã—ã€USBストレージをOFFã«ã™ã‚‹ã¾ã§ä½¿ç”¨ã§ããªããªã‚‹å ´åˆãŒã‚ã‚Šã¾ã™ã€‚"</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USBæ“作ã«å¤±æ•—ã—ã¾ã—ãŸ"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"SDカードをフォーマット"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"SDカードをフォーマットã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿã‚«ãƒ¼ãƒ‰å†…ã®ã™ã¹ã¦ã®ãƒ‡ãƒ¼ã‚¿ãŒå¤±ã‚ã‚Œã¾ã™ã€‚"</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"フォーマット"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"一致ã™ã‚‹ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"コンãƒãƒ¼ãƒãƒ³ãƒˆä½¿ç”¨çŠ¶æ³ã«é–¢ã™ã‚‹çµ±è¨ˆæƒ…å ±ã®æ›´æ–°"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"åŽé›†ã•ã‚ŒãŸã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆä½¿ç”¨çŠ¶æ³ã«é–¢ã™ã‚‹çµ±è¨ˆæƒ…å ±ã®å¤‰æ›´ã‚’許å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"既定ã®ã‚³ãƒ³ãƒ†ãƒŠã‚µãƒ¼ãƒ“スを呼ã³å‡ºã—ã¦ã‚³ãƒ³ãƒ†ãƒ³ãƒ„をコピーã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"既定ã®ã‚³ãƒ³ãƒ†ãƒŠã‚µãƒ¼ãƒ“スを呼ã³å‡ºã—ã¦ã‚³ãƒ³ãƒ†ãƒ³ãƒ„をコピーã™ã‚‹ã“ã¨ã‚’アプリケーションã«è¨±å¯ã—ã¾ã™ã€‚通常ã®ã‚¢ãƒ—リケーションã§ã¯ä½¿ç”¨ã—ã¾ã›ã‚“。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"ダブルタップã§ã‚ºãƒ¼ãƒ ã—ã¾ã™"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"ウィジェットã®å±•é–‹ã‚¨ãƒ©ãƒ¼"</string>
<string name="ime_action_go" msgid="8320845651737369027">"移動"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>を使ã£ã¦"\n"連絡先を新è¦ç™»éŒ²"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"オン"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"オフ"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"リストã•ã‚Œã¦ã„るアプリケーションãŒã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ<xliff:g id="ACCOUNT">%1$s</xliff:g>ã®ãƒ­ã‚°ã‚¤ãƒ³èªè¨¼æƒ…å ±ã«<xliff:g id="APPLICATION">%2$s</xliff:g>ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹æ¨©é™ã‚’リクエストã—ã¦ã„ã¾ã™ã€‚ã“ã®æ¨©é™ã‚’許å¯ã—ã¾ã™ã‹ï¼Ÿè¨±å¯ã™ã‚‹ã¨ã€å…¥åŠ›ãŒè¨˜éŒ²ã•ã‚Œã€æ¬¡å›žä»¥é™ã¯ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒè¡¨ç¤ºã•ã‚Œãªããªã‚Šã¾ã™ã€‚"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"リストã•ã‚Œã¦ã„るアプリケーションãŒã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ<xliff:g id="ACCOUNT">%2$s</xliff:g>ã®<xliff:g id="TYPE">%1$s</xliff:g>ログインèªè¨¼æƒ…å ±ã«<xliff:g id="APPLICATION">%3$s</xliff:g>ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹æ¨©é™ã‚’リクエストã—ã¦ã„ã¾ã™ã€‚ã“ã®æ¨©é™ã‚’許å¯ã—ã¾ã™ã‹ï¼Ÿè¨±å¯ã™ã‚‹ã¨ã€å…¥åŠ›ã¯è¨˜éŒ²ã•ã‚Œã€æ¬¡å›žä»¥é™ã¯ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ãŒè¡¨ç¤ºã•ã‚Œãªããªã‚Šã¾ã™ã€‚"</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"下記ã®ã‚¢ãƒ—リケーションãŒã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’今後も許å¯ã™ã‚‹ã‚ˆã†ã«ãƒªã‚¯ã‚¨ã‚¹ãƒˆã—ã¦ã„ã¾ã™ã€‚"</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"ã“ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’許å¯ã—ã¦ã‚‚よã‚ã—ã„ã§ã™ã‹ï¼Ÿ"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"アクセスリクエスト"</string>
<string name="allow" msgid="7225948811296386551">"許å¯"</string>
<string name="deny" msgid="2081879885755434506">"æ‹’å¦"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"リクエスト済ã¿æ¨©é™"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"レイヤー2トンãƒãƒªãƒ³ã‚°ãƒ—ロトコル"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPNベースã®äº‹å‰å…±æœ‰éµ"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPNベースã®è¨¼æ˜Žæ›¸"</string>
+ <string name="upload_file" msgid="2897957172366730416">"ファイルをé¸æŠž"</string>
+ <string name="reset" msgid="2448168080964209908">"リセット"</string>
+ <string name="submit" msgid="1602335572089911941">"é€ä¿¡"</string>
+ <string name="description_star" msgid="2654319874908576133">"ãŠæ°—ã«å…¥ã‚Š"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"é‹è»¢ãƒ¢ãƒ¼ãƒ‰ã‚’有効ã«ã™ã‚‹"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"é‹è»¢ãƒ¢ãƒ¼ãƒ‰ã‚’終了ã™ã‚‹ã«ã¯é¸æŠžã—ã¦ãã ã•ã„。"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"テザリングãŒæœ‰åŠ¹ã§ã™"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"タップã—ã¦è¨­å®šã™ã‚‹"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"モãƒã‚¤ãƒ«ãƒ‡ãƒ¼ã‚¿ã®ä½¿ç”¨é‡ãŒå¢—ãˆã¦ã„ã¾ã™"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"タップã—ã¦ãƒ¢ãƒã‚¤ãƒ«ãƒ‡ãƒ¼ã‚¿åˆ©ç”¨ã®è©³ç´°ã‚’表示ã—ã¾ã™"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"モãƒã‚¤ãƒ«ãƒ‡ãƒ¼ã‚¿ã®åˆ¶é™ã‚’超ãˆã¾ã—ãŸ"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"タップã—ã¦ãƒ¢ãƒã‚¤ãƒ«ãƒ‡ãƒ¼ã‚¿åˆ©ç”¨ã®è©³ç´°ã‚’表示ã—ã¾ã™"</string>
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index aaeb6d8..1f742bf 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -42,8 +47,8 @@
<string name="invalidPin" msgid="3850018445187475377">"4~ 8ìžë¦¬ 숫ìžë¡œ ëœ PINì„ ìž…ë ¥í•˜ì„¸ìš”."</string>
<string name="needPuk" msgid="919668385956251611">"SIM ì¹´ë“œì˜ PUKê°€ 잠겨 있습니다. 잠금해제하려면 PUK 코드를 입력하세요."</string>
<string name="needPuk2" msgid="4526033371987193070">"SIM ì¹´ë“œ ìž ê¸ˆì„ í•´ì œí•˜ë ¤ë©´ PUK2를 입력하세요."</string>
- <string name="ClipMmi" msgid="6952821216480289285">"수신 ë°œì‹ ìž ë²ˆí˜¸"</string>
- <string name="ClirMmi" msgid="7784673673446833091">"발신 ë°œì‹ ìž ë²ˆí˜¸"</string>
+ <string name="ClipMmi" msgid="6952821216480289285">"ë°œì‹ ìž ë²ˆí˜¸"</string>
+ <string name="ClirMmi" msgid="7784673673446833091">"내 발신 번호"</string>
<string name="CfMmi" msgid="5123218989141573515">"착신전환"</string>
<string name="CwMmi" msgid="9129678056795016867">"통화중 대기"</string>
<string name="BaMmi" msgid="455193067926770581">"착발신 제한"</string>
@@ -64,14 +69,18 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"ì œí•œëœ ì•¡ì„¸ìŠ¤ê°€ 변경ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"ë°ì´í„° 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"긴급 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"ìŒì„±/SMS 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"모든 ìŒì„±/SMS 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"ìŒì„± 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"모든 ìŒì„± 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMS 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"ìŒì„±/ë°ì´í„° 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"ìŒì„±/SMS 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"모든 ìŒì„±/ë°ì´í„°/SMS 서비스가 차단ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"ìŒì„±"</string>
<string name="serviceClassData" msgid="872456782077937893">"ë°ì´í„°"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"팩스"</string>
<string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
- <string name="serviceClassDataAsync" msgid="4523454783498551468">"비ë™ê¸°"</string>
- <string name="serviceClassDataSync" msgid="7530000519646054776">"ë™ê¸°í™”"</string>
+ <string name="serviceClassDataAsync" msgid="4523454783498551468">"비ë™ê¸°ì‹"</string>
+ <string name="serviceClassDataSync" msgid="7530000519646054776">"ë™ê¸°ì‹"</string>
<string name="serviceClassPacket" msgid="6991006557993423453">"패킷"</string>
<string name="serviceClassPAD" msgid="3235259085648271037">"PAD"</string>
<string name="roamingText0" msgid="7170335472198694945">"ë¡œë° í‘œì‹œê¸° 사용"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"종료"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"종료 중..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"휴대전화가 종료ë©ë‹ˆë‹¤."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"최근 작업"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"최신 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì•„ë‹™ë‹ˆë‹¤."</string>
<string name="global_actions" msgid="2406416831541615258">"휴대전화 옵션"</string>
<string name="global_action_lock" msgid="2844945191792119712">"화면 잠금"</string>
@@ -132,9 +142,9 @@
<string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"ë¬´ìŒ ëª¨ë“œ"</string>
<string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"소리 꺼ì§"</string>
<string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"소리 켜ì§"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"비행 모드"</string>
- <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"비행 모드 사용"</string>
- <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"비행 모드 사용 안함"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"비행기 모드"</string>
+ <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"비행기 모드 사용"</string>
+ <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"비행기 모드 사용 안함"</string>
<string name="safeMode" msgid="2788228061547930246">"안전 모드"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android 시스템"</string>
<string name="permgrouplab_costMoney" msgid="5429808217861460401">"ìš”ê¸ˆì´ ë¶€ê³¼ë˜ëŠ” 서비스"</string>
@@ -185,14 +195,18 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì— ëŒ€í•´ ë””ë²„ê¹…ì„ ì‚¬ìš©í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ì¤‘ì§€ì‹œí‚¬ 수 있습니다."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI 설정 변경"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¡œì¼€ì¼ ë˜ëŠ” ì „ì²´ 글꼴 í¬ê¸°ì™€ ê°™ì€ í˜„ìž¬ êµ¬ì„±ì„ ë³€ê²½í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"다른 ì‘용프로그램 다시 시작"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ê°•ì œë¡œ 다시 시작할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"차량 모드 사용"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì°¨ëŸ‰ 모드를 사용할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"백그ë¼ìš´ë“œ 프로세스 종료"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"메모리가 부족하지 ì•Šì€ ê²½ìš°ì—ë„ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì˜ ë°±ê·¸ë¼ìš´ë“œ 프로세스를 중단할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"다른 ì‘용프로그램 ê°•ì œ 종료"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ê°•ì œë¡œ 종료할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"강제로 ì‘용프로그램 닫기"</string>
- <string name="permdesc_forceBack" msgid="6534109744159919013">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í¬ê·¸ë¼ìš´ë“œì— 있는 활ë™ì„ 강제로 ë‹«ê³  ë˜ëŒì•„ê°ˆ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
+ <string name="permdesc_forceBack" msgid="6534109744159919013">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í¬ê·¸ë¼ìš´ë“œì— 있는 활ë™ì„ 강제로 ë‹«ê³  ë˜ëŒì•„ê°ˆ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
<string name="permlab_dump" msgid="1681799862438954752">"시스템 내부 ìƒíƒœ 검색"</string>
<string name="permdesc_dump" msgid="2198776174276275220">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ë‚´ë¶€ ìƒíƒœë¥¼ 검색할 수 있ë„ë¡ í•©ë‹ˆë‹¤. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ ì¼ë°˜ì ìœ¼ë¡œ 필요하지 ì•Šì€ ë‹¤ì–‘í•œ ê°œì¸ì •ë³´ì™€ 보안정보를 검색할 수 있습니다."</string>
<string name="permlab_shutdown" msgid="7185747824038909016">"부분 종료"</string>
- <string name="permdesc_shutdown" msgid="7046500838746291775">"ìž‘ì—… 관리ìžë¥¼ 종료 ìƒíƒœë¡œ 설정하며 ì „ì²´ 종료를 수행하지 않습니다."</string>
+ <string name="permdesc_shutdown" msgid="7046500838746291775">"ìž‘ì—… 관리ìžë¥¼ 종료 ìƒíƒœë¡œ 설정합니다. ì „ì²´ 종료를 수행하지는 않습니다."</string>
<string name="permlab_stopAppSwitches" msgid="4138608610717425573">"ì‘용프로그램 전환 방지"</string>
<string name="permdesc_stopAppSwitches" msgid="3857886086919033794">"사용ìžê°€ 다른 ì‘용프로그램으로 전환하지 못하게 합니다."</string>
<string name="permlab_runSetActivityWatcher" msgid="7811586187574696296">"실행 ì¤‘ì¸ ëª¨ë“  ì‘용프로그램 ëª¨ë‹ˆí„°ë§ ë° ì œì–´"</string>
@@ -200,19 +214,17 @@
<string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"패키지 제거 브로드ìºìŠ¤íŠ¸ 보내기"</string>
<string name="permdesc_broadcastPackageRemoved" msgid="3453286591439891260">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘용프로그램 패키지가 ì‚­ì œë˜ì—ˆë‹¤ëŠ” ì•Œë¦¼ì„ ë¸Œë¡œë“œìºìŠ¤íŠ¸í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹¤í–‰ ì¤‘ì¸ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ì¤‘ì§€ì‹œí‚¬ 수 있습니다."</string>
<string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"SMS 수신 브로드ìºìŠ¤íŠ¸ 보내기"</string>
- <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ SMS 메시지를 받았다는 ì•Œë¦¼ì„ ë¸Œë¡œë“œìºìŠ¤íŠ¸í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë“¤ì–´ì˜¤ëŠ” SMS 메시지처럼 위장할 수 있습니다."</string>
+ <string name="permdesc_broadcastSmsReceived" msgid="9122419277306740155">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ SMS 메시지를 받았다는 ì•Œë¦¼ì„ ë¸Œë¡œë“œìºìŠ¤íŠ¸í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìˆ˜ì‹ ëœ SMS 메시지처럼 위장할 수 있습니다."</string>
<string name="permlab_broadcastWapPush" msgid="3145347413028582371">"WAP-PUSH-수신 브로드ìºìŠ¤íŠ¸ 보내기"</string>
<string name="permdesc_broadcastWapPush" msgid="3955303669461378091">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ WAP PUSH 메시지를 받았다는 ì•Œë¦¼ì„ ë¸Œë¡œë“œìºìŠ¤íŠ¸í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ MMS 메시지를 ë°›ì€ ê²ƒì²˜ëŸ¼ 위장하거나 웹페ì´ì§€ì˜ 콘í…츠를 악성 변종으로 몰래 바꿀 수 있습니다."</string>
<string name="permlab_setProcessLimit" msgid="2451873664363662666">"실행 ì¤‘ì¸ í”„ë¡œì„¸ìŠ¤ 수 제한"</string>
- <string name="permdesc_setProcessLimit" msgid="7824786028557379539">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹¤í–‰í•  최대 프로세스 수를 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
+ <string name="permdesc_setProcessLimit" msgid="7824786028557379539">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹¤í–‰í•  최대 프로세스 수를 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
<string name="permlab_setAlwaysFinish" msgid="5342837862439543783">"모든 백그ë¼ìš´ë“œ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹«ížˆë„ë¡ í•˜ê¸°"</string>
- <string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë°±ê·¸ë¼ìš´ë“œë¡œ ì´ë™í•œ 활ë™ì„ í•­ìƒ ë°”ë¡œ 종료할지 여부를 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
+ <string name="permdesc_setAlwaysFinish" msgid="8773936403987091620">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë°±ê·¸ë¼ìš´ë“œë¡œ ì´ë™í•œ 활ë™ì„ í•­ìƒ ë°”ë¡œ 종료할지 여부를 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
<string name="permlab_batteryStats" msgid="7863923071360031652">"배터리 통계 수정"</string>
- <string name="permdesc_batteryStats" msgid="5847319823772230560">"ìˆ˜ì§‘ëœ ë°°í„°ë¦¬ 통계를 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용할 수 없습니다."</string>
+ <string name="permdesc_batteryStats" msgid="5847319823772230560">"ìˆ˜ì§‘ëœ ë°°í„°ë¦¬ 통계를 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_backup" msgid="470013022865453920">"시스템 백업 ë° ë³µì› ê´€ë¦¬"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ë°±ì—… ë° ë³µì› ë§¤ì»¤ë‹ˆì¦˜ì„ ì œì–´í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"ì‘용프로그램 ë°ì´í„° 백업 ë° ë³µì›"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ë°±ì—… ë° ë³µì› ë§¤ì»¤ë‹ˆì¦˜ì— ì°¸ì—¬í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"ì¸ì¦ë˜ì§€ ì•Šì€ ì°½ 표시"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"내부 시스템 ì‚¬ìš©ìž ì¸í„°íŽ˜ì´ìŠ¤ì—ì„œ 사용하는 ì°½ì„ ë§Œë“¤ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"시스템 수준 경고 표시"</string>
@@ -220,21 +232,23 @@
<string name="permlab_setAnimationScale" msgid="2805103241153907174">"ì „ì²´ 애니메ì´ì…˜ ì†ë„ 수정"</string>
<string name="permdesc_setAnimationScale" msgid="7181522138912391988">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì–¸ì œë“ ì§€ ì „ì²´ 애니메ì´ì…˜ ì†ë„를 빠르게 ë˜ëŠ” ëŠë¦¬ê²Œ 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_manageAppTokens" msgid="17124341698093865">"ì‘용프로그램 í† í° ê´€ë¦¬"</string>
- <string name="permdesc_manageAppTokens" msgid="977127907524195988">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¼ë°˜ì ì¸ Z-순서를 무시하여 ìžì²´ 토í°ì„ 만들고 관리할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
+ <string name="permdesc_manageAppTokens" msgid="977127907524195988">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¼ë°˜ì ì¸ Z-순서를 무시하여 ìžì²´ 토í°ì„ 만들고 관리할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
<string name="permlab_injectEvents" msgid="1378746584023586600">"키 ë° ì»¨íŠ¸ë¡¤ 버튼 누르기"</string>
<string name="permdesc_injectEvents" msgid="3946098050410874715">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìž…ë ¥ ì´ë²¤íŠ¸(예: 키 누름)를 다른 ì‘ìš©í”„ë¡œê·¸ëž¨ì— ì „ë‹¬í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ë¥¼ 완전히 제어할 수 있습니다."</string>
<string name="permlab_readInputState" msgid="469428900041249234">"사용ìžê°€ 입력한 ë‚´ìš© ë° ìˆ˜í–‰í•œ ìž‘ì—… 기ë¡"</string>
- <string name="permdesc_readInputState" msgid="5132879321450325445">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘용프로그램과 ìƒí˜¸ìž‘ìš©í•  ë•Œì—ë„ ì‚¬ìš©ìžê°€ 누르는 키(예: 비밀번호 ìž…ë ¥)를 ë³¼ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
- <string name="permlab_bindInputMethod" msgid="3360064620230515776">"입력 방법 고정"</string>
- <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"보유ìžê°€ ìž…ë ¥ ë°©ë²•ì˜ ìµœìƒìœ„ ì¸í„°íŽ˜ì´ìŠ¤ë§Œ 사용하ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
- <string name="permlab_bindWallpaper" msgid="8716400279937856462">"배경화면만 사용"</string>
- <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"보유ìžê°€ ë°°ê²½í™”ë©´ì˜ ìµœìƒìœ„ ì¸í„°íŽ˜ì´ìŠ¤ë§Œ 사용하ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
+ <string name="permdesc_readInputState" msgid="5132879321450325445">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘용프로그램과 ìƒí˜¸ìž‘ìš©í•  ë•Œì—ë„ ì‚¬ìš©ìžê°€ 누르는 키(예: 비밀번호 ìž…ë ¥)를 ë³¼ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
+ <string name="permlab_bindInputMethod" msgid="3360064620230515776">"입력 방법 연결"</string>
+ <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"ê¶Œí•œì„ ê°€ì§„ í”„ë¡œê·¸ëž¨ì´ ìž…ë ¥ ë°©ë²•ì— ëŒ€í•œ 최ìƒìœ„ ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 사용하ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
+ <string name="permlab_bindWallpaper" msgid="8716400279937856462">"배경화면 연결"</string>
+ <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"ê¶Œí•œì„ ê°€ì§„ í”„ë¡œê·¸ëž¨ì´ ë°°ê²½í™”ë©´ì— ëŒ€í•œ 최ìƒìœ„ ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 사용하ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"기기 관리ìžì™€ ìƒí˜¸ ìž‘ìš©"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"보유ìžê°€ 기기 관리ìžì—게 ì¸í…트를 보낼 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"화면 방향 변경"</string>
- <string name="permdesc_setOrientation" msgid="6335814461615851863">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì–¸ì œë“ ì§€ 화면 íšŒì „ì„ ë³€ê²½í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 필요하지 않습니다."</string>
- <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"ì‘ìš©í”„ë¡œê·¸ëž¨ì— Linux 신호 보내기"</string>
- <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì œê³µëœ ì‹ í˜¸ë¥¼ 모든 ì˜êµ¬ 프로세스로 ë³´ë‚´ë„ë¡ ìš”ì²­í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permdesc_setOrientation" msgid="6335814461615851863">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì–¸ì œë“ ì§€ 화면 íšŒì „ì„ ë³€ê²½í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—는 절대로 필요하지 않습니다."</string>
+ <string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"ì‘ìš©í”„ë¡œê·¸ëž¨ì— Linux ì‹œê·¸ë„ ë³´ë‚´ê¸°"</string>
+ <string name="permdesc_signalPersistentProcesses" msgid="3565530463215015289">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì œê³µëœ ì‹œê·¸ë„ì„ ëª¨ë“  ì˜êµ¬ 프로세스로 ë³´ë‚´ë„ë¡ ìš”ì²­í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_persistentActivity" msgid="8659652042401085862">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í•­ìƒ ì‹¤í–‰ë˜ë„ë¡ ì„¤ì •"</string>
- <string name="permdesc_persistentActivity" msgid="5037199778265006008">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì—°ê²°ëœ ì¼ë¶€ 구성 요소를 지ì†í•˜ì—¬ 다른 ì‘ìš©í”„ë¡œê·¸ëž¨ì— ì‚¬ìš©í•  수 ì—†ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permdesc_persistentActivity" msgid="5037199778265006008">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìžì‹ ì˜ ì¼ë¶€ 구성 요소를 지ì†ê°€ëŠ¥ìœ¼ë¡œ 설정하여 다른 ì‘ìš©í”„ë¡œê·¸ëž¨ì— ì‚¬ìš©í•  수 ì—†ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_deletePackages" msgid="3343439331576348805">"ì‘용프로그램 ì‚­ì œ"</string>
<string name="permdesc_deletePackages" msgid="3634943677518723314">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ Android 패키지를 삭제할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¤‘ìš”í•œ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ì‚­ì œí•  수 있습니다."</string>
<string name="permlab_clearAppUserData" msgid="2192134353540277878">"다른 ì‘ìš©í”„ë¡œê·¸ëž¨ì˜ ë°ì´í„° ì‚­ì œ"</string>
@@ -247,14 +261,16 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìƒˆë¡œìš´ ë˜ëŠ” ì—…ë°ì´íŠ¸ëœ Android 패키지를 설치할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìž„ì˜ì˜ 강력한 권한으로 새 ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ì¶”ê°€í•  수 있습니다."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"모든 ì‘용프로그램 ìºì‹œ ë°ì´í„° ì‚­ì œ"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘용프로그램 ìºì‹œ ë””ë ‰í† ë¦¬ì— ìžˆëŠ” 파ì¼ì„ 삭제하여 íœ´ëŒ€ì „í™”ì˜ ì €ìž¥ê³µê°„ì„ ëŠ˜ë¦´ 수 있ë„ë¡ í•©ë‹ˆë‹¤. 액세스는 ì¼ë°˜ì ìœ¼ë¡œ 시스템 프로세스로 제한ë©ë‹ˆë‹¤."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"ì‘용프로그램 리소스 ì´ë™"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘용프로그램 리소스를 내부ì—ì„œ 외부 미디어로 ë˜ëŠ” ê·¸ 반대로 ì´ë™í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"시스템 로그 íŒŒì¼ ì½ê¸°"</string>
- <string name="permdesc_readLogs" msgid="2257937955580475902">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ë‹¤ì–‘í•œ 로그 파ì¼ì„ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ì‚¬ìš©ìžê°€ 휴대전화로 수행하는 ìž‘ì—…ì— ëŒ€í•œ ì¼ë°˜ì ì¸ 정보를 검색할 수 있지만 ì—¬ê¸°ì— ê°œì¸ì •ë³´ëŠ” í¬í•¨ë˜ì–´ì„œëŠ” 안 ë©ë‹ˆë‹¤."</string>
- <string name="permlab_diagnostic" msgid="8076743953908000342">"진단 그룹 ì†Œìœ ì˜ ë¦¬ì†ŒìŠ¤ ì½ê¸°/작성"</string>
+ <string name="permdesc_readLogs" msgid="2257937955580475902">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ë‹¤ì–‘í•œ 로그 파ì¼ì„ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ì‚¬ìš©ìžê°€ 휴대전화로 수행하는 ìž‘ì—…ì— ëŒ€í•œ ì¼ë°˜ì ì¸ 정보를 검색할 수 있습니다. 하지만 로그 파ì¼ì— ì–´ë– í•œ ê°œì¸ì •ë³´ë„ í¬í•¨ë˜ì–´ì„œëŠ” 안 ë©ë‹ˆë‹¤."</string>
+ <string name="permlab_diagnostic" msgid="8076743953908000342">"진단 그룹 ì†Œìœ ì˜ ë¦¬ì†ŒìŠ¤ ì½ê¸°/쓰기"</string>
<string name="permdesc_diagnostic" msgid="3121238373951637049">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì§„ë‹¨ 그룹 ì†Œìœ ì˜ ë¦¬ì†ŒìŠ¤(예: /devì— ìžˆëŠ” 파ì¼)를 ì½ê³  쓸 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¸°ëŠ¥ì€ ì‹œìŠ¤í…œ 안정성 ë° ë³´ì•ˆì— ì˜í–¥ì„ 미칠 수 있으므로 제조업체 ë˜ëŠ” 사업ìžê°€ 하드웨어 관련 ì§„ë‹¨ì„ ìˆ˜í–‰í•˜ëŠ” 경우ì—만 사용해야 합니다."</string>
<string name="permlab_changeComponentState" msgid="79425198834329406">"ì‘용프로그램 구성 요소 사용 ë˜ëŠ” 사용 안함"</string>
<string name="permdesc_changeComponentState" msgid="4569107043246700630">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë‹¤ë¥¸ ì‘용프로그램 구성 요소 사용 여부를 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¤‘ìš”í•œ 휴대전화 ê¸°ëŠ¥ì„ ì‚¬ìš©í•˜ì§€ ì•Šë„ë¡ ì„¤ì •í•  수 있습니다. ì´ ê¶Œí•œì„ ì„¤ì •í•  경우 ì‘용프로그램 구성 요소가 사용 불가능하게 ë˜ê±°ë‚˜ ì¼ê´€ì„±ì´ 맞지 않거나 불안정해질 수 있으므로 주ì˜í•´ì•¼ 합니다."</string>
<string name="permlab_setPreferredApplications" msgid="3393305202145172005">"기본 ì‘용프로그램 설정"</string>
- <string name="permdesc_setPreferredApplications" msgid="760008293501937546">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ê¸°ë³¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ìˆ˜ì •í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ ê°œì¸ ì •ë³´ë¥¼ 수집하기 위해 기존 ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ìŠ¤í‘¸í•‘í•˜ì—¬ 실행ë˜ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ëª°ëž˜ 변경할 수 있습니다."</string>
+ <string name="permdesc_setPreferredApplications" msgid="760008293501937546">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ê¸°ë³¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ìˆ˜ì •í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ ê°œì¸ ì •ë³´ë¥¼ 수집하기 위해 기존 ì‘용프로그램으로 위장하ë„ë¡ ì‹¤í–‰ë˜ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ëª°ëž˜ 변경할 수 있습니다."</string>
<string name="permlab_writeSettings" msgid="1365523497395143704">"전체 시스템 설정 수정"</string>
<string name="permdesc_writeSettings" msgid="838789419871034696">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œì˜ ì„¤ì • ë°ì´í„°ë¥¼ 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œ êµ¬ì„±ì„ ì†ìƒì‹œí‚¬ 수 있습니다."</string>
<string name="permlab_writeSecureSettings" msgid="204676251876718288">"보안 시스템 설정 수정"</string>
@@ -263,8 +279,8 @@
<string name="permdesc_writeGservices" msgid="6602362746516676175">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ Google 서비스 지ë„를 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_receiveBootCompleted" msgid="7776779842866993377">"부팅할 ë•Œ ìžë™ 시작"</string>
<string name="permdesc_receiveBootCompleted" msgid="698336728415008796">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œ ë¶€íŒ…ì´ ëë‚œ 후 바로 시작할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 휴대전화가 시작하는 ë° ì‹œê°„ì´ ì˜¤ëž˜ 걸리고 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í•­ìƒ ì‹¤í–‰ë˜ì–´ ì „ì²´ 휴대전화 ì†ë„ê°€ ëŠë ¤ì§ˆ 수 있습니다."</string>
- <string name="permlab_broadcastSticky" msgid="7919126372606881614">"ë‚¨ì€ ë¸Œë¡œë“œìºìŠ¤íŠ¸ 보내기"</string>
- <string name="permdesc_broadcastSticky" msgid="1920045289234052219">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¸Œë¡œë“œìºìŠ¤íŠ¸ê°€ ëë‚œ í›„ì— ë‚¨ì€ ë¸Œë¡œë“œìºìŠ¤íŠ¸ë¥¼ 보낼 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ê°€ 메모리를 너무 ë§Žì´ ì‚¬ìš©í•˜ë„ë¡ í•˜ì—¬ ì†ë„를 저하시키거나 불안정하게 만들 수 있습니다."</string>
+ <string name="permlab_broadcastSticky" msgid="7919126372606881614">"스티키 브로드ìºìŠ¤íŠ¸ 보내기"</string>
+ <string name="permdesc_broadcastSticky" msgid="1920045289234052219">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¸Œë¡œë“œìºìŠ¤íŠ¸ê°€ ëë‚œ 후ì—ë„ ìœ ì§€ë˜ëŠ” 스티키 브로드ìºìŠ¤íŠ¸(Sticky Broadcast)를 보낼 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ê°€ 메모리를 너무 ë§Žì´ ì‚¬ìš©í•˜ë„ë¡ í•˜ì—¬ ì†ë„를 저하시키거나 불안정하게 만들 수 있습니다."</string>
<string name="permlab_readContacts" msgid="6219652189510218240">"ì—°ë½ì²˜ ë°ì´í„° ì½ê¸°"</string>
<string name="permdesc_readContacts" msgid="3371591512896545975">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ ëª¨ë“  ì—°ë½ì²˜(주소) ë°ì´í„°ë¥¼ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë°ì´í„°ë¥¼ 다른 사람ì—게 보낼 수 있습니다."</string>
<string name="permlab_writeContacts" msgid="644616215860933284">"ì—°ë½ì²˜ ë°ì´í„° 작성"</string>
@@ -273,30 +289,30 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ ì†Œìœ ìž ë°ì´í„°ë¥¼ 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ ì†Œìœ ìž ë°ì´í„°ë¥¼ 지우거나 수정할 수 있습니다."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"ì†Œìœ ìž ë°ì´í„° ì½ê¸°"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ íœ´ëŒ€ì „í™” ì†Œìœ ìž ë°ì´í„°ë¥¼ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™” ì†Œìœ ìž ë°ì´í„°ë¥¼ ì½ì„ 수 있습니다."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"ìº˜ë¦°ë” ë°ì´í„° ì½ê¸°"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"ìº˜ë¦°ë” ì¼ì • ì½ê¸°"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ ëª¨ë“  ìº˜ë¦°ë” ì¼ì •ì„ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìº˜ë¦°ë” ì¼ì •ì„ 다른 사람ì—게 보낼 수 있습니다."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"ìº˜ë¦°ë” ë°ì´í„° 작성"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ ìº˜ë¦°ë” ì¼ì •ì„ 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìº˜ë¦°ë” ë°ì´í„°ë¥¼ 지우거나 수정할 수 있습니다."</string>
- <string name="permlab_accessMockLocation" msgid="8688334974036823330">"테스트를 위해 위치 소스로 가장"</string>
- <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"테스트용 가짜 위치 소스를 만듭니다. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS, ë„¤íŠ¸ì›Œí¬ ì œê³µì—…ì²´ ê°™ì€ ì‹¤ì œ 위치 소스ì—ì„œ 반환한 위치 ë°/ë˜ëŠ” ìƒíƒœë¥¼ ë®ì–´ì“¸ 수 있습니다."</string>
- <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"추가 위치 제공업체 명령 액세스"</string>
- <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"추가 위치 제공업체 ëª…ë ¹ì— ì•¡ì„¸ìŠ¤í•©ë‹ˆë‹¤. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS ë˜ëŠ” 기타 위치 ì†ŒìŠ¤ì˜ ìž‘ë™ì„ ë°©í•´í•  수 있습니다."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"ìº˜ë¦°ë” ì¼ì • 추가/수정 ë° ì°¸ì„ìžì—게 ì´ë©”ì¼ ì „ì†¡"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìº˜ë¦°ë”ì— ì¼ì •ì„ 추가하거나 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ë ‡ê²Œ 하면 ì°¸ì„ìžì—게 ì´ë©”ì¼ì„ 보낼 수 있습니다. 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ë¥¼ 사용하여 ìº˜ë¦°ë” ì¼ì •ì„ ì‚­ì œ, 수정하거나 ì°¸ì„ìžì—게 ì´ë©”ì¼ì„ 보낼 수 있습니다."</string>
+ <string name="permlab_accessMockLocation" msgid="8688334974036823330">"테스트를 위해 위치 정보제공ìžë¡œ 가장"</string>
+ <string name="permdesc_accessMockLocation" msgid="7648286063459727252">"테스트용 가짜 위치 ì •ë³´ 제공ìžë¥¼ 만듭니다. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS, ë„¤íŠ¸ì›Œí¬ ê³µê¸‰ìž ê°™ì€ ì‹¤ì œ 위치 정보제공ìžì—ì„œ 반환한 위치 ë°/ë˜ëŠ” ìƒíƒœë¥¼ ë®ì–´ì“¸ 수 있습니다."</string>
+ <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"추가 위치 제공업체 ëª…ë ¹ì— ì•¡ì„¸ìŠ¤"</string>
+ <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"추가ì ì¸ 위치 제공 ëª…ë ¹ì„ ì‚¬ìš©í•©ë‹ˆë‹¤. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS ë˜ëŠ” 기타 위치 ì†ŒìŠ¤ì˜ ìž‘ë™ì„ ë°©í•´í•  수 있습니다."</string>
<string name="permlab_installLocationProvider" msgid="6578101199825193873">"위치 ì •ë³´ ê³µê¸‰ìž ì„¤ì¹˜ 권한"</string>
- <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"테스트용 가짜 위치 소스를 만듭니다. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS, ë„¤íŠ¸ì›Œí¬ ì œê³µì—…ì²´ ê°™ì€ ì‹¤ì œ 위치 소스ì—ì„œ 반환한 위치 ë°/ë˜ëŠ” ìƒíƒœë¥¼ ë®ì–´ì“°ê±°ë‚˜ 사용ìžì˜ 위치를 모니터ë§í•˜ì—¬ 외부 소스로 ë³´ê³ í•  수 있습니다."</string>
+ <string name="permdesc_installLocationProvider" msgid="5449175116732002106">"테스트용 가짜 위치 정보제공ìžë¥¼ 만듭니다. 단, 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ ê¸°ëŠ¥ì„ ì´ìš©í•˜ì—¬ GPS, ë„¤íŠ¸ì›Œí¬ ê³µê¸‰ì—…ì²´ ê°™ì€ ì‹¤ì œ 위치 소스ì—ì„œ 반환한 위치 ë°/ë˜ëŠ” ìƒíƒœë¥¼ ë®ì–´ì“°ê±°ë‚˜ 사용ìžì˜ 위치를 모니터ë§í•˜ì—¬ 외부 소스로 ë³´ê³ í•  수 있습니다."</string>
<string name="permlab_accessFineLocation" msgid="8116127007541369477">"ìžì„¸í•œ (GPS) 위치"</string>
- <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"가능한 경우 휴대전화ì—ì„œ GPS(범지구 위치 측정 시스템) ë“±ì˜ ìžì„¸í•œ 위치 ì†ŒìŠ¤ì— ì•¡ì„¸ìŠ¤í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ 위치를 확ì¸í•˜ê³  추가 배터리 ì „ì›ì„ 소비할 수 있습니다."</string>
- <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"광범위한 ë„¤íŠ¸ì›Œí¬ ê¸°ë°˜ 위치"</string>
- <string name="permdesc_accessCoarseLocation" msgid="8235655958070862293">"íœ´ëŒ€ì „í™”ì˜ ëŒ€ëžµì ì¸ 위치를 측정하기 위해 셀룰러 ë„¤íŠ¸ì›Œí¬ ë°ì´í„°ë² ì´ìŠ¤ì™€ ê°™ì€ ê´‘ë²”ìœ„í•œ 위치 ì†ŒìŠ¤ì— ì•¡ì„¸ìŠ¤í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ 위치를 대략ì ìœ¼ë¡œ 측정할 수 있습니다."</string>
+ <string name="permdesc_accessFineLocation" msgid="7411213317434337331">"GPS ë“±ì˜ ìžì„¸í•œ 위치 ì •ë³´ê°€ 사용 가능한 경우 휴대전화ì—ì„œ ì´ë¥¼ 사용합니다. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ 위치를 확ì¸í•˜ê³  추가 배터리 ì „ì›ì„ 소비할 수 있습니다."</string>
+ <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"ë„¤íŠ¸ì›Œí¬ ê¸°ë°˜ì˜ ëŒ€ëžµì ì¸ 위치"</string>
+ <string name="permdesc_accessCoarseLocation" msgid="8235655958070862293">"íœ´ëŒ€ì „í™”ì˜ ëŒ€ëžµì ì¸ 위치를 측정하기 위해 셀룰러 ë„¤íŠ¸ì›Œí¬ ë°ì´í„°ë² ì´ìŠ¤ì™€ ê°™ì€ ê´‘ë²”ìœ„í•œ 위치 정보를 사용합니다. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ 위치를 대략ì ìœ¼ë¡œ 측정할 수 있습니다."</string>
<string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"SurfaceFlinger 액세스"</string>
<string name="permdesc_accessSurfaceFlinger" msgid="6805241830020733025">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ SurfaceFlingerì˜ í•˜ìœ„ 수준 ê¸°ëŠ¥ì„ ì‚¬ìš©í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_readFrameBuffer" msgid="6690504248178498136">"프레임 ë²„í¼ ì½ê¸°"</string>
- <string name="permdesc_readFrameBuffer" msgid="7530020370469942528">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í”„ë ˆìž„ 버í¼ì˜ 콘í…츠를 ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permdesc_readFrameBuffer" msgid="7530020370469942528">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í”„ë ˆìž„ 버í¼ì˜ ë‚´ìš©ì„ ì½ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"오디오 설정 변경"</string>
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³¼ë¥¨ ë° ê²½ë¡œ 지정 ê°™ì€ ì „ì²´ 오디오 ì„¤ì •ì„ ìˆ˜ì •í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 ë…¹ìŒ"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì˜¤ë””ì˜¤ 레코드 ê²½ë¡œì— ì•¡ì„¸ìŠ¤í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_camera" msgid="8059288807274039014">"사진 ì´¬ì˜"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¹´ë©”ë¼ë¡œ ì‚¬ì§„ì„ ì°ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¹´ë©”ë¼ì— 표시ë˜ëŠ” ì´ë¯¸ì§€ë¥¼ 언제든지 수집할 수 있습니다."</string>
+ <string name="permdesc_camera" msgid="9013476258810982546">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¹´ë©”ë¼ë¡œ ì‚¬ì§„ì„ ì°ì„ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¹´ë©”ë¼ì— 보여지는 í™”ë©´ì„ ì–¸ì œë“ ì§€ 수집할 수 있습니다."</string>
<string name="permlab_brick" msgid="8337817093326370537">"휴대전화를 ì˜êµ¬ì ìœ¼ë¡œ 사용 중지"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ë¥¼ ì˜êµ¬ì ìœ¼ë¡œ 사용 중지할 수 있게 합니다. ì´ ê¸°ëŠ¥ì€ ë§¤ìš° 위험합니다."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"휴대전화 강제로 다시 부팅"</string>
@@ -304,29 +320,39 @@
<string name="permlab_mount_unmount_filesystems" msgid="1761023272170956541">"파ì¼ì‹œìŠ¤í…œ 마운트 ë° ë§ˆìš´íŠ¸ í•´ì œ"</string>
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ë™ì‹ ì €ìž¥ì†Œì˜ íŒŒì¼ ì‹œìŠ¤í…œì„ ë§ˆìš´íŠ¸í•˜ê³  마운트 해제할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"외부 저장소 í¬ë§·"</string>
- <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì œê±° 가능한 저장소를 í¬ë§·í•˜ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ë™ì‹ 저장소를 í¬ë§·í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"보안 ì €ìž¥ì†Œì— ëŒ€í•œ ì •ë³´ 가져오기"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³´ì•ˆ ì €ìž¥ì†Œì˜ ì •ë³´ë¥¼ 가져올 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"보안 저장소 만들기"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³´ì•ˆ 저장소를 만들 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"보안 저장소 제거"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³´ì•ˆ 저장소를 제거할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"보안 저장소 마운트/마운트 해제"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³´ì•ˆ 저장소를 마운트/마운트 해제할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"보안 저장소 ì´ë¦„ 바꾸기"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë³´ì•ˆ ì €ìž¥ì†Œì˜ ì´ë¦„ì„ ë°”ê¿€ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"ì§„ë™ ì œì–´"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì§„ë™ì„ 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_flashlight" msgid="2155920810121984215">"ì†ì „등 제어"</string>
- <string name="permdesc_flashlight" msgid="6433045942283802309">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì†ì „ë“±ì„ ì œì–´í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_flashlight" msgid="2155920810121984215">"ì¹´ë©”ë¼ í”Œëž˜ì‹œ 제어"</string>
+ <string name="permdesc_flashlight" msgid="6433045942283802309">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¹´ë©”ë¼ í”Œëž˜ì‹œë¥¼ 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"하드웨어 테스트"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í•˜ë“œì›¨ì–´ë¥¼ 테스트할 목ì ìœ¼ë¡œ 다양한 주변장치를 제어할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_callPhone" msgid="3925836347681847954">"전화번호로 ì§ì ‘ 전화걸기"</string>
+ <string name="permlab_callPhone" msgid="3925836347681847954">"전화번호 ìžë™ ì—°ê²°"</string>
<string name="permdesc_callPhone" msgid="3369867353692722456">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ ì¡°ìž‘ ì—†ì´ ì „í™”ë²ˆí˜¸ë¡œ 전화를 걸 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘용프로그램으로 ì¸í•´ 예ìƒì¹˜ 못한 통화 ìš”ê¸ˆì´ ë¶€ê³¼ë  ìˆ˜ 있습니다. ì´ ê¶Œí•œìœ¼ë¡œ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¹„ìƒ ì „í™”ë¥¼ 걸게 í•  수는 없습니다."</string>
- <string name="permlab_callPrivileged" msgid="4198349211108497879">"전화번호로 ì§ì ‘ 전화걸기"</string>
- <string name="permdesc_callPrivileged" msgid="244405067160028452">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ ì¡°ìž‘ ì—†ì´ ë¹„ìƒ ë²ˆí˜¸ë¥¼ í¬í•¨í•œ 전화번호로 전화를 걸 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘급 서비스를 불필요하거나 불법ì ìœ¼ë¡œ 호출할 수 있습니다."</string>
+ <string name="permlab_callPrivileged" msgid="4198349211108497879">"모든 전화번호 ìžë™ ì—°ê²°"</string>
+ <string name="permdesc_callPrivileged" msgid="244405067160028452">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìžì˜ ì¡°ìž‘ ì—†ì´ ë¹„ìƒ ë²ˆí˜¸ë¥¼ í¬í•¨í•œ 전화번호로 전화를 걸 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘급 서비스를 불필요하게 ë˜ëŠ” 불법ì ìœ¼ë¡œ 호출할 수 있습니다."</string>
<string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"ì§ì ‘ CDMA ì „í™” 설정 시작"</string>
<string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ CDMA 프로비저ë‹ì„ 시작할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¶ˆí•„ìš”í•˜ê²Œ CDMA 프로비저ë‹ì„ 시작할 수 있습니다."</string>
<string name="permlab_locationUpdates" msgid="7785408253364335740">"위치 ì—…ë°ì´íŠ¸ 알림 제어"</string>
<string name="permdesc_locationUpdates" msgid="2300018303720930256">"ë¬´ì„ ì˜ ìœ„ì¹˜ ì—…ë°ì´íŠ¸ ì•Œë¦¼ì„ ì‚¬ìš©í•˜ê±°ë‚˜ 사용 중지할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_checkinProperties" msgid="7855259461268734914">"ì²´í¬ì¸ ì†ì„± 액세스"</string>
- <string name="permdesc_checkinProperties" msgid="7150307006141883832">"ì²´í¬ì¸ 서비스ì—ì„œ 업로드한 ì†ì„±ì— 대한 ì½ê¸°/쓰기 액세스를 허용합니다. ì¼ë°˜ ì‘용프로그램ì—서는 사용할 수 없습니다."</string>
+ <string name="permdesc_checkinProperties" msgid="7150307006141883832">"ì²´í¬ì¸ 서비스ì—ì„œ 업로드한 ì†ì„±ì— 대한 ì½ê¸°/쓰기 ì ‘ê·¼ì„ í—ˆìš©í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_bindGadget" msgid="776905339015863471">"위젯 ì„ íƒ"</string>
- <string name="permdesc_bindGadget" msgid="2098697834497452046">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‘용프로그램ì—ì„œ 사용할 수 있는 ìœ„ì ¯ì„ ì‹œìŠ¤í…œì— ì•Œë¦´ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¶Œí•œì„ ê°–ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ê°œì¸ ì •ë³´ì— ëŒ€í•œ 액세스 ê¶Œí•œì„ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì— ë¶€ì—¬í•  수 있습니다. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
+ <string name="permdesc_bindGadget" msgid="2098697834497452046">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì–´ë–¤ ì‘용프로그램ì—ì„œ ì–´ë–¤ ìœ„ì ¯ì„ ì‚¬ìš©í•  수 있는 지를 ì‹œìŠ¤í…œì— ì•Œë¦´ 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¶Œí•œì„ ê°–ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ê°œì¸ ì •ë³´ì— ëŒ€í•œ 액세스 ê¶Œí•œì„ ë‹¤ë¥¸ ì‘ìš©í”„ë¡œê·¸ëž¨ì— ë¶€ì—¬í•  수 있습니다. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="permlab_modifyPhoneState" msgid="8423923777659292228">"휴대전화 ìƒíƒœ 수정"</string>
<string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìž¥ì¹˜ì˜ íœ´ëŒ€ì „í™” ê¸°ëŠ¥ì„ ì œì–´í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¶Œí•œì„ ê°–ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ì‚¬ìš©ìžì—게 알리지 ì•Šê³  네트워í¬ë¥¼ 전환하거나 휴대전화 무선 ê¸°ëŠ¥ì„ ì¼œê³  ë„는 ë“±ì˜ ìž‘ì—…ì„ ìˆ˜í–‰í•  수 있습니다."</string>
<string name="permlab_readPhoneState" msgid="2326172951448691631">"휴대전화 ìƒíƒœ ë° ID ì½ê¸°"</string>
- <string name="permdesc_readPhoneState" msgid="188877305147626781">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìž¥ì¹˜ì˜ íœ´ëŒ€ì „í™” ê¸°ëŠ¥ì— ì•¡ì„¸ìŠ¤í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¶Œí•œì„ ê°–ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì€ íœ´ëŒ€ì „í™”ì˜ ì „í™”ë²ˆí˜¸ ë° ì¼ë ¨ë²ˆí˜¸, 통화가 활성ì¸ì§€ 여부, 해당 통화가 ì—°ê²°ëœ ë²ˆí˜¸ ë“±ì„ í™•ì¸í•  수 있습니다."</string>
+ <string name="permdesc_readPhoneState" msgid="188877305147626781">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìž¥ì¹˜ì˜ íœ´ëŒ€ì „í™” ê¸°ëŠ¥ì— ì ‘ê·¼í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê¶Œí•œì„ ê°–ëŠ” ì‘ìš©í”„ë¡œê·¸ëž¨ì€ íœ´ëŒ€ì „í™”ì˜ ì „í™”ë²ˆí˜¸ ë° ì¼ë ¨ë²ˆí˜¸, 통화가 활성ì¸ì§€ 여부, 해당 통화가 ì—°ê²°ëœ ë²ˆí˜¸ ë“±ì„ í™•ì¸í•  수 있습니다."</string>
<string name="permlab_wakeLock" msgid="573480187941496130">"휴대전화가 절전 모드로 전환ë˜ì§€ ì•Šë„ë¡ ì„¤ì •"</string>
<string name="permdesc_wakeLock" msgid="7584036471227467099">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ê°€ 절전 모드로 전환ë˜ì§€ ì•Šë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_devicePower" msgid="4928622470980943206">"휴대전화 ì „ì› ì¼œê³  ë„기"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‹œìŠ¤í…œ 배경화면 í¬ê¸° 힌트를 설정할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"ì‹œìŠ¤í…œì„ ê¸°ë³¸ê°’ìœ¼ë¡œ 재설정"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ëª¨ë“  ë°ì´í„°, 구성 ë° ì„¤ì¹˜ëœ ì‘ìš©í”„ë¡œê·¸ëž¨ì„ ì§€ì›Œì„œ ì‹œìŠ¤í…œì„ ì™„ì „ížˆ 초기화할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"시간 설정"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™” ì‹œê³„ì˜ ì‹œê°„ì„ ë³€ê²½í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"표준시간대 설정"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì˜ í‘œì¤€ì‹œê°„ëŒ€ë¥¼ 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"AccountManagerServiceë¡œ 활ë™"</string>
@@ -353,12 +381,14 @@
<string name="permdesc_useCredentials" msgid="7416570544619546974">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¸ì¦ 토í°ì„ 요청하ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_accessNetworkState" msgid="6865575199464405769">"ë„¤íŠ¸ì›Œí¬ ìƒíƒœ 보기"</string>
<string name="permdesc_accessNetworkState" msgid="558721128707712766">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ëª¨ë“  네트워í¬ì˜ ìƒíƒœë¥¼ ë³¼ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"ì¸í„°ë„·ì— 완전히 액세스"</string>
+ <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"ì¸í„°ë„·ì— 최대한 액세스"</string>
<string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë„¤íŠ¸ì›Œí¬ ì†Œì¼“ì„ ë§Œë“¤ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_writeApnSettings" msgid="7823599210086622545">"액세스í¬ì¸íŠ¸ ì´ë¦„ 설정 쓰기"</string>
+ <string name="permlab_writeApnSettings" msgid="7823599210086622545">"액세스í¬ì¸íŠ¸ ì´ë¦„(APN) 설정 쓰기"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ APNì˜ í”„ë¡ì‹œ ë° í¬íŠ¸ ê°™ì€ APN ì„¤ì •ì„ ìˆ˜ì •í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"ë„¤íŠ¸ì›Œí¬ ì—°ê²° 변경"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë„¤íŠ¸ì›Œí¬ ì—°ê²° ìƒíƒœë¥¼ 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë„¤íŠ¸ì›Œí¬ ì—°ê²° ìƒíƒœë¥¼ 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"í…ŒëŸ¬ë§ ì—°ê²° 변경"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í…Œë”ë§ëœ 네트워í¬ì˜ ì—°ê²° ìƒíƒœë¥¼ 변경할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"백그ë¼ìš´ë“œ ë°ì´í„° 사용 설정 변경"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë°±ê·¸ë¼ìš´ë“œ ë°ì´í„° 사용 ì„¤ì •ì„ ë³€ê²½í•  수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Wi-Fi ìƒíƒœ 보기"</string>
@@ -385,10 +415,22 @@
<string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ í˜„ìž¬ ë™ê¸°í™”ëœ í”¼ë“œë¥¼ 수정할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë™ê¸°í™”ëœ í”¼ë“œë¥¼ 변경할 수 있습니다."</string>
<string name="permlab_readDictionary" msgid="432535716804748781">"ì‚¬ìš©ìž ì •ì˜ ì‚¬ì „ ì½ê¸°"</string>
<string name="permdesc_readDictionary" msgid="1082972603576360690">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìž ì‚¬ì „ì— ë³´ê´€ë˜ì–´ 있는 비공개 단어, ì´ë¦„ ë° êµ¬ë¬¸ì„ ì½ë„ë¡ í•©ë‹ˆë‹¤."</string>
- <string name="permlab_writeDictionary" msgid="6703109511836343341">"ìƒìš©ìž ì •ì˜ ì‚¬ì „ì— ìž‘ì„±"</string>
+ <string name="permlab_writeDictionary" msgid="6703109511836343341">"사용ìžì •ì˜ ì‚¬ì „ì— ìž‘ì„±"</string>
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì‚¬ìš©ìž ì‚¬ì „ì— ìƒˆ 단어를 입력할 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD ì¹´ë“œ 콘í…츠 수정/ì‚­ì œ"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ SD ì¹´ë“œì— ì“¸ 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"ìºì‹œ 파ì¼ì‹œìŠ¤í…œ 액세스"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ìºì‹œ 파ì¼ì‹œìŠ¤í…œì„ ì½ê³  쓸 수 있ë„ë¡ í•©ë‹ˆë‹¤."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"비밀번호 제한"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"사용할 수 있는 비밀번호 ìœ í˜•ì„ ì œí•œí•©ë‹ˆë‹¤."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"ë¡œê·¸ì¸ ì‹œë„ ë³´ê¸°"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"몇 가지 ìž‘ì—…ì„ ìˆ˜í–‰í•˜ê¸° 위해 ê¸°ê¸°ì— ëŒ€í•´ 실패한 ë¡œê·¸ì¸ ì‹œë„를 모니터ë§í•©ë‹ˆë‹¤."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"비밀번호 재설정"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"비밀번호를 새 값으로 ê°•ì œ 설정합니다. ì´ë¥¼ 수행하려면 로그ì¸í•˜ê¸° ì „ì— ê´€ë¦¬ìžì—게 새로 지정할 비밀번호 ê°’ì„ ìš”ì²­í•´ì•¼ 합니다."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"강제 잠금"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"기기가 잠겨 ìžˆì„ ë•Œ ìž‘ë™í•˜ë ¤ë©´ 비밀번호를 다시 입력해야 합니다."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"모든 ë°ì´í„° ì‚­ì œ"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"초기화를 수행하여 모든 ë°ì´í„°ë¥¼ 확ì¸í•˜ì§€ ì•Šê³  삭제합니다."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"집"</item>
<item msgid="869923650527136615">"모바ì¼"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>ì„(를) 통해"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>(<xliff:g id="SOURCE">%2$s</xliff:g> 사용)"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN 코드 입력"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"ìž ê¸ˆì„ í•´ì œí•˜ë ¤ë©´ 비밀번호 ìž…ë ¥"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 코드가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"잠금해제하려면 메뉴를 누른 ë‹¤ìŒ 0ì„ ëˆ„ë¦…ë‹ˆë‹¤."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ë¹„ìƒ ì „í™”ë²ˆí˜¸"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"잠금해제하려면 메뉴를 누르세요."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"잠금해제를 위해 패턴 그리기"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"ë¹„ìƒ ì „í™”"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"통화로 ëŒì•„가기"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"맞습니다."</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"죄송합니다. 다시 ì‹œë„하세요."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,9 +547,9 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"SIM 카드가 없습니다."</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"íœ´ëŒ€ì „í™”ì— SIM 카드가 없습니다."</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"SIM 카드를 삽입하세요."</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"ë¹„ìƒ ì „í™”ë§Œ"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"긴급 통화만"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"ë„¤íŠ¸ì›Œí¬ ìž ê¹€"</string>
- <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM ì¹´ë“œì˜ PUKê°€ 잠겨 있습니다."</string>
+ <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM 카드가 PUK ìž ê¹€ ìƒíƒœìž…니다."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"ì‚¬ìš©ìž ê°€ì´ë“œë¥¼ 참조하거나 ê³ ê°ì§€ì›íŒ€ì— 문ì˜í•˜ì„¸ìš”."</string>
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 카드가 잠겨 있습니다."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM 카드 잠금해제 중..."</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"잠금해제"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"사운드 켜기"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"사운드 ë„기"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"지우기"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¸Œë¼ìš°ì €ë¡œ 방문한 모든 URLê³¼ 브ë¼ìš°ì €ì˜ 모든 ë¶ë§ˆí¬ë¥¼ ì½ë„ë¡ í—ˆìš©í•©ë‹ˆë‹¤."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"브ë¼ìš°ì €ì˜ ê¸°ë¡ ë° ë¶ë§ˆí¬ 쓰기"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ íœ´ëŒ€ì „í™”ì— ì €ìž¥ëœ ë¸Œë¼ìš°ì € ê¸°ë¡ ë˜ëŠ” ë¶ë§ˆí¬ë¥¼ 수정할 수 있ë„ë¡ í—ˆìš©í•©ë‹ˆë‹¤. ì´ ê²½ìš° 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¸Œë¼ìš°ì €ì˜ ë°ì´í„°ë¥¼ 지우거나 수정할 수 있습니다."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"브ë¼ìš°ì € 위치 ì •ë³´ 수정 권한"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ë¸Œë¼ìš°ì €ì˜ 위치 ì •ë³´ ê¶Œí•œì„ ìˆ˜ì •í•  수 있ë„ë¡ í•©ë‹ˆë‹¤. 악성 ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì´ë¥¼ 사용하여 ìž„ì˜ì˜ 웹사ì´íŠ¸ì— 위치 정보를 보낼 ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤."</string>
<string name="save_password_message" msgid="767344687139195790">"브ë¼ìš°ì €ì— ì´ ë¹„ë°€ë²ˆí˜¸ë¥¼ 저장하시겠습니까?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"나중ì—"</string>
<string name="save_password_remember" msgid="6491879678996749466">"저장"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1시간 전"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g>시간 전"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"지난 <xliff:g id="COUNT">%d</xliff:g>ì¼"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"지난 달"</string>
+ <string name="older" msgid="5211975022815554840">"ì´ì „"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"어제"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g>ì¼ ì „"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"주"</string>
<string name="year" msgid="4001118221013892076">"ë…„"</string>
<string name="years" msgid="6881577717993213522">"ë…„"</string>
- <string name="every_weekday" msgid="8777593878457748503">"주중 매ì¼(ì›”-금)"</string>
- <string name="daily" msgid="5738949095624133403">"매ì¼"</string>
- <string name="weekly" msgid="983428358394268344">"매주 <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"매월"</string>
- <string name="yearly" msgid="1519577999407493836">"매년"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"ë™ì˜ìƒ ìž¬ìƒ ì•ˆë¨"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"죄송합니다. ì´ ê¸°ê¸°ë¡œì˜ ìŠ¤íŠ¸ë¦¬ë°ì— ì í•©í•˜ì§€ ì•Šì€ ë™ì˜ìƒìž…니다."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"죄송합니다. ë™ì˜ìƒì„ 재ìƒí•  수 없습니다."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"닫기"</string>
<string name="report" msgid="4060218260984795706">"ì‹ ê³ "</string>
<string name="wait" msgid="7147118217226317732">"대기"</string>
- <string name="debug" msgid="9103374629678531849">"디버그"</string>
<string name="sendText" msgid="5132506121645618310">"í…ìŠ¤íŠ¸ì— ëŒ€í•œ ìž‘ì—… ì„ íƒ"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"벨소리 볼륨"</string>
<string name="volume_music" msgid="5421651157138628171">"미디어 볼륨"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"권한 í•„ìš” ì—†ìŒ"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"숨기기"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"ëª¨ë‘ í‘œì‹œ"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"로드 중..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 대용량 저장소"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB ì—°ê²°ë¨"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"USB를 통해 휴대전화를 ì»´í“¨í„°ì— ì—°ê²°í–ˆìŠµë‹ˆë‹¤. 컴퓨터와 휴대전화 SD ì¹´ë“œ ê°„ì— íŒŒì¼ì„ 복사하려면 \'마운트\'를 ì„ íƒí•˜ì„¸ìš”."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"마운트"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"마운트 안함"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"USB를 통해 휴대전화를 ì»´í“¨í„°ì— ì—°ê²°í–ˆìŠµë‹ˆë‹¤. 컴퓨터와 Androidì˜ SD ì¹´ë“œ ê°„ì— íŒŒì¼ì„ 복사하려면 ì•„ëž˜ì˜ ë²„íŠ¼ì„ ì„ íƒí•˜ì„¸ìš”."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB 저장소 사용"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"USB 저장소로 SD 카드를 사용하는 ë™ì•ˆ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB ì—°ê²°ë¨"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"ì»´í“¨í„°ì— íŒŒì¼ì„ 복사하거나 ì»´í“¨í„°ì˜ íŒŒì¼ì„ 복사하려면 ì„ íƒí•©ë‹ˆë‹¤."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB 저장소 ë„기"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USB 저장소 ë„기를 ì„ íƒí•˜ì„¸ìš”."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"USB 저장소 ë„기"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"USB 저장소를 ë„기 ì „ì— ë°˜ë“œì‹œ USB 호스트ì—ì„œ 마운트 해제하세요. USB 저장소를 ë„려면 \'ë„기\'를 ì„ íƒí•˜ì„¸ìš”."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"USB 저장소 ë„기"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"취소"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"USB 저장소를 ë„는 ë™ì•ˆ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. USB 호스트와 ì—°ê²°ì„ í•´ì œí–ˆëŠ”ì§€ 확ì¸í•œ ë‹¤ìŒ ë‹¤ì‹œ ì‹œë„하세요."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB 저장소 사용 중"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"USB 저장소를 사용하지 ì•Šë„ë¡ ì„¤ì •í•˜ê¸° ì „ì— ì»´í“¨í„°ì—ì„œ Androidì˜ SD 카드를 마운트 해제했는지(꺼냈는지) 확ì¸í•˜ì‹œê¸° ë°”ëžë‹ˆë‹¤."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB 저장소 사용 안함"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USB 저장소를 사용하지 ì•Šë„ë¡ ì„¤ì •í•˜ëŠ” ë™ì•ˆ 문제가 ë°œìƒí–ˆìŠµë‹ˆë‹¤. USB 호스트와 ì—°ê²°ì„ í•´ì œí–ˆëŠ”ì§€ 확ì¸í•œ ë‹¤ìŒ ë‹¤ì‹œ ì‹œë„하세요."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB 저장소 사용"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USB 저장소를 사용 설정하면 사용 ì¤‘ì¸ ì¼ë¶€ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ì¤‘ì§€ë˜ê³  USB 저장소를 사용 중지할 때까지 사용할 수 없게 ë©ë‹ˆë‹¤."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB 작업 실패"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"확ì¸"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"SD ì¹´ë“œ í¬ë§·"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"SD 카드를 í¬ë§·í•˜ì‹œê² ìŠµë‹ˆê¹Œ? í¬ë§·í•˜ë©´ ì¹´ë“œì˜ ëª¨ë“  ë°ì´í„°ë¥¼ 잃게 ë©ë‹ˆë‹¤."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"í¬ë§·"</string>
@@ -762,15 +812,17 @@
<string name="ext_media_unmountable_notification_message" msgid="6902531775948238989">"SD 카드가 ì†ìƒë˜ì—ˆìŠµë‹ˆë‹¤. 카드를 다시 í¬ë§·í•´ì•¼ í•  수 있습니다."</string>
<string name="ext_media_badremoval_notification_title" msgid="6872152882604407837">"SD 카드가 예ìƒì¹˜ 않게 제거ë˜ì—ˆìŠµë‹ˆë‹¤."</string>
<string name="ext_media_badremoval_notification_message" msgid="7260183293747448241">"ë°ì´í„° ì†ì‹¤ì„ 피하려면 SD 카드를 제거하기 ì „ì— ë§ˆìš´íŠ¸ 해제합니다."</string>
- <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD 카드를 안전하게 제거할 수 있습니다."</string>
+ <string name="ext_media_safe_unmount_notification_title" msgid="6729801130790616200">"SD 카드 제거 가능"</string>
<string name="ext_media_safe_unmount_notification_message" msgid="568841278138377604">"안전하게 SD 카드를 제거할 수 있습니다."</string>
- <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD 카드를 제거했습니다."</string>
- <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 카드가 제거ë˜ì—ˆìŠµë‹ˆë‹¤. 새 카드를 넣으세요."</string>
+ <string name="ext_media_nomedia_notification_title" msgid="8902518030404381318">"SD ì¹´ë“œ ì—†ìŒ"</string>
+ <string name="ext_media_nomedia_notification_message" msgid="3870120652983659641">"SD 카드가 없습니다. SD 카드를 넣으세요."</string>
<string name="activity_list_empty" msgid="4168820609403385789">"ì¼ì¹˜í•˜ëŠ” 활ë™ì´ 없습니다."</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"구성 요소 사용 통계 ì—…ë°ì´íŠ¸"</string>
- <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"ìˆ˜ì§‘ëœ êµ¬ì„±ìš”ì†Œ 사용 통계를 수정할 수 있는 ê¶Œí•œì„ ë¶€ì—¬í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ì´ ê¶Œí•œì„ ì‚¬ìš©í•  수 없습니다."</string>
+ <string name="permdesc_pkgUsageStats" msgid="891553695716752835">"ìˆ˜ì§‘ëœ êµ¬ì„±ìš”ì†Œ 사용 통계를 수정할 수 있는 ê¶Œí•œì„ ë¶€ì—¬í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘ìš©í”„ë¡œê·¸ëž¨ì€ ì´ ê¶Œí•œì„ ì‚¬ìš©í•˜ì§€ 않습니다."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"기본 컨테ì´ë„ˆ 서비스를 호출하여 콘í…츠를 복사할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"기본 컨테ì´ë„ˆ 서비스를 호출하여 콘í…츠를 복사할 수 있ë„ë¡ í•©ë‹ˆë‹¤. ì¼ë°˜ ì‘용프로그램ì—서는 사용하지 않습니다."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"확대/축소하려면 ë‘ ë²ˆ 탭하세요."</string>
- <string name="gadget_host_error_inflating" msgid="2613287218853846830">"ìœ„ì ¯ì„ í™•ìž¥í•˜ëŠ” ë™ì•ˆ 오류가 ë°œìƒí–ˆìŠµë‹ˆë‹¤."</string>
+ <string name="gadget_host_error_inflating" msgid="2613287218853846830">"ìœ„ì ¯ì„ ìƒì„±í•˜ëŠ” 과정(inflate)ì— ì˜¤ë¥˜ê°€ ë°œìƒí–ˆìŠµë‹ˆë‹¤."</string>
<string name="ime_action_go" msgid="8320845651737369027">"ì´ë™"</string>
<string name="ime_action_search" msgid="658110271822807811">"검색"</string>
<string name="ime_action_send" msgid="2316166556349314424">"전송"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"전화번호부ì—"\n"<xliff:g id="NUMBER">%s</xliff:g> 추가"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"ì„ íƒí•¨"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ì„ íƒ ì•ˆí•¨"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"ë‚˜ì—´ëœ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ <xliff:g id="APPLICATION">%2$s</xliff:g>ì—ì„œ <xliff:g id="ACCOUNT">%1$s</xliff:g> ê³„ì •ì˜ ë¡œê·¸ì¸ ìžê²©ì¦ëª…ì— ì•¡ì„¸ìŠ¤í•  수 있는 ê¶Œí•œì„ ìš”ì²­ 중입니다. ê¶Œí•œì„ ë¶€ì—¬í•˜ì‹œê² ìŠµë‹ˆê¹Œ? ê¶Œí•œì„ ë¶€ì—¬í•˜ë©´ ì‘답 ë‚´ìš©ì´ ì €ìž¥ë˜ë©° 메시지가 다시 표시ë˜ì§€ 않습니다."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"ë‚˜ì—´ëœ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ <xliff:g id="APPLICATION">%3$s</xliff:g>ì—ì„œ <xliff:g id="ACCOUNT">%2$s</xliff:g> ê³„ì •ì˜ <xliff:g id="TYPE">%1$s</xliff:g> ë¡œê·¸ì¸ ìžê²©ì¦ëª…ì— ì•¡ì„¸ìŠ¤í•  수 있는 ê¶Œí•œì„ ìš”ì²­ 중입니다. ê¶Œí•œì„ ë¶€ì—¬í•˜ì‹œê² ìŠµë‹ˆê¹Œ? ê¶Œí•œì„ ë¶€ì—¬í•˜ë©´ ì‘답 ë‚´ìš©ì´ ì €ìž¥ë˜ë©° 메시지가 다시 표시ë˜ì§€ 않습니다."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"현재 ì´í›„ë¡œ 하나 ì´ìƒì˜ ë‹¤ìŒ ì‘ìš©í”„ë¡œê·¸ëž¨ì´ ê³„ì •ì— ëŒ€í•œ 액세스 ê¶Œí•œì„ ìš”ì²­í•©ë‹ˆë‹¤."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"ìš”ì²­ì„ í—ˆìš©í•˜ì‹œê² ìŠµë‹ˆê¹Œ?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"액세스 요청"</string>
<string name="allow" msgid="7225948811296386551">"허용"</string>
<string name="deny" msgid="2081879885755434506">"거부"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"권한 요청"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"L2TP(Layer 2 Tunneling Protocol)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"사전 공유 키 기반 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"ì¸ì¦ì„œ 기반 L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"íŒŒì¼ ì„ íƒ"</string>
+ <string name="reset" msgid="2448168080964209908">"재설정"</string>
+ <string name="submit" msgid="1602335572089911941">"제출"</string>
+ <string name="description_star" msgid="2654319874908576133">"ì¦ê²¨ì°¾ê¸°"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"차량 모드 사용"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"차량 모드를 종료하려면 ì„ íƒí•˜ì„¸ìš”."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"í…Œë”ë§ ì‚¬ìš©ì¤‘"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"구성하려면 터치하세요."</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"ë†’ì€ ëª¨ë°”ì¼ ë°ì´í„° 사용량"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"ëª¨ë°”ì¼ ë°ì´í„° ì‚¬ìš©ì— ëŒ€í•´ ìžì„¸ížˆ 알아보려면 터치하세요."</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"ëª¨ë°”ì¼ ë°ì´í„° ì œí•œì„ ì´ˆê³¼í–ˆìŠµë‹ˆë‹¤."</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"ëª¨ë°”ì¼ ë°ì´í„° ì‚¬ìš©ì— ëŒ€í•´ ìžì„¸ížˆ 알아보려면 터치하세요."</string>
</resources>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
new file mode 100644
index 0000000..647a562
--- /dev/null
+++ b/core/res/res/values-land/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<resources>
+ <dimen name="password_keyboard_key_height">47dip</dimen>
+ <dimen name="password_keyboard_spacebar_vertical_correction">2dip</dimen>
+</resources> \ No newline at end of file
diff --git a/core/res/res/values-mcc204-cs/strings.xml b/core/res/res/values-mcc204-cs/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-cs/strings.xml
+++ b/core/res/res/values-mcc204-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-da/strings.xml b/core/res/res/values-mcc204-da/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-da/strings.xml
+++ b/core/res/res/values-mcc204-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-de/strings.xml b/core/res/res/values-mcc204-de/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-de/strings.xml
+++ b/core/res/res/values-mcc204-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-el/strings.xml b/core/res/res/values-mcc204-el/strings.xml
index 94786f1..97bfe65 100644
--- a/core/res/res/values-mcc204-el/strings.xml
+++ b/core/res/res/values-mcc204-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc204-es-rUS/strings.xml b/core/res/res/values-mcc204-es-rUS/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-es-rUS/strings.xml
+++ b/core/res/res/values-mcc204-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-es/strings.xml b/core/res/res/values-mcc204-es/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-es/strings.xml
+++ b/core/res/res/values-mcc204-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-fr/strings.xml b/core/res/res/values-mcc204-fr/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-fr/strings.xml
+++ b/core/res/res/values-mcc204-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-it/strings.xml b/core/res/res/values-mcc204-it/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-it/strings.xml
+++ b/core/res/res/values-mcc204-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-ja/strings.xml b/core/res/res/values-mcc204-ja/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-ja/strings.xml
+++ b/core/res/res/values-mcc204-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-ko/strings.xml b/core/res/res/values-mcc204-ko/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-ko/strings.xml
+++ b/core/res/res/values-mcc204-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-nl/strings.xml b/core/res/res/values-mcc204-nl/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-nl/strings.xml
+++ b/core/res/res/values-mcc204-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-pl/strings.xml b/core/res/res/values-mcc204-pl/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-pl/strings.xml
+++ b/core/res/res/values-mcc204-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-pt-rPT/strings.xml b/core/res/res/values-mcc204-pt-rPT/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc204-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-pt/strings.xml b/core/res/res/values-mcc204-pt/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-pt/strings.xml
+++ b/core/res/res/values-mcc204-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-ru/strings.xml b/core/res/res/values-mcc204-ru/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-ru/strings.xml
+++ b/core/res/res/values-mcc204-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-sv/strings.xml b/core/res/res/values-mcc204-sv/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-sv/strings.xml
+++ b/core/res/res/values-mcc204-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-tr/strings.xml b/core/res/res/values-mcc204-tr/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-tr/strings.xml
+++ b/core/res/res/values-mcc204-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-zh-rCN/strings.xml b/core/res/res/values-mcc204-zh-rCN/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc204-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc204-zh-rTW/strings.xml b/core/res/res/values-mcc204-zh-rTW/strings.xml
index 94786f1..7d96230 100644
--- a/core/res/res/values-mcc204-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc204-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
+ <string name="locale_replacement">"nl_nl"</string>
</resources>
diff --git a/core/res/res/values-mcc230-cs/strings.xml b/core/res/res/values-mcc230-cs/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-cs/strings.xml
+++ b/core/res/res/values-mcc230-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-da/strings.xml b/core/res/res/values-mcc230-da/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-da/strings.xml
+++ b/core/res/res/values-mcc230-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-de/strings.xml b/core/res/res/values-mcc230-de/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-de/strings.xml
+++ b/core/res/res/values-mcc230-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-el/strings.xml b/core/res/res/values-mcc230-el/strings.xml
index 63ade62..97bfe65 100644
--- a/core/res/res/values-mcc230-el/strings.xml
+++ b/core/res/res/values-mcc230-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc230-es-rUS/strings.xml b/core/res/res/values-mcc230-es-rUS/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-es-rUS/strings.xml
+++ b/core/res/res/values-mcc230-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-es/strings.xml b/core/res/res/values-mcc230-es/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-es/strings.xml
+++ b/core/res/res/values-mcc230-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-fr/strings.xml b/core/res/res/values-mcc230-fr/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-fr/strings.xml
+++ b/core/res/res/values-mcc230-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-it/strings.xml b/core/res/res/values-mcc230-it/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-it/strings.xml
+++ b/core/res/res/values-mcc230-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-ja/strings.xml b/core/res/res/values-mcc230-ja/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-ja/strings.xml
+++ b/core/res/res/values-mcc230-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-ko/strings.xml b/core/res/res/values-mcc230-ko/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-ko/strings.xml
+++ b/core/res/res/values-mcc230-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-nl/strings.xml b/core/res/res/values-mcc230-nl/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-nl/strings.xml
+++ b/core/res/res/values-mcc230-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-pl/strings.xml b/core/res/res/values-mcc230-pl/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-pl/strings.xml
+++ b/core/res/res/values-mcc230-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-pt-rPT/strings.xml b/core/res/res/values-mcc230-pt-rPT/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc230-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-pt/strings.xml b/core/res/res/values-mcc230-pt/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-pt/strings.xml
+++ b/core/res/res/values-mcc230-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-ru/strings.xml b/core/res/res/values-mcc230-ru/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-ru/strings.xml
+++ b/core/res/res/values-mcc230-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-sv/strings.xml b/core/res/res/values-mcc230-sv/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-sv/strings.xml
+++ b/core/res/res/values-mcc230-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-tr/strings.xml b/core/res/res/values-mcc230-tr/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-tr/strings.xml
+++ b/core/res/res/values-mcc230-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-zh-rCN/strings.xml b/core/res/res/values-mcc230-zh-rCN/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc230-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc230-zh-rTW/strings.xml b/core/res/res/values-mcc230-zh-rTW/strings.xml
index 63ade62..d3ecdbb 100644
--- a/core/res/res/values-mcc230-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc230-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
+ <string name="locale_replacement">"cs_cz"</string>
</resources>
diff --git a/core/res/res/values-mcc232-cs/strings.xml b/core/res/res/values-mcc232-cs/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-cs/strings.xml
+++ b/core/res/res/values-mcc232-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-da/strings.xml b/core/res/res/values-mcc232-da/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-da/strings.xml
+++ b/core/res/res/values-mcc232-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-de/strings.xml b/core/res/res/values-mcc232-de/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-de/strings.xml
+++ b/core/res/res/values-mcc232-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-el/strings.xml b/core/res/res/values-mcc232-el/strings.xml
index b028927..97bfe65 100644
--- a/core/res/res/values-mcc232-el/strings.xml
+++ b/core/res/res/values-mcc232-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc232-es-rUS/strings.xml b/core/res/res/values-mcc232-es-rUS/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-es-rUS/strings.xml
+++ b/core/res/res/values-mcc232-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-es/strings.xml b/core/res/res/values-mcc232-es/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-es/strings.xml
+++ b/core/res/res/values-mcc232-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-fr/strings.xml b/core/res/res/values-mcc232-fr/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-fr/strings.xml
+++ b/core/res/res/values-mcc232-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-it/strings.xml b/core/res/res/values-mcc232-it/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-it/strings.xml
+++ b/core/res/res/values-mcc232-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-ja/strings.xml b/core/res/res/values-mcc232-ja/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-ja/strings.xml
+++ b/core/res/res/values-mcc232-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-ko/strings.xml b/core/res/res/values-mcc232-ko/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-ko/strings.xml
+++ b/core/res/res/values-mcc232-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-nl/strings.xml b/core/res/res/values-mcc232-nl/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-nl/strings.xml
+++ b/core/res/res/values-mcc232-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-pl/strings.xml b/core/res/res/values-mcc232-pl/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-pl/strings.xml
+++ b/core/res/res/values-mcc232-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-pt-rPT/strings.xml b/core/res/res/values-mcc232-pt-rPT/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc232-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-pt/strings.xml b/core/res/res/values-mcc232-pt/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-pt/strings.xml
+++ b/core/res/res/values-mcc232-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-ru/strings.xml b/core/res/res/values-mcc232-ru/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-ru/strings.xml
+++ b/core/res/res/values-mcc232-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-sv/strings.xml b/core/res/res/values-mcc232-sv/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-sv/strings.xml
+++ b/core/res/res/values-mcc232-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-tr/strings.xml b/core/res/res/values-mcc232-tr/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-tr/strings.xml
+++ b/core/res/res/values-mcc232-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-zh-rCN/strings.xml b/core/res/res/values-mcc232-zh-rCN/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc232-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc232-zh-rTW/strings.xml b/core/res/res/values-mcc232-zh-rTW/strings.xml
index b028927..4773838 100644
--- a/core/res/res/values-mcc232-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc232-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
+ <string name="locale_replacement">"de_at"</string>
</resources>
diff --git a/core/res/res/values-mcc234-cs/strings.xml b/core/res/res/values-mcc234-cs/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-cs/strings.xml
+++ b/core/res/res/values-mcc234-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-da/strings.xml b/core/res/res/values-mcc234-da/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-da/strings.xml
+++ b/core/res/res/values-mcc234-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-de/strings.xml b/core/res/res/values-mcc234-de/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-de/strings.xml
+++ b/core/res/res/values-mcc234-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-el/strings.xml b/core/res/res/values-mcc234-el/strings.xml
index bd391e1..97bfe65 100644
--- a/core/res/res/values-mcc234-el/strings.xml
+++ b/core/res/res/values-mcc234-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc234-es-rUS/strings.xml b/core/res/res/values-mcc234-es-rUS/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-es-rUS/strings.xml
+++ b/core/res/res/values-mcc234-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-es/strings.xml b/core/res/res/values-mcc234-es/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-es/strings.xml
+++ b/core/res/res/values-mcc234-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-fr/strings.xml b/core/res/res/values-mcc234-fr/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-fr/strings.xml
+++ b/core/res/res/values-mcc234-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-it/strings.xml b/core/res/res/values-mcc234-it/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-it/strings.xml
+++ b/core/res/res/values-mcc234-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-ja/strings.xml b/core/res/res/values-mcc234-ja/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-ja/strings.xml
+++ b/core/res/res/values-mcc234-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-ko/strings.xml b/core/res/res/values-mcc234-ko/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-ko/strings.xml
+++ b/core/res/res/values-mcc234-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-nl/strings.xml b/core/res/res/values-mcc234-nl/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-nl/strings.xml
+++ b/core/res/res/values-mcc234-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-pl/strings.xml b/core/res/res/values-mcc234-pl/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-pl/strings.xml
+++ b/core/res/res/values-mcc234-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-pt-rPT/strings.xml b/core/res/res/values-mcc234-pt-rPT/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc234-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-pt/strings.xml b/core/res/res/values-mcc234-pt/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-pt/strings.xml
+++ b/core/res/res/values-mcc234-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-ru/strings.xml b/core/res/res/values-mcc234-ru/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-ru/strings.xml
+++ b/core/res/res/values-mcc234-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-sv/strings.xml b/core/res/res/values-mcc234-sv/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-sv/strings.xml
+++ b/core/res/res/values-mcc234-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-tr/strings.xml b/core/res/res/values-mcc234-tr/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-tr/strings.xml
+++ b/core/res/res/values-mcc234-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-zh-rCN/strings.xml b/core/res/res/values-mcc234-zh-rCN/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc234-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc234-zh-rTW/strings.xml b/core/res/res/values-mcc234-zh-rTW/strings.xml
index bd391e1..2538b73 100644
--- a/core/res/res/values-mcc234-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc234-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
+ <string name="locale_replacement">"en_gb"</string>
</resources>
diff --git a/core/res/res/values-mcc260-cs/strings.xml b/core/res/res/values-mcc260-cs/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-cs/strings.xml
+++ b/core/res/res/values-mcc260-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-da/strings.xml b/core/res/res/values-mcc260-da/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-da/strings.xml
+++ b/core/res/res/values-mcc260-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-de/strings.xml b/core/res/res/values-mcc260-de/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-de/strings.xml
+++ b/core/res/res/values-mcc260-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-el/strings.xml b/core/res/res/values-mcc260-el/strings.xml
index 13ea1b2..97bfe65 100644
--- a/core/res/res/values-mcc260-el/strings.xml
+++ b/core/res/res/values-mcc260-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc260-es-rUS/strings.xml b/core/res/res/values-mcc260-es-rUS/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-es-rUS/strings.xml
+++ b/core/res/res/values-mcc260-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-es/strings.xml b/core/res/res/values-mcc260-es/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-es/strings.xml
+++ b/core/res/res/values-mcc260-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-fr/strings.xml b/core/res/res/values-mcc260-fr/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-fr/strings.xml
+++ b/core/res/res/values-mcc260-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-it/strings.xml b/core/res/res/values-mcc260-it/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-it/strings.xml
+++ b/core/res/res/values-mcc260-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-ja/strings.xml b/core/res/res/values-mcc260-ja/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-ja/strings.xml
+++ b/core/res/res/values-mcc260-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-ko/strings.xml b/core/res/res/values-mcc260-ko/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-ko/strings.xml
+++ b/core/res/res/values-mcc260-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-nb/strings.xml b/core/res/res/values-mcc260-nb/strings.xml
deleted file mode 100644
index 13ea1b2..0000000
--- a/core/res/res/values-mcc260-nb/strings.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 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.
--->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
-</resources>
diff --git a/core/res/res/values-mcc260-nl/strings.xml b/core/res/res/values-mcc260-nl/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-nl/strings.xml
+++ b/core/res/res/values-mcc260-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-pl/strings.xml b/core/res/res/values-mcc260-pl/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-pl/strings.xml
+++ b/core/res/res/values-mcc260-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-pt-rPT/strings.xml b/core/res/res/values-mcc260-pt-rPT/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc260-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-pt/strings.xml b/core/res/res/values-mcc260-pt/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-pt/strings.xml
+++ b/core/res/res/values-mcc260-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-ru/strings.xml b/core/res/res/values-mcc260-ru/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-ru/strings.xml
+++ b/core/res/res/values-mcc260-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-sv/strings.xml b/core/res/res/values-mcc260-sv/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-sv/strings.xml
+++ b/core/res/res/values-mcc260-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-tr/strings.xml b/core/res/res/values-mcc260-tr/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-tr/strings.xml
+++ b/core/res/res/values-mcc260-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-zh-rCN/strings.xml b/core/res/res/values-mcc260-zh-rCN/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc260-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc260-zh-rTW/strings.xml b/core/res/res/values-mcc260-zh-rTW/strings.xml
index 13ea1b2..1161f9a 100644
--- a/core/res/res/values-mcc260-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc260-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="6306793451973344945">"pl_pl"</string>
+ <string name="locale_replacement">"pl_pl"</string>
</resources>
diff --git a/core/res/res/values-mcc262-cs/strings.xml b/core/res/res/values-mcc262-cs/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-cs/strings.xml
+++ b/core/res/res/values-mcc262-cs/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-da/strings.xml b/core/res/res/values-mcc262-da/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-da/strings.xml
+++ b/core/res/res/values-mcc262-da/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-de/strings.xml b/core/res/res/values-mcc262-de/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-de/strings.xml
+++ b/core/res/res/values-mcc262-de/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-el/strings.xml b/core/res/res/values-mcc262-el/strings.xml
index a90e7cf..97bfe65 100644
--- a/core/res/res/values-mcc262-el/strings.xml
+++ b/core/res/res/values-mcc262-el/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"el_GR"</string>
</resources>
diff --git a/core/res/res/values-mcc262-es-rUS/strings.xml b/core/res/res/values-mcc262-es-rUS/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-es-rUS/strings.xml
+++ b/core/res/res/values-mcc262-es-rUS/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-es/strings.xml b/core/res/res/values-mcc262-es/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-es/strings.xml
+++ b/core/res/res/values-mcc262-es/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-fr/strings.xml b/core/res/res/values-mcc262-fr/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-fr/strings.xml
+++ b/core/res/res/values-mcc262-fr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-it/strings.xml b/core/res/res/values-mcc262-it/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-it/strings.xml
+++ b/core/res/res/values-mcc262-it/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-ja/strings.xml b/core/res/res/values-mcc262-ja/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-ja/strings.xml
+++ b/core/res/res/values-mcc262-ja/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-ko/strings.xml b/core/res/res/values-mcc262-ko/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-ko/strings.xml
+++ b/core/res/res/values-mcc262-ko/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-nl/strings.xml b/core/res/res/values-mcc262-nl/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-nl/strings.xml
+++ b/core/res/res/values-mcc262-nl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-pl/strings.xml b/core/res/res/values-mcc262-pl/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-pl/strings.xml
+++ b/core/res/res/values-mcc262-pl/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-pt-rPT/strings.xml b/core/res/res/values-mcc262-pt-rPT/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc262-pt-rPT/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-pt/strings.xml b/core/res/res/values-mcc262-pt/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-pt/strings.xml
+++ b/core/res/res/values-mcc262-pt/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-ru/strings.xml b/core/res/res/values-mcc262-ru/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-ru/strings.xml
+++ b/core/res/res/values-mcc262-ru/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-sv/strings.xml b/core/res/res/values-mcc262-sv/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-sv/strings.xml
+++ b/core/res/res/values-mcc262-sv/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-tr/strings.xml b/core/res/res/values-mcc262-tr/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-tr/strings.xml
+++ b/core/res/res/values-mcc262-tr/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-zh-rCN/strings.xml b/core/res/res/values-mcc262-zh-rCN/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc262-zh-rCN/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-mcc262-zh-rTW/strings.xml b/core/res/res/values-mcc262-zh-rTW/strings.xml
index a90e7cf..9505cf4 100644
--- a/core/res/res/values-mcc262-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc262-zh-rTW/strings.xml
@@ -15,5 +15,5 @@
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+ <string name="locale_replacement">"de_de"</string>
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index b012e12..318fc15 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Tilgangsbegrensning endret"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Datatjenesten er blokkert."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Nødtjenesten er blokkert."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Tale-/SMS-tjenester er blokkert."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle tale-/SMS-tjenester er blokkert."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Taletjenesten er blokkert."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Alle taletjenester er blokkert."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Tekstmeldingstjenesten er blokkert."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Alle tjenester for tale og data er blokkert."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Tjenester for tale og tekstmeldinger er blokkert."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Alle tjenester for tale, data og tekstmeldinger er blokkert."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Tale"</string>
<string name="serviceClassData" msgid="872456782077937893">"Data"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"Fax"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Slå av"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Avslutter…"</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Telefonen vil bli slått av."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Nylig"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Ingen nylig brukte applikasjoner."</string>
<string name="global_actions" msgid="2406416831541615258">"Telefoninnstillinger"</string>
<string name="global_action_lock" msgid="2844945191792119712">"LÃ¥s skjermen"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Lar applikasjonen skru på debugging for en annen applikasjon. Ondsinnede applikasjoner kan bruke dette til å drepe andre applikasjoner."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"endre innstillingene for brukergrensesnitt"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillater applikasjonen å endre gjeldende innstillinger, slik som språk eller skriftstørrelse."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"omstarte andre applikasjoner"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Lar applikasjonen tvinge andre applikasjoner til å starte på nytt."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"aktiver bilmodus"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Tillater et program å aktivere bilmodus."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"avslutt bakgrunnsprosesser"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Tillater et program å avslutte bakgrunnsprosesser for andre programmer, selv om det er nok minne."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"fremtving stopp av andre programmer"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Tillater et program å framtvinge stopp av andre programmer."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"tvinge applikasjoner til å lukkes"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Lar applikasjonen tvinge enhver aktivitet som er i forgrunnen til å lukkes og gå tilbake. Vanlige applikasjoner bør aldri trenge dette."</string>
<string name="permlab_dump" msgid="1681799862438954752">"hente intern systemtilstand"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Lar applikasjonen endre på innsamlet batteristatistikk. Ikke ment for vanlige applikasjoner."</string>
<string name="permlab_backup" msgid="470013022865453920">"kontrollere backup og gjenoppretting"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Lar applikasjonen kontrollere systemets backup- og gjenopprettingsmekanisme. Ikke ment for vanlige applikasjoner."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"foreta backup og gjenoppretting av applikasjonens data"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Lar applikasjonen delta i systemets backup- og gjenopprettingsmekanisme."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"vis uautoriserte vinduer"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillater at det opprettes vinduer ment for bruk av systemets interne brukergrensesnitt. Ikke ment for vanlige applikasjoner."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"vise advarsler på systemnivå"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Lar applikasjonen binde til toppnivågrensesnittet for en inndatametode. Vanlige applikasjoner bør aldri trenge dette."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"bind til bakgrunnsbilde"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Lar innehaveren binde det øverste nivået av grensesnittet til en bakgrunnsbilder. Skal ikke være nødvendig for vanlige programmer."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"kommuniser med enhetsadministrator"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Tillater innehaveren å sende hensikter til enhetsadministrator. Bør aldri være nødvendig for normale programmer."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"snu skjermen"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Lar applikasjonen rotere skjermen når som helst. Vanlige applikasjoner bør aldri trenge dette."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"sende Linux-signaler til applikasjoner"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Lar applikasjonen installere nye eller oppdaterte Android-pakker. Ondsinnede applikasjoner kan bruke dette til å legge til nye applikasjoner med vilkårlig kraftige rettigheter."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"slette hurtigbufferdata for alle applikasjoner"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Lar applikasjonen frigjøre lagringsplass ved å slette filer i applikasjoners hurtigbufferkatalog. Tilgangen er vanligvis sterkt begrenset, til systemprosesser."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Flytter programressurser"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Gir et program tillatelse til å flytte programressurser fra interne til eksterne medier og omvendt."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"lese systemets loggfiler"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Lar applikasjonen lese fra diverse loggfiler på systemet. Disse inneholder generell informasjon om hva som gjøres med telefonen, men skal ikke inneholde personlig eller privat informasjon."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lese/skrive ressurser eid av diag"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Lar applikasjonen endre dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å slette eller redigere telefonens eierdata."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"lese eierinformasjon"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Lar applikasjonen lese dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å lese telefonens eierdata."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"lese kalenderinformasjon"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"les kalenderaktiviteter"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Lar applikasjonen lese alle kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende kalenderhendelser til andre."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"skrive kalenderinformasjon"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Lar applikasjonen endre kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å slette eller endre kalenderinformasjon."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"legg til eller endre kalenderaktiviteter og send e-postmelding til gjestene"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Gir et program tillatelse til å legge til eller endre aktiviteter i kalenderen. Dette kan medføre at det sendes e-postmelding til deltakerne. Skadelige programmer kan bruke dette til å slette eller endre kalenderaktiviteter eller sende e-postmeldinger til deltakerne."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"lage simulerte plasseringskilder for testing"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Lage simulerte plassingskilder for testing. Ondsinnede applikasjoner kan bruke dette til å overstyre plasseringen og/eller statusen rapportert av ekte plasseringskilder slik som GPS eller nettverksoperatører."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få tilgang til ekstra plasseringskommandoer"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Lar applikasjonen montere og avmontere filsystemer for uttagbar lagring."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatere ekstern lagringsplass"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Lar applikasjonen formatere ekstern lagringsplass."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"få informasjon om sikker lagring"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Tillater programmet å innhente informasjon om sikker lagring."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"opprette sikker lagring"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Tillater programmet å opprette sikker lagring."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"stenge sikker lagring"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Tillater programmet å stenge sikker lagring."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"koble til eller fra sikker lagring"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Tillater programmet å koble sikker lagring til eller fra."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"gi nytt navn til sikker lagring"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Tillater programmet å gi nytt navn til sikker lagring."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"kontrollere vibratoren"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Lar applikasjonen kontrollere vibratoren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontrollere lommelykten"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Lar applikasjonen sette størrelseshint for systemets bakgrunnsbilde."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"tilbakestille systemet til fabrikkinnstillinger"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Lar applikasjonen tilbakestille systemet til fabrikkinnstillinger, noe som vil fjerne alle data, alt oppsett, og alle installerte applikasjoner."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"stille klokken"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Tillater et program å stille klokken på telefonen."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"endre tidssone"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Lar applikasjonen endre telefonens tidssone."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"fungere som kontoadministrasjonstjenesten"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"skrive APN-innstillinger"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Lar applikasjonen to endre APN-innstillinger slik som mellomtjener eller port for hvilket som helst aksesspunkt."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"endre nettverkskonnektivitet"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Lar applikasjonen endre tilstanden til nettverkskonnektivitet."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Tillater et program å endre innstillingene for nettverkstilkoblingen."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Endre tilknytningsoppsett"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Tillater et program å endre innstillingene for nettverkstilknytningen."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"endre innstilling for bakgrunnsdata"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Lar applikasjonen endre innstillingen for bakgrunnsdata."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"se tilstand for trådløse nettverk"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Lar applikasjonen skrive nye ord til den brukerdefinerte ordlisten."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"redigere/slette innhold på minnekort"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Lar applikasjonen skrive til minnekortet."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"tilgang til bufrede filer"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillater et program å lese og skrive til bufrede filer."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Begrens passord"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begrense typene passord du kan bruke."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Overvåk påloggingsforsøk"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Overvåk mislykkede påloggingsforsøk eller forsøk på handlinger på enheten."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Tilbakestill passordet"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Fremtving tilbakestilling av passord, slik at administratoren må gi deg et nytt passord når du skal logge deg på."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Obligatorisk låsing"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollerer når enheten låses. Du må skrive inn passordet på nytt for å låse den opp."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Slett alle data"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Utfører tilbakestilling til fabrikkstandard. Alle data slettes uten varsel."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hjemmenummer"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Skriv inn PIN-kode:"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Skriv inn passord for å låse opp"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Gal PIN-kode!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"For å låse opp, trykk på menyknappen og deretter 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Trykk på menyknappen for å låse opp."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Tegn mønster for å låse opp"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Nødanrop"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbake til samtale"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Riktig!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager, prøv igjen:"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Lader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,7 +547,7 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Mangler SIM-kort."</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"Ikke noe SIM-kort i telefonen."</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"Sett inn et SIM-kort."</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"Kun nødsamtaler"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"Kun nødanrop"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"Nettverk ikke tillatt"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM-kortet er PUK-låst."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"Se manualen eller kontakt kundeservice."</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"LÃ¥s opp"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Lyd på"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Lyd av"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Fjern"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Lar applikasjonen lese alle adresser nettleseren har besøkt, og alle nettleserens bokmerker."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skrive til nettleserens logg og bokmerker"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Lar applikasjonen endre nettleserens logg og bokmerker lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å fjerne eller redigere nettleserens data."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Endre nettleserens tillatelser for geografisk posisjonering"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Tillater programmet å endre nettleserens tillatelser for geografisk posisjonering. Skadelige programmer kan bruke denne funksjonen til å sende posisjonsopplysninger til vilkårlige nettsteder."</string>
<string name="save_password_message" msgid="767344687139195790">"Ønsker du at nettleseren skal huske dette passordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ikke nå"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Husk"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"for en time siden"</item>
<item quantity="other" msgid="2467273239587587569">"for <xliff:g id="COUNT">%d</xliff:g> timer siden"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Siste <xliff:g id="COUNT">%d</xliff:g> dager"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Forrige måned"</string>
+ <string name="older" msgid="5211975022815554840">"Eldre"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"i går"</item>
<item quantity="other" msgid="2479586466153314633">"for <xliff:g id="COUNT">%d</xliff:g> dager siden"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"uker"</string>
<string name="year" msgid="4001118221013892076">"Ã¥r"</string>
<string name="years" msgid="6881577717993213522">"Ã¥r"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Hverdager (man–fre)"</string>
- <string name="daily" msgid="5738949095624133403">"Hver dag"</string>
- <string name="weekly" msgid="983428358394268344">"Hver <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"En gang i måneden"</string>
- <string name="yearly" msgid="1519577999407493836">"En gang i året"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Kan ikke spille video"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Beklager, denne videoen er ikke gyldig for streaming til denne enheten."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Beklager, kan ikke spille denne videoen."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string>
<string name="report" msgid="4060218260984795706">"Rapportér"</string>
<string name="wait" msgid="7147118217226317732">"Vent"</string>
- <string name="debug" msgid="9103374629678531849">"Debug"</string>
<string name="sendText" msgid="5132506121645618310">"Velg mål for tekst"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ringetonevolum"</string>
<string name="volume_music" msgid="5421651157138628171">"Medievolum"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Trenger ingen rettigheter"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Skjul"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Vis alle"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Laster inn…"</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masselagring"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB koblet til"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Du har koblet telefonen til en datamaskin via USB. Velg «Montér» dersom du ønsker å kopiere filer mellom datmaskinen og minnekortet i telefonen."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Montér"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Ikke montér"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Du har koblet telefonen til datamaskinen via USB. Velg knappen nedenfor hvis du vil kopiere filer mellom datamaskinen og SD-kortet i Android-telefonen."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Slå på USB-lagring"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Det oppsto et problem med å bruke minnekortet ditt for USB-lagring."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB tilkoblet"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Velg om du ønsker å kopiere filer til/fra en datamaskin."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Slå av USB-lagring"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Velg for å slå av USB-lagring."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Slå av USB-lagring"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Før du slår av USB-lagring, sjekk at du har avmontert enheten i USB-verten. Velg «slå av» for å slå av USB-lagring."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Slå av"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Avbryt"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Det har oppstått et problem ved deaktiveringen av USB-lagring. Kontroller at du har demontert USB-verten, og prøv igjen."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lagring er i bruk"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Før du slår av USB-lagring, må du kontrollere at du har løst ut SD-kortet fra Android-telefonen fra datamaskinen."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Slå av USB-lagring"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Det oppstod et problem ved deaktivering av USB-lagring. Kontroller at du har demontert USB-verten, og prøv på nytt."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Slå på USB-lagring"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Hvis du aktiverer USB-lagring, virker ikke lenger enkelte av programmene du bruker, og de kan være utilgjengelige inntil du deaktiverer USB-lagringen."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB-operasjonen mislyktes"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatere minnekort"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Er du sikker på at du ønsker å formatere minnekortet? Alle data på kortet vil gå tapt."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatér"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Fant ingen tilsvarende aktiviteter"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"oppdater statistikk over komponentbruk"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillater endring av innsamlet data om bruk av komponenter. Ikke ment for vanlige applikasjoner."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Tillater bruk av standard meldingsbeholdertjeneste for kopiering av innhold. Brukes ikke for normale programmer."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Tillater bruk av standard meldingsbeholdertjeneste for kopiering av innhold. Brukes ikke for normale programmer."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Trykk to ganger for zoomkontroll"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Feil under oppakking av gadget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"GÃ¥"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Lag kontakt"\n"med nummeret <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"valgt"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"ikke valgt"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Det nevnte programmet ber om tilgangstillatelse til påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%1$s</xliff:g> fra <xliff:g id="APPLICATION">%2$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Det nevnte programmet ber om tilgangstillatelse til <xliff:g id="TYPE">%1$s</xliff:g>-påloggingsopplysningene for konto <xliff:g id="ACCOUNT">%2$s</xliff:g> fra <xliff:g id="APPLICATION">%3$s</xliff:g>. Vil du gi denne tillatelsen? I så fall vil svaret ditt bli lagret, og du vil ikke bli spurt flere ganger."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Ett eller flere av de følgende programmene ber om tillatelse til å få tilgang til kontoen din fra nå av."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Vil du tillate dette?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Få tilgang til forespørsler"</string>
<string name="allow" msgid="7225948811296386551">"Tillat"</string>
<string name="deny" msgid="2081879885755434506">"Avslå"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Tillatelse forespurt"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Lag 2-tunneleringsprotokoll"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Passordbasert L2TP/IPSec-VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sertifikatbasert L2TP/IPSec-VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
+ <string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
+ <string name="submit" msgid="1602335572089911941">"Send inn"</string>
+ <string name="description_star" msgid="2654319874908576133">"favoritt"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Bilmodus er aktivert"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Velg for å avslutte bilmodus"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Tilknytning er aktiv"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Trykk for å konfigurere"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Høy mobildatabruk"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Berør for å lese mer om bruk av mobildata"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Grensen for mobildatabruk er overskredet"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Berør for å lese mer om bruk av mobildata"</string>
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 8046f1f..736855f 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Beperkte toegang gewijzigd"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Gegevensservice is geblokkeerd."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Alarmservice is geblokkeerd."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Spraak-/SMS-service is geblokkeerd."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Alle spraak-/SMS-services zijn geblokkeerd."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Spraakservice is geblokkeerd."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Alle spraakservices zijn geblokkeerd."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMS-service is geblokkeerd."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Spraak-/gegevensservices zijn geblokkeerd."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Spraak-/SMS-services zijn geblokkeerd."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Alle spraak-/gegevens-/SMS-services zijn geblokkeerd."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Spraak"</string>
<string name="serviceClassData" msgid="872456782077937893">"Gegevens"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Uitschakelen"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Uitschakelen..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Uw telefoon wordt uitgeschakeld."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Recent"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Geen recente toepassingen."</string>
<string name="global_actions" msgid="2406416831541615258">"Telefoonopties"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Schermvergrendeling"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Hiermee kan een toepassing de foutopsporing voor een andere toepassing inschakelen. Schadelijke toepassingen kunnen dit gebruiken om andere toepassingen af te sluiten."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"uw UI-instellingen wijzigen"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Hiermee kan een toepassing de huidige configuratie, zoals de landinstelling of de algemene lettergrootte, wijzigen."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"andere toepassingen opnieuw starten"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Hiermee kan een toepassing andere toepassingen opnieuw starten."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"automodus inschakelen"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Staat een toepassing toe de automodus in te schakelen."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"processen op de achtergrond beëindigen"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Staat een toepassing toe processen op de achtergrond te beëindigen, zelfs als er voldoende geheugen beschikbaar is."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"andere toepassingen gedwongen stoppen"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Staat een toepassing toe andere toepassingen te stoppen."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"toepassing nu sluiten"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Hiermee kan een toepassing elke willekeurige activiteit die op de voorgrond wordt uitgevoerd, sluiten en naar de achtergrond verplaatsen. Nooit vereist voor normale toepassingen."</string>
<string name="permlab_dump" msgid="1681799862438954752">"interne systeemstatus ophalen"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Hiermee kunnen verzamelde accustatistieken worden gewijzigd. Niet voor gebruik door normale toepassingen."</string>
<string name="permlab_backup" msgid="470013022865453920">"systeemback-up en -herstel beheren"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Hiermee kan de toepassing het mechanisme voor systeemback-up en -herstel beheren. Niet voor gebruik door normale toepassingen."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"back-up maken en de gegevens van de toepassing herstellen"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Hiermee kan de toepassing deelnemen aan het beheer van het mechanisme voor systeemback-up en -herstel."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"niet-geautoriseerde vensters weergeven"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Hiermee kunnen vensters worden gemaakt die door de interne systeemgebruikersinterface worden gebruikt. Niet voor gebruik door normale toepassingen."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"waarschuwingen op systeemniveau weergeven"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Hiermee staat u de houder toe zich te verbinden met de hoofdinterface van een invoermethode. Nooit vereist voor normale toepassingen."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"verbinden met een achtergrond"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Hiermee staat u de houder toe zich te verbinden met de hoofdinterface van een achtergrond. Nooit vereist voor normale toepassingen."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interactie met apparaatbeheer"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Staat de houder toe intenties te verzenden naar een apparaatbeheerder. Nooit vereist voor normale toepassingen."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"schermstand wijzigen"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Hiermee kan een toepassing op elk gewenst moment de oriëntatie van het scherm wijzigen. Nooit vereist voor normale toepassingen."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"Linux-signalen verzenden naar toepassingen"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Hiermee kan een toepassing nieuwe of bijgewerkte Android-pakketten installeren. Schadelijke toepassingen kunnen hiervan gebruik maken om nieuwe toepassingen met willekeurig krachtige machtigingen toe te voegen."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"alle cachegegevens van toepassing verwijderen"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Hiermee kan een toepassing opslagruimte op de telefoon vrij maken door bestanden te verwijderen uit de cachemap van de toepassing. De toegang is doorgaans beperkt tot het systeemproces."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Toepassingsbronnen verplaatsen"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Een toepassing toestaan toepassingsbronnen te verplaatsen van interne naar externe media en omgekeerd."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"systeemlogbestanden lezen"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Hiermee kan een toepassing de verschillende logbestanden van het systeem lezen. De toepassing kan op deze manier algemene informatie achterhalen over uw telefoongebruik. Hierin is geen persoonlijke of privé-informatie opgenomen."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"lezen/schrijven naar bronnen van diag"</string>
@@ -273,14 +289,14 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar wijzigen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar verwijderen of wijzigen."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"gegevens eigenaar lezen"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar lezen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar lezen."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"agendagegevens lezen"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"agendagebeurtenissen lezen"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Hiermee kan een toepassing alle agendagebeurtenissen lezen die zijn opgeslagen op uw telefoon. Schadelijke toepassingen kunnen hiervan gebruik maken om uw agendagebeurtenissen te verzenden naar andere personen."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"agendagegevens schrijven"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Hiermee kan een toepassing de op uw telefoon opgeslagen agendagebeurtenissen wijzigen. Schadelijke toepassingen kunnen hiermee uw agendagegevens verwijderen of wijzigen."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"agendagebeurtenissen toevoegen of aanpassen en e-mail verzenden naar gasten"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Een toepassing toestaan gebeurtenissen aan uw agenda toe te voegen of te wijzigen, wat inhoudt dat er e-mails kunnen worden verzonden naar gasten. Schadelijke toepassingen kunnen dit gebruiken om uw agendagebeurtenissen te wissen of aan te passen of om e-mail naar gasten te verzenden."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"neplocatiebronnen voor test"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Neplocatiebronnen voor testdoeleinden maken. Schadelijke toepassingen kunnen dit gebruiken om de locatie en/of status te overschrijven die door de echte locatiebronnen wordt aangegeven, zoals GPS of netwerkaanbieders."</string>
- <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"toegang opdrachten aanbieder extra locaties"</string>
- <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"Toegang tot opdrachten aanbieder extra locaties. Schadelijke toepassingen kunnen hiervan gebruik maken om de werking van GPS of andere locatiebronnen te verstoren."</string>
+ <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"toegang tot extra opdrachten van locatieaanbieder"</string>
+ <string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"Toegang tot extra opdrachten van locatieaanbieder. Schadelijke toepassingen kunnen hiervan gebruik maken om de werking van GPS of andere locatiebronnen te verstoren."</string>
<string name="permlab_installLocationProvider" msgid="6578101199825193873">"toestemming om een locatieprovider te installeren"</string>
<string name="permdesc_installLocationProvider" msgid="5449175116732002106">"Neplocatiebronnen voor testdoeleinden maken. Schadelijke toepassingen kunnen dit gebruiken om de locatie en/of status te overschrijven die door de echte locatiebronnen, zoals GPS of netwerkaanbieders, wordt aangegeven of om uw locatie bij te houden en te rapporteren aan externe bronnen."</string>
<string name="permlab_accessFineLocation" msgid="8116127007541369477">"nauwkeurige (GPS) locatie"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Hiermee kan de toepassing bestandssystemen koppelen en ontkoppelen voor verwisselbare opslagruimte."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"externe opslag formatteren"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Hiermee kan de toepassing de externe opslag formatteren."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"informatie over de beveiligde opslag verkrijgen"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Staat de toepassing toe informatie over de beveiligde opslag te verkrijgen."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"beveiligde opslag maken"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Staat de toepassing toe beveiligde opslag te maken."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"beveiligde opslag vernietigen"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Staat de toepassing toe de beveiligde opslag te vernietigen."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"beveiligde opslag koppelen/ontkoppelen"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Staat de toepassing toe de beveiligde opslag te koppelen/ontkoppelen."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"naam van beveiligde opslag wijzigen"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Staat de toepassing toe de naam van de beveiligde opslag te wijzigen."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"trilstand beheren"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Hiermee kan de toepassing de trilstand beheren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"zaklamp bedienen"</string>
@@ -316,7 +342,7 @@
<string name="permlab_callPrivileged" msgid="4198349211108497879">"alle telefoonnummers rechtstreeks bellen"</string>
<string name="permdesc_callPrivileged" msgid="244405067160028452">"Hiermee kan een toepassing elk telefoonnummer, inclusief alarmnummers, bellen zonder uw tussenkomst. Schadelijke toepassingen kunnen onnodige en illegale oproepen uitvoeren naar alarmdiensten."</string>
<string name="permlab_performCdmaProvisioning" msgid="5604848095315421425">"meteen starten met CDMA-telefooninstelling"</string>
- <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Hiermee kan de toepassing starten met CDMA-levering. Schadelijke toepassingen kunnen de CDMA-levering onnodig starten"</string>
+ <string name="permdesc_performCdmaProvisioning" msgid="6457447676108355905">"Hiermee kan de toepassing starten met CDMA-provisioning. Schadelijke applicaties kunnen de CDMA-provisioning onnodig starten"</string>
<string name="permlab_locationUpdates" msgid="7785408253364335740">"meldingen over locatie-updates beheren"</string>
<string name="permdesc_locationUpdates" msgid="2300018303720930256">"Hiermee kunnen updatemeldingen voor locaties van de radio worden ingeschakeld/uitgeschakeld. Niet voor gebruik door normale toepassingen."</string>
<string name="permlab_checkinProperties" msgid="7855259461268734914">"toegang tot checkin-eigenschappen"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Hiermee kan de toepassing de grootte van de achtergrond instellen."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"systeem terugzetten op fabrieksinstellingen"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Hiermee kan een toepassing het systeem terugzetten op de fabrieksinstellingen, waarbij alle gegevens, configuraties en geïnstalleerde toepassingen worden verwijderd."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"tijd instellen"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Staat een toepassing toe de kloktijd van de telefoon te wijzigen."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"tijdzone instellen"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Hiermee kan een toepassing de tijdzone van de telefoon wijzigen."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"fungeren als de AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"instellingen voor toegangspuntnaam schrijven"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Hiermee kan een toepassing de APN-instellingen, zoals proxy en poort, van elke APN wijzigen."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"netwerkverbinding wijzigen"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Hiermee kan een toepassing de verbindingsstatus van het netwerk wijzigen."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Staat een toepassing toe de status van de netwerkverbinding te wijzigen."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Getetherde verbinding wijzigen"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Staat een toepassing toe de status van de getetherde netwerkverbinding te wijzigen."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"instelling voor gebruik van achtergrondgegevens van gegevens wijzigen"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Hiermee kan een toepassing de instelling voor gebruik van achtergrondgegevens wijzigen."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Wi-Fi-status bekijken"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Hiermee kan een toepassing nieuwe woorden schrijven naar het gebruikerswoordenboek."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"inhoud op de SD-kaart aanpassen/verwijderen"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Hiermee kan een toepassing schrijven naar de SD-kaart."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"het cachebestandssysteem openen"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Staat een toepassing toe het cachebestandssysteem te lezen en te schrijven."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Wachtwoord beperken"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"De typen wachtwoorden beperken die u mag gebruiken."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Aanmeldingspogingen controleren"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Mislukte pogingen controleren voor aanmelding bij het apparaat om een bepaalde actie uit te voeren."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Wachtwoord opnieuw instellen"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Uw wachtwoord gedwongen wijzigen in een nieuwe waarde, wat vereist dat de beheerder u het wachtwoord geeft voordat u zich kunt aanmelden."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Gedwongen vergrendelen"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Beheren wanneer het apparaat wordt vergrendeld, wat vereist dat u het wachtwoord opnieuw invoert."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Alle gegevens wissen"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"De fabrieksinstellingen herstellen, waarbij alle gegevens worden verwijderd zonder bevestiging."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Thuis"</item>
<item msgid="869923650527136615">"Mobiel"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-code invoeren"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Voer het wachtwoord in om te ontgrendelen"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Onjuiste PIN-code!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Alarmnummer"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Druk op \'Menu\' om te ontgrendelen."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Patroon tekenen om te ontgrendelen"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Noodoproep"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Terug naar gesprek"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Juist!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Probeer het opnieuw"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Opladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Ontgrendelen"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Geluid aan"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Geluid uit"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Hiermee kan een toepassing de URL\'s lezen die u via de browser heeft bezocht, evenals alle bladwijzers van de browser."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"browsergeschiedenis en bladwijzers schrijven"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Hiermee kan een toepassing de op uw telefoon opgeslagen browsergeschiedenis of bladwijzers wijzigen. Schadelijke toepassingen kunnen hiermee uw browsergegevens verwijderen of wijzigen."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Geolocatierechten voor browser aanpassen"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Staat een toepassing toe de geolocatierechten van de browser aan te passen. Schadelijke toepassingen kunnen dit gebruiken om locatiegegevens te verzenden naar willekeurige websites."</string>
<string name="save_password_message" msgid="767344687139195790">"Wilt u dat de browser dit wachtwoord onthoudt?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Niet nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Onthouden"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 uur geleden"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> uur geleden"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Afgelopen <xliff:g id="COUNT">%d</xliff:g> dagen"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Afgelopen maand"</string>
+ <string name="older" msgid="5211975022815554840">"Ouder"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"gisteren"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> dagen geleden"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"weken"</string>
<string name="year" msgid="4001118221013892076">"jaar"</string>
<string name="years" msgid="6881577717993213522">"jaren"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Elke weekdag (ma-vr)"</string>
- <string name="daily" msgid="5738949095624133403">"Dagelijks"</string>
- <string name="weekly" msgid="983428358394268344">"Wekelijks op <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Maandelijks"</string>
- <string name="yearly" msgid="1519577999407493836">"Jaarlijks"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Video kan niet worden afgespeeld"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Deze video kan helaas niet worden gestreamd naar dit apparaat."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Deze video kan niet worden afgespeeld."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string>
<string name="report" msgid="4060218260984795706">"Rapport"</string>
<string name="wait" msgid="7147118217226317732">"Wachten"</string>
- <string name="debug" msgid="9103374629678531849">"Foutopsporing"</string>
<string name="sendText" msgid="5132506121645618310">"Selecteer een actie voor tekst"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Belvolume"</string>
<string name="volume_music" msgid="5421651157138628171">"Mediavolume"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Geen machtigingen vereist"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Verbergen"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Alles weergeven"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Laden..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-massaopslag"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB-verbinding"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer \'Koppelen\' als u bestanden tussen uw computer en de SD-kaart van uw telefoon wilt kopiëren."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Koppelen"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Niet koppelen"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"U heeft uw telefoon via USB op uw computer aangesloten. Selecteer de onderstaande knop als u bestanden tussen uw computer en de SD-kaart van uw Android wilt kopiëren."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB-opslag inschakelen"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Er is een probleem bij het gebruik van uw SD-kaart voor USB-opslag."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-verbinding"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Selecteer dit om bestanden naar/van uw computer te kopiëren."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB-opslag uitschakelen"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Selecteer dit om USB-opslag uit te schakelen."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"USB-opslag uitschakelen"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Voordat u de USB-opslag uitschakelt, moet u de koppeling met de USB-host verbreken. Selecteer \'Uitschakelen\' om USB-opslag uit te schakelen."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Uitschakelen"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Annuleren"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Er is een probleem opgetreden tijdens het uitschakelen van de USB-opslag. Controleer of u de USB-host heeft losgekoppeld en probeer het opnieuw."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-opslag in gebruik"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Voordat u USB-opslag uitschakelt, moet u de SD-kaart van uw Android hebben ontkoppeld (\'uitgeworpen\') van uw computer."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB-opslag uitschakelen"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Er is een probleem opgetreden tijdens het uitschakelen van de USB-opslag. Controleer of u de USB-host heeft losgekoppeld en probeer het opnieuw."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB-opslag inschakelen"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Als u USB-opslag inschakelt, worden bepaalde toepassingen die u gebruikt, gestopt en worden deze mogelijk pas weer beschikbaar wanneer u USB-opslag uitschakelt."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB-bewerking mislukt"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"SD-kaart formatteren"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Weet u zeker dat u de SD-kaart wilt formatteren? Alle gegevens op uw kaart gaan dan verloren."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatteren"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Geen overeenkomende activiteiten gevonden"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"gebruiksstatistieken van component bijwerken"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Hiermee kunnen verzamelde gebruiksstatistieken van een component worden gewijzigd. Niet voor gebruik door normale toepassingen."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Staat het aanroepen van de standaardcontainerservice toe om inhoud te kopiëren. Niet voor gebruik door normale toepassingen."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Staat het aanroepen van de standaardcontainerservice toe om inhoud te kopiëren. Niet voor gebruik door normale toepassingen."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tik twee keer voor zoomregeling"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Fout bij uitbreiden van widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Ga"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Contact maken"\n"met <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"aangevinkt"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niet aangevinkt"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor account \'<xliff:g id="ACCOUNT">%1$s</xliff:g>\' van <xliff:g id="APPLICATION">%2$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"De weergegeven toepassingen vragen toestemming voor toegang tot de aanmeldingsgegevens voor <xliff:g id="TYPE">%1$s</xliff:g> van het account \'<xliff:g id="ACCOUNT">%2$s</xliff:g>\' van <xliff:g id="APPLICATION">%3$s</xliff:g>. Wilt u deze toestemming verlenen? Als u dit wilt doen, wordt uw antwoord onthouden en wordt dit niet opnieuw gevraagd."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"De volgende toepassingen vragen toegang tot uw account, nu en in de toekomst."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Wilt u dit verzoek toestaan?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Toegangsverzoek"</string>
<string name="allow" msgid="7225948811296386551">"Toestaan"</string>
<string name="deny" msgid="2081879885755434506">"Weigeren"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Toestemming gevraagd"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol (L2TP)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Vooraf gedeelde sleutel op basis van L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificaat op basis van L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
+ <string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
+ <string name="submit" msgid="1602335572089911941">"Verzenden"</string>
+ <string name="description_star" msgid="2654319874908576133">"favoriet"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus ingeschakeld"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecteer dit om de automodus af te sluiten."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Tethering actief"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Aanraken om te configureren"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hoog mobiel gegevensgebruik"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Mobiele gegevenslimiet overschreden"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string>
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 0cf137e..597bd69 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Zmieniono ograniczenie dostępu"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Usługa transmisji danych jest zablokowana."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Usługa połączeń alarmowych jest zablokowana."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Usługa głosowa/SMS jest zablokowana."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Wszystkie usługi głosowe/SMS są zablokowane."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Usługa głosowa jest zablokowana."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Wszystkie usługi głosowe są zablokowane."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Usługa SMS jest zablokowana."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Usługi głosowe/danych są zablokowane."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Usługi głosowe/SMS są zablokowane."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Wszystkie usługi głosowe/danych/SMS są zablokowane."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"GÅ‚os"</string>
<string name="serviceClassData" msgid="872456782077937893">"Dane"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAKS"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Wyłącz"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Wyłączanie..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Telefon zostanie wyłączony"</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Najnowsze"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Brak ostatnio używanych aplikacji."</string>
<string name="global_actions" msgid="2406416831541615258">"Opcje telefonu"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Blokada ekranu"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Pozwala aplikacji na włączenie debugowania innej aplikacji. Szkodliwe aplikacje mogą to wykorzystać do wyłączenia innych programów."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"zmienianie ustawień interfejsu użytkownika"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Pozwala aplikacji zmieniać bieżącą konfigurację, na przykład lokalny lub globalny rozmiar czcionki."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"resetowanie innych aplikacji"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Pozwala aplikacji na wymuszenie ponownego uruchomienia innych aplikacji."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"włączanie trybu samochodowego"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Zezwala aplikacji na włączanie trybu samochodowego."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"kończenie procesów w tle"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Zezwala aplikacji na kończenie procesów innych aplikacji w tle, nawet jeśli jest dużo pamięci."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"wymuszanie zatrzymywania innych aplikacji"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Zezwala aplikacji na wymuszanie zatrzymania innych aplikacji."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"wymuszanie zamknięcia aplikacji"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Pozwala aplikacji na wymuszenie zamknięcia i cofnięcia dowolnej operacji działającej na pierwszym planie. Nigdy nie powinno być potrzebne normalnym aplikacjom."</string>
<string name="permlab_dump" msgid="1681799862438954752">"pobieranie informacji o wewnętrznym stanie systemu"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Pozwala na zmianÄ™ zebranych statystyk dotyczÄ…cych baterii. Nie do wykorzystania przez normalne aplikacje."</string>
<string name="permlab_backup" msgid="470013022865453920">"kontrolowanie tworzenia i przywracania kopii zapasowych systemu"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Zezwala aplikacji na kontrolowanie mechanizmu tworzenia i przywracania kopii zapasowych systemu. Opcja nie jest przeznaczona dla zwykłych aplikacji."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"Tworzenie i przywracanie kopii zapasowej danych aplikacji"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Zezwala aplikacji na działanie w ramach mechanizmu tworzenia i przywracania kopii zapasowych systemu."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"wyświetlanie nieuwierzytelnionych okien"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Pozwala na tworzenie okien, które przeznaczone są do wykorzystania przez wewnętrzny interfejs użytkownika systemu. Nie do wykorzystania przez normalne aplikacje."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"wyświetlanie ostrzeżeń systemowych"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Pozwala na tworzenie powiązania z interfejsem najwyższego poziomu metody wejściowej. To uprawnienie nie powinno być nigdy wymagane przez zwykłe aplikacje."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"powiÄ…zanie z tapetÄ…"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Umożliwia posiadaczowi powiązać interfejs najwyższego poziomu dla tapety. Nie powinno być nigdy potrzebne w przypadku zwykłych aplikacji."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interakcja z administratorem urzÄ…dzenia"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Zezwala posiadaczowi na wysyłanie informacji o zamiarach do administratora urządzenia. Opcja nie powinna być nigdy potrzebna w przypadku zwykłych aplikacji."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"zmienianie orientacji ekranu"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Pozwala aplikacji na zmianę orientacji ekranu w dowolnym momencie. Nigdy nie powinno być potrzeby stosowania w normalnych aplikacjach."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"wysyłanie sygnałów systemu Linux do aplikacji"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Pozwala aplikacji na instalowanie nowych lub zaktualizowanych pakietów systemu Android. Szkodliwe aplikacje mogą to wykorzystać, aby dodać nowe aplikacje z arbitralnie nadanymi rozległymi uprawnieniami."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"usuwanie wszystkich danych aplikacji z pamięci podręcznej"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Pozwala aplikacji na zwalnianie pamięci telefonu przez usuwanie plików z katalogu pamięci podręcznej aplikacji. Dostęp jest bardzo ograniczony, z reguły do procesów systemu."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Przenoszenie zasobów aplikacji"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Zezwala aplikacji na przeniesienie zasobów aplikacji z nośnika wewnętrznego na zewnętrzny i odwrotnie."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"czytanie plików dziennika systemu"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Umożliwia aplikacji czytanie różnych plików dziennika systemowego. Pozwala to na uzyskanie ogólnych informacji o czynnościach wykonywanych w telefonie, ale bez ujawniania danych osobowych lub osobistych informacji."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"czytanie/zapisywanie w zasobach należących do diagnostyki"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Pozwala aplikacji na zmianę danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wymazać lub zmienić dane właściciela."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"czytanie danych właściciela"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Pozwala aplikacji na czytanie danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do odczytania danych właściciela."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"czytanie danych kalendarza"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"odczytywanie wydarzeń w kalendarzu"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Pozwala aplikacji na odczytywanie wszystkich wydarzeń z kalendarza, zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do rozsyłania wydarzeń z kalendarza do innych ludzi."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"zapisywanie danych kalendarza"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Pozwala aplikacji na zmianę wydarzeń z kalendarza zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane w kalendarzu."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"dodawanie i modyfikowanie wydarzeń w kalendarzu oraz wysyłanie wiadomości e-mail do gości"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Zezwala aplikacji na dodawanie i zmianę wydarzeń w kalendarzu, co może powodować wysyłanie wiadomości e-mail do gości. Złośliwe aplikacje mogą używać tego uprawnienia do usuwania i modyfikowania wydarzeń w kalendarzu oraz do wysyłania wiadomości e-mail do gości."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"udawanie źródeł położenia dla testów"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Tworzenie pozorowanych źródeł ustalania położenia dla testów. Szkodliwe aplikacje mogą to wykorzystać, aby zastąpić prawdziwe położenie i/lub stan zwracany przez prawdziwe źródła, takie jak GPS lub dostawcy usługi sieciowej."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"dostęp do dodatkowych poleceń dostawcy informacji o lokalizacji"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Pozwala aplikacjom na podłączanie i odłączanie systemów plików w pamięciach przenośnych."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatowanie pamięci zewnętrznej"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Zezwala aplikacji na formatowanie wymiennych nośników."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"uzyskiwanie informacji o bezpiecznym magazynie"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Zezwala aplikacji na uzyskiwanie informacji o bezpiecznym magazynie."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"tworzenie bezpiecznego magazynu"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Zezwala aplikacji na tworzenie bezpiecznego magazynu."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"usuwanie bezpiecznego magazynu"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Zezwala aplikacji na usuwanie bezpiecznego magazynu."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"podłączanie/odłączanie bezpiecznego magazynu"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Zezwala aplikacji na podłączanie/odłączanie bezpiecznego magazynu."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"zmiana nazwy bezpiecznego magazynu"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Zezwala aplikacji na zmianÄ™ nazwy bezpiecznego magazynu."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"kontrolowanie wibracji"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Pozwala aplikacjom na kontrolowanie wibracji."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontrolowanie latarki"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Pozwala aplikacji na ustawianie wskazówek dotyczących rozmiaru tapety."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"resetowanie systemu do ustawień fabrycznych"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Pozwala aplikacji na całkowite zresetowanie systemu do ustawień fabrycznych, z wymazaniem wszystkich danych, konfiguracji oraz zainstalowanych aplikacji."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"ustawianie godziny"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Zezwala aplikacji na zmianÄ™ ustawienia zegara w telefonie."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"ustawianie strefy czasowej"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Pozwala aplikacji na zmianÄ™ strefy czasowej w telefonie."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"działanie jako usługa AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"zapisywanie ustawień nazwy punktu dostępowego (APN, Access Point Name)"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Pozwala aplikacji na zmianę ustawień APN, takich jak serwer proxy oraz port dowolnego APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"zmienianie połączeń sieci"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Pozwala aplikacji na zmianę stanu połączeń sieciowych."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Zezwala aplikacji na zmianę stanu łączności sieciowej."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Zmiana łączności powiązanej"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Zezwala aplikacji na zmianę stanu powiązanej łączności sieciowej."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"zmienianie ustawienia używania danych w tle"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Zezwala aplikacji na zmianę ustawień użycia danych w tle."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"wyświetlanie stanu Wi-Fi"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Zezwala aplikacjom na zapisywanie nowych słów w słowniku użytkownika."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"modyfikowanie/usuwanie zawartości karty SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Umożliwia aplikacji zapis na karcie SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"dostęp do systemu plików pamięci podręcznej"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Zezwala aplikacji na odczyt i zapis w systemie plików pamięci podręcznej."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Ogranicz liczbę prób wprowadzenia hasła"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Ogranicza dozwolone typy haseł użytkownika."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Monitoruj próby logowania"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitoruje nieudane próby zalogowania do urządzenia w celu wykonania określonego działania."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Resetuj hasło"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Wymusza podanie nowej wartości hasła, wymagając przekazania go użytkownikowi przez administratora przed zalogowaniem."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"WymuÅ› zablokowanie"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontroluje, kiedy urządzenie jest blokowane, wymagając ponownego wprowadzenia hasła."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Usuń wszystkie dane"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Wykonuje reset fabryczny, usuwając wszystkie dane użytkownika bez żadnego potwierdzenia."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Dom"</item>
<item msgid="869923650527136615">"Komórka"</item>
@@ -483,8 +525,9 @@
<string name="orgTypeOther" msgid="3951781131570124082">"Inny"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Niestandardowy"</string>
<string name="contact_status_update_attribution" msgid="5112589886094402795">"przez <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> za pośrednictwem: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
+ <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> przez <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Wprowadź kod PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Wprowadź hasło, aby odblokować"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Błędny kod PIN!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Aby odblokować, naciśnij Menu, a następnie 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numer alarmowy"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Naciśnij Menu, aby odblokować."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Narysuj wzór, aby odblokować"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Połączenie alarmowe"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Powrót do połączenia"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Poprawnie!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Niestety, spróbuj ponownie"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Åadowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Odblokuj"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Włącz dźwięk"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Wyłącz dźwięk"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wyczyść"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Umożliwia aplikacji odczyt wszystkich adresów URL odwiedzonych przez przeglądarkę, a także wszystkich zakładek przeglądarki."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"zapis historii i zakładek przeglądarki"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Umożliwia aplikacji modyfikowanie historii lub zakładek przeglądarki zapisanych w telefonie. Złośliwe aplikacje mogą używać tej opcji do usuwania lub modyfikowania danych przeglądarki."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Zezwala aplikacji na modyfikowanie uprawnień przeglądarki dotyczących lokalizacji geograficznej. Złośliwe aplikacje mogą używać tej opcji do wysyłania informacji o lokalizacji do dowolnych witryn internetowych."</string>
<string name="save_password_message" msgid="767344687139195790">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Nie teraz"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Zapamiętaj"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"godzinÄ™ temu"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> godzin temu"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Ostatnie dni (<xliff:g id="COUNT">%d</xliff:g>)"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Ostatni miesiÄ…c"</string>
+ <string name="older" msgid="5211975022815554840">"Starsze"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"wczoraj"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> dni temu"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"tygodni"</string>
<string name="year" msgid="4001118221013892076">"rok"</string>
<string name="years" msgid="6881577717993213522">"lat"</string>
- <string name="every_weekday" msgid="8777593878457748503">"W każdy dzień roboczy (pon–pt)"</string>
- <string name="daily" msgid="5738949095624133403">"Codziennie"</string>
- <string name="weekly" msgid="983428358394268344">"Co tydzień w <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Miesięcznie"</string>
- <string name="yearly" msgid="1519577999407493836">"Co roku"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Nie można odtworzyć filmu wideo"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Przepraszamy, ten film wideo nie nadaje się do przesyłania strumieniowego do tego urządzenia."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Niestety, nie można odtworzyć tego filmu wideo."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Wymuś zamknięcie"</string>
<string name="report" msgid="4060218260984795706">"Zgłoś"</string>
<string name="wait" msgid="7147118217226317732">"Czekaj"</string>
- <string name="debug" msgid="9103374629678531849">"Debuguj"</string>
<string name="sendText" msgid="5132506121645618310">"Jak wysłać tekst?"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Głośność dzwonka"</string>
<string name="volume_music" msgid="5421651157138628171">"Głośność multimediów"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Nie są wymagane żadne uprawnienia"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ukryj"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Pokaż wszystko"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Åadowanie..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Pamięć masowa USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Połączenie przez USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Telefon zostaÅ‚ podÅ‚Ä…czony do komputera przez port USB. Wybierz polecenie „PodÅ‚Ä…czâ€, jeÅ›li chcesz skopiować pliki miÄ™dzy komputerem a kartÄ… SD w telefonie."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Podłącz"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Nie podłączaj"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Telefon został połączony z komputerem za pośrednictwem USB. Wybierz poniższy przycisk, aby skopiować pliki między komputerem a kartą SD systemu Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Włącz nośnik USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Wystąpił problem z wykorzystaniem karty SD dla pamięci USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Połączenie przez USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Wybierz, aby skopiować pliki do/z komputera"</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Wyłącz nośnik USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Wybierz, aby wyłączyć nośnik USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Wyłącz nośnik USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Przed wyÅ‚Ä…czeniem noÅ›nika USB upewnij siÄ™, że odÅ‚Ä…czono go od hosta USB. Wybierz „WyÅ‚Ä…czâ€, aby wyÅ‚Ä…czyć noÅ›nik USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Wyłącz"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Anuluj"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Napotkano problem podczas wyłączania nośnika USB. Sprawdź, czy host USB został odłączony, a następnie spróbuj ponownie."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Nośnik USB w użyciu"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Przed wyÅ‚Ä…czeniem noÅ›nika USB upewnij siÄ™, że karta SD systemu Android zostaÅ‚a odÅ‚Ä…czona („wyjÄ™taâ€) od komputera."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Wyłącz nośnik USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Wystąpił problem podczas wyłączania nośnika USB. Upewnij się, że host USB został odłączony, a następnie spróbuj ponownie."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Włącz nośnik USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Po włączeniu nośnika USB niektóre używane aplikacje zostaną zatrzymane i mogą być niedostępne do chwili wyłączenia nośnika USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Operacja USB nie powiodła się"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatuj kartÄ™ SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Czy na pewno sformatować kartę SD? Wszystkie dane na karcie zostaną utracone."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatuj"</string>
@@ -769,9 +819,11 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Nie znaleziono pasujących działań"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"aktualizowanie statystyk użycia komponentu"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Zezwala na modyfikacje zebranych statystyk użycia komponentu. Nieprzeznaczone dla zwykłych aplikacji."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Zezwala na wywoływanie domyślnej usługi kontenera w celu skopiowania zawartości. Opcja nie jest przeznaczona dla zwykłych aplikacji."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Zezwala na wywoływanie domyślnej usługi kontenera w celu skopiowania zawartości. Opcja nie jest przeznaczona dla zwykłych aplikacji."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Dotknij dwukrotnie, aby sterować powiększeniem"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Błąd podczas wyodrębniania widżetu"</string>
- <string name="ime_action_go" msgid="8320845651737369027">"Przejdź"</string>
+ <string name="ime_action_go" msgid="8320845651737369027">"OK"</string>
<string name="ime_action_search" msgid="658110271822807811">"Szukaj"</string>
<string name="ime_action_send" msgid="2316166556349314424">"Wyślij"</string>
<string name="ime_action_next" msgid="3138843904009813834">"Dalej"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Utwórz kontakt"\n"dla numeru <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"zaznaczone"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"niezaznaczone"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dla konta <xliff:g id="ACCOUNT">%1$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%2$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Wymienione aplikacje żądają pozwolenia na dostęp do danych logowania dotyczących funkcji <xliff:g id="TYPE">%1$s</xliff:g> dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g> powiązanego z aplikacją <xliff:g id="APPLICATION">%3$s</xliff:g>. Czy chcesz udzielić takiego pozwolenia? Jeśli tak, odpowiedź zostanie zapamiętana i to pytanie nie będzie już wyświetlane."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Co najmniej jedna z następujących aplikacji żąda uprawnień dostępu do Twojego konta – teraz i w przyszłości."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Chcesz zezwolić na to żądanie?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Żądanie dostępu"</string>
<string name="allow" msgid="7225948811296386551">"Zezwól"</string>
<string name="deny" msgid="2081879885755434506">"Odmów"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Żądane pozwolenie"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protokół L2TP"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Sieć VPN L2TP/IPSec z kluczem PSK"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sieć VPN L2TP/IPSec z certyfikatem"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
+ <string name="reset" msgid="2448168080964209908">"Resetuj"</string>
+ <string name="submit" msgid="1602335572089911941">"Prześlij"</string>
+ <string name="description_star" msgid="2654319874908576133">"ulubione"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Tryb samochodowy włączony"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Wybierz, aby zakończyć tryb samochodowy."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"PowiÄ…zanie aktywne"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Dotknij, aby skonfigurować"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Wysoki poziom użycia danych komórkowych"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotknij, aby uzyskać więcej informacji na temat używania danych komórkowych"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Przekroczono limit danych komórkowych"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Dotknij, aby uzyskać więcej informacji na temat używania danych komórkowych"</string>
</resources>
diff --git a/core/res/res/values-port-mdpi/donottranslate.xml b/core/res/res/values-port-mdpi/donottranslate.xml
new file mode 100644
index 0000000..b4581fe
--- /dev/null
+++ b/core/res/res/values-port-mdpi/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- @hide DO NOT TRANSLATE. There isn't enough room on mdpi devices, allow height to vary -->
+ <string name="lock_pattern_view_aspect">lock_width</string>
+</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 3e3d2e5..8302dd2 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Acesso restrito modificado"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"O serviço de dados está bloqueado."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"O serviço de emergência está bloqueado."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"O serviço de voz/SMS está bloqueado."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Todos os serviços de voz/SMS estão bloqueados."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"O serviço de voz está bloqueado."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Todos os serviços de Voz estão bloqueados."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"O serviço de SMS está bloqueado."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Os serviços de Voz/Dados estão bloqueados."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Os serviços de Voz/SMS estão bloqueados."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Todos os serviços de Voz/Dados/SMS estão bloqueados."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voz"</string>
<string name="serviceClassData" msgid="872456782077937893">"Dados"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Desligar"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"A encerrar..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"O seu telefone irá encerrar."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Recente"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Nenhuma aplicação recente."</string>
<string name="global_actions" msgid="2406416831541615258">"Opções do telefone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloqueio de ecrã"</string>
@@ -151,7 +161,7 @@
<string name="permgroupdesc_accounts" msgid="4948732641827091312">"Aceda às contas disponíveis."</string>
<string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"Controlos de hardware"</string>
<string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"Aceda directamente ao hardware no telefone."</string>
- <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Chamadas"</string>
+ <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"Chamadas telefónicas"</string>
<string name="permgroupdesc_phoneCalls" msgid="7489701620446183770">"Monitorize, grave e processe chamadas telefónicas."</string>
<string name="permgrouplab_systemTools" msgid="4652191644082714048">"Ferramentas do sistema"</string>
<string name="permgroupdesc_systemTools" msgid="8162102602190734305">"Acesso e controlo de nível inferior do sistema."</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite a uma aplicação activar a depuração para outra aplicação. Algumas aplicações maliciosas podem utilizar este item para eliminar outras aplicações."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"alterar definições da IU"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Permite a uma aplicação mudar a configuração actual como, por exemplo, a região ou o tamanho global do tipo de letra."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"reiniciar outras aplicações"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Permite a uma aplicação efectuar o reinício forçado de outras aplicações."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"activar modo de carro"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Permite a uma aplicação activar o modo de carro."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"eliminar processos em segundo plano"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Permite a uma aplicação eliminar processos em segundo plano de outras aplicações, mesmo que não existam problemas de falta de memória."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"forçar paragem de outras aplicações"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Permite a uma aplicação efectuar uma paragem forçada de outras aplicações."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"forçar fecho da aplicação"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Permite a uma aplicação forçar qualquer actividade em primeiro plano a fechar e retroceder. Nunca deve ser necessário para aplicações normais."</string>
<string name="permlab_dump" msgid="1681799862438954752">"obter estado interno do sistema"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Permite a modificação das estatísticas recolhidas sobre a bateria. Não se destina a utilização por aplicações normais."</string>
<string name="permlab_backup" msgid="470013022865453920">"controlar a cópia de segurança e restauro do sistema"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Permite que a aplicação controle o mecanismo de cópia de segurança e restauro do sistema. Não deve ser utilizado por aplicações normais."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"fazer uma cópia de segurança e restaurar os dados da aplicação"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Permite que a aplicação participe no mecanismo de cópia de segurança e restauro do sistema."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"apresentar janelas não autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Permite a criação de janelas destinadas a utilização pela interface de utilizador interna do sistema. Não se destina a utilização por aplicações normais."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"apresentar alertas ao nível do sistema"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Permite ao titular vincular a interface de nível superior a um método de entrada de som. Nunca deve ser necessário para aplicações normais."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"vincular a uma imagem de fundo"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Permite ao titular vincular a interface de nível superior de uma imagem de fundo. Nunca deverá ser necessário para aplicações normais."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interagir com um administrador do dispositivo"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Permite ao titular enviar intenções para um administrador do dispositivo. Nunca deverá ser necessário para aplicações normais."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"mudar orientação do ecrã"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Permite a uma aplicação mudar a rotação do ecrã em qualquer momento. Nunca deve ser necessário para aplicações normais."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"enviar sinais Linux para aplicações"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Permite a uma aplicação instalar pacotes novos ou actualizados do Android. Algumas aplicações maliciosas podem utilizar este item para adicionar novas aplicações com autorizações arbitrariamente fortes."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"eliminar todos os dados da aplicações"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite a uma aplicação libertar espaço de armazenamento no telefone eliminando ficheiros no directório da cache da aplicação. Geralmente, o acesso é muito limitado para processamento do sistema."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos de aplicações"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que uma aplicação mova recursos de aplicações de meios internos para meios externos e vice-versa."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"ler ficheiros de registo do sistema"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Permite a uma aplicação ler a partir dos diversos ficheiros de registo do sistema. Isto permite descobrir informações gerais sobre a forma como o utilizador usa o telefone, mas estas não devem conter quaisquer dados pessoais ou privados."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ler/escrever em recursos propriedade de diag"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para apagar ou modificar dados do proprietário."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para ler dados do proprietário do telefone."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"ler dados do calendário"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite a uma aplicação ler todos os eventos do calendário armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os eventos do seu calendário a outras pessoas."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"escrever dados do calendário"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Permite a uma aplicação modificar os eventos do calendário armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para apagar ou modificar os dados do seu calendário."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou alterar eventos da agenda e enviar e-mails para os convidados"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Permite que uma aplicação adicione ou altere os eventos na sua agenda, a qual pode enviar e-mails para os convidados. As aplicações maliciosas podem utilizar esta função para apagar ou alterar os eventos da sua agenda ou para enviar e-mails para os convidados."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"fontes de localização fictícias para teste"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Crie fontes de localização fictícias para fins de teste. Algumas aplicações maliciosas podem utilizar este item para substituir a localização e/ou o estado devolvido por fontes de localização reais, tais como fornecedores de GPS ou de Rede."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"aceder a comandos adicionais do fornecedor de localização"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Permite à aplicação montar e desmontar sistemas de ficheiros para armazenamento removível."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatar armazenamento externo"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Permite a uma aplicação formatar o armazenamento removível."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"obter informações sobre o armazenamento seguro"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Permite à aplicação obter informações sobre o armazenamento seguro."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"criar armazenamento seguro"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Permite à aplicação criar armazenamento seguro."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"destruir armazenamento seguro"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Permite à aplicação destruir o armazenamento seguro."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"instalar/desinstalar armazenamento seguro"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Permite à aplicação instalar/desinstalar o armazenamento seguro."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"mudar o nome do armazenamento seguro"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Permite à aplicação mudar o nome do armazenamento seguro."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"controlar vibrador"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite à aplicação controlar o vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
@@ -327,7 +353,7 @@
<string name="permdesc_modifyPhoneState" msgid="3302284561346956587">"Permite à aplicação controlar as funcionalidades do telefone do dispositivo. Uma aplicação com esta autorização pode alternar entre redes, ligar e desligar o rádio do telefone, etc., sem nunca notificar o utilizador."</string>
<string name="permlab_readPhoneState" msgid="2326172951448691631">"ler identidade e estado do telefone"</string>
<string name="permdesc_readPhoneState" msgid="188877305147626781">"Permite à aplicação aceder às funcionalidades do dispositivo. Uma aplicação com esta autorização pode determinar o número deste telefone assim como o número de série do mesmo, se existe uma chamada activa, o número a que a essa chamada está ligada, etc."</string>
- <string name="permlab_wakeLock" msgid="573480187941496130">"impedir que o telefone entre em inactividade"</string>
+ <string name="permlab_wakeLock" msgid="573480187941496130">"impedir modo de inactividade do telefone"</string>
<string name="permdesc_wakeLock" msgid="7584036471227467099">"Permite a uma aplicação impedir o telefone de entrar em inactividade."</string>
<string name="permlab_devicePower" msgid="4928622470980943206">"ligar ou desligar o telefone"</string>
<string name="permdesc_devicePower" msgid="4577331933252444818">"Permite a uma aplicação ligar ou desligar o telefone."</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Permitir que a aplicação defina as sugestões de tamanho da imagem de fundo do sistema."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"repor definições de fábrica do sistemas"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Permite a uma aplicação repor totalmente as definições de fábrica do sistema, apagando todos os dados, configurações e aplicações instaladas."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"definir hora"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Permite a uma aplicação alterar a hora do telefone."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"definir fuso horário"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Permite a uma aplicação mudar o fuso horário do telefone."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"funciona como Serviço de Gestor de Conta"</string>
@@ -353,12 +381,14 @@
<string name="permdesc_useCredentials" msgid="7416570544619546974">"Permitir que uma aplicação solicite tokens de autenticação."</string>
<string name="permlab_accessNetworkState" msgid="6865575199464405769">"ver estado da rede"</string>
<string name="permdesc_accessNetworkState" msgid="558721128707712766">"Permite a uma aplicação ver o estado de todas as redes."</string>
- <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"acesso total à Internet"</string>
+ <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"acesso total à internet"</string>
<string name="permdesc_createNetworkSockets" msgid="4593339106921772192">"Permite a uma aplicação criar sockets de rede."</string>
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"escrever definições de Nome do ponto de acesso"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Permite a uma aplicaçaõ modificar as definições de APN, tais como Proxy e Porta de qualquer APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"mudar conectividade de rede"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Permite a uma aplicação mudar o estado da conectividade de rede."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Permite a uma aplicação alterar o estado da conectividade de rede."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Alterar conectividade associada"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Permite a uma aplicação alterar o estado da conectividade de rede associada."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"mudar definição de utilização de dados de segundo plano"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Permite a uma aplicação mudar a definição de utilização de dados de segundo plano."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"ver estado de Wi-Fi"</string>
@@ -387,8 +417,20 @@
<string name="permdesc_readDictionary" msgid="1082972603576360690">"Permite a uma aplicação ler quaisquer palavras, nomes e expressões privadas que o utilizador possa ter armazenado no dicionário do utilizador."</string>
<string name="permlab_writeDictionary" msgid="6703109511836343341">"escrever no dicionário definido pelo utilizador"</string>
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite a uma aplicação escrever novas palavras no dicionário do utilizador."</string>
- <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/eliminar os conteúdos do cartão SD"</string>
+ <string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/eliminar conteúdo do cartão SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que uma aplicação escreva no cartão SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"aceder ao sistema de ficheiros da cache"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite a uma aplicação ler e escrever no sistema de ficheiros da cache."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar palavra-passe"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restrinja os tipos de palavras-passe que está autorizado a utilizar."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Ver tentativas de início de sessão"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"O monitor falhou as tentativas de iniciar sessão no dispositivo para efectuar algumas acções."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Repor palavra-passe"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Force a palavra-passe para um novo valor, sendo necessário que o administrador lho forneça antes de poder iniciar sessão."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Forçar bloqueio"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Controle quando o dispositivo é bloqueado, sendo necessário reintroduzir a respectiva palavra-passe."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Efectue uma reposição de fábrica, eliminando todos os dados sem qualquer confirmação."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Residência"</item>
<item msgid="869923650527136615">"Móvel"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> através de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduzir código PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introduza a palavra-passe para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorrecto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, prima Menu e, em seguida, 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Prima Menu para desbloquear."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Desenhar padrão para desbloquear"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Chamada de emergência"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regressar à chamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lamentamos, tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"A carregar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Som activado"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Som desactivado"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que a aplicação leia todos os URLs visitados pelo browser e todos os marcadores do browser."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e marcadores do browser"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que uma aplicação modifique o histórico e os marcadores do browser armazenados no telefone. As aplicações maliciosas podem utilizar esta permissão para apagar ou modificar os dados do browser."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modificar permissões de localização geográfica do Navegador"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite a uma aplicação modificar as permissões de localização geográfica do Navegador. As aplicações mal intencionadas podem utilizar isto para enviar informações de localização para Web sites arbitrários."</string>
<string name="save_password_message" msgid="767344687139195790">"Quer que o browser memorize esta palavra-passe?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"Há 1 hora"</item>
<item quantity="other" msgid="2467273239587587569">"Há <xliff:g id="COUNT">%d</xliff:g> horas"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Últimos <xliff:g id="COUNT">%d</xliff:g> dias"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Último mês"</string>
+ <string name="older" msgid="5211975022815554840">"Mais antiga"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"ontem"</item>
<item quantity="other" msgid="2479586466153314633">"Há <xliff:g id="COUNT">%d</xliff:g> dias"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"semanas"</string>
<string name="year" msgid="4001118221013892076">"ano"</string>
<string name="years" msgid="6881577717993213522">"anos"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Todos os dias úteis (Seg-Sex)"</string>
- <string name="daily" msgid="5738949095624133403">"Diariamente"</string>
- <string name="weekly" msgid="983428358394268344">"Semanalmente à/ao <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Mensalmente"</string>
- <string name="yearly" msgid="1519577999407493836">"Anualmente"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Impossível reproduzir vídeo"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Lamentamos, este vídeo não é válido para transmissão em sequência neste dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Lamentamos, não é possível reproduzir este vídeo."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Forçar fecho"</string>
<string name="report" msgid="4060218260984795706">"Relatório"</string>
<string name="wait" msgid="7147118217226317732">"Esperar"</string>
- <string name="debug" msgid="9103374629678531849">"Depuração"</string>
<string name="sendText" msgid="5132506121645618310">"Seleccionar uma acção para texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume de multimédia"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Não são necessárias permissões"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ocultar"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar tudo"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"A carregar..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Armazenamento em massa USB "</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Ligado através de USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Ligou o telefone ao computador através de USB. Seleccione \"Montar\" se pretender copiar ficheiros entre o computador e o cartão SD do telefone."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Montar"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Não montar"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Ligou o telefone ao computador através de USB. Seleccione o botão abaixo se pretender copiar ficheiros entre o computador e o cartão SD do Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Activar armazenamento USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Existe um problema ao utilizar o cartão SD para armazenamento USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Ligado através de USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Seleccione para copiar ficheiro para/do seu computador."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desactivar armazenamento USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Opte por desactivar o armazenamento USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Desactivar armazenamento USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Antes de desactivar o armazenamento USB, certifique-se de que o desmontou no anfitrião USB. Seleccione \"Desactivar\" para desactivar o armazenamento USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Desactivar"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Cancelar"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Detectámos um problema ao desactivar o armazenamento USB. Verifique para se certificar de que desmontou o anfitrião USB e, em seguida, tente novamente."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"O armazenamento USB está a ser utilizado"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desactivar o armazenamento USB, certifique-se de que desinstalou (\"ejectou\") o cartão SD do Android do computador."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desactivar armazenamento USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ocorreu um problema ao desactivar o armazenamento USB. Confirme se desinstalou o anfitrião USB e, em seguida, tente novamente."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Activar armazenamento USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se activar o armazenamento USB, algumas aplicações que estiver a utilizar serão paradas e poderão ficar indisponíveis até desactivar o armazenamento USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Falha na operação USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatar cartão SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Tem a certeza de que pretende formatar o cartão SD? Perder-se-ão todos os dados no cartão."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Nenhuma actividade correspondente encontrada"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"actualizar estatísticas de utilização de componentes"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite a modificação de estatísticas de utilização de componentes recolhidas. Não se destina a utilização por aplicações normais."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Permite invocar o serviço de contentor predefinido para copiar conteúdo. Não se destina a ser utilizado por aplicações normais."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Permite invocar o serviço de contentor predefinido para copiar conteúdo. Não se destina a ser utilizado por aplicações normais."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Tocar duas vezes para controlar o zoom"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Erro ao inchar miniaplicação"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Ir"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Criar contacto"\n"utilizando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"verificado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não verificado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão da conta <xliff:g id="ACCOUNT">%1$s</xliff:g> a partir de <xliff:g id="APPLICATION">%2$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"As aplicações listadas estão a pedir autorização para aceder às credenciais de início de sessão <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> a partir de <xliff:g id="APPLICATION">%3$s</xliff:g>. Pretende conceder esta autorização? Em caso afirmativo, a sua resposta será memorizada e não terá de responder a esta questão novamente."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Uma ou várias das aplicações seguintes solicitam permissão para aceder à sua conta, agora e no futuro."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Pretende autorizar este pedido?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Pedido de acesso"</string>
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Recusar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização Solicitada"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de túnel de camada 2 (L2TP)"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec baseada em chave pré- partilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec baseada em certificado"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
+ <string name="reset" msgid="2448168080964209908">"Repor"</string>
+ <string name="submit" msgid="1602335572089911941">"Enviar"</string>
+ <string name="description_star" msgid="2654319874908576133">"favorito"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo automóvel activado"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para sair do modo automóvel."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Associação activa"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilização elevada de dados móveis"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre a utilização de dados móveis"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados móveis excedido"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre a utilização de dados móveis"</string>
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index b6bcca2..92bf56c 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Acesso restrito alterado"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"O serviço de dados está bloqueado."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"O serviço de emergência está bloqueado."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"O serviço de voz/SMS está bloqueado."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Todos os serviços de voz/SMS estão bloqueados."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"O serviço de voz está bloqueado."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Todos os serviços de voz estão bloqueados."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"O serviço de SMS está bloqueado."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Os serviços de voz/dados estão bloqueados."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Os serviços de voz/SMS estão bloqueados."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Todos os serviços de voz/dados/SMS estão bloqueados."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Voz"</string>
<string name="serviceClassData" msgid="872456782077937893">"Dados"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Desligar"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Encerrando…"</string>
<string name="shutdown_confirm" msgid="649792175242821353">"O seu telefone será desligado."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Recente"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Nenhum aplicativo recente."</string>
<string name="global_actions" msgid="2406416831541615258">"Opções do telefone"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Bloquear tela"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Permite que um aplicativo ative a depuração de outro aplicativo. Aplicativos maliciosos podem usar isso para encerrar outros aplicativos."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"alterar as suas configurações de UI"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Permite que um aplicativo altere a configuração atual, como o local ou o tamanho geral da fonte."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"reiniciar outros aplicativos"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Permite que um aplicativo reinicie forçosamente outros aplicativos."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"ativar o modo de carro"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Permite que um aplicativo ative o modo de carro."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"interromper processos em segundo plano"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Permite que um aplicativo interrompa processos em segundo plano de outros aplicativos, mesmo se a memória não estiver baixa."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"forçar a interrupção de outros aplicativos"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Permite que um aplicativo pare forçosamente outros aplicativos."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"forçar fechamento do aplicativo"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Permite que um aplicativo force o fechamento de qualquer atividade que esteja em primeiro plano. Aplicativos normais não devem precisar disso em momento algum."</string>
<string name="permlab_dump" msgid="1681799862438954752">"recuperar o estado interno do sistema"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Permite a modificação das estatísticas de bateria coletadas. Não deve ser usado por aplicativos normais."</string>
<string name="permlab_backup" msgid="470013022865453920">"controlar backup e restauração do sistema"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Permite que o aplicativo controle o mecanismo de backup e restauração do sistema. Não deve ser usado por aplicativos normais."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"fazer backup e restaurar os dados do aplicativo"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Permite que o aplicativo participe no mecanismo de backup e restauração do sistema."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"exibir janelas não autorizadas"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Permite a criação de janelas destinadas ao uso pela interface de usuário do sistema interno. Não deve ser usado por aplicativos normais."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"exibir alertas de nível do sistema"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Permite que o detentor se sujeite à interface de nível superior de um método de entrada. Aplicativos normais não devem precisar disso em momento algum."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"sujeitar-se a um plano de fundo"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Permite que o detentor se sujeite à interface de nível superior de um plano de fundo. Aplicativos normais não devem precisar disso em momento algum."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"interagir com o administrador de um dispositivo"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Permite que o detentor envie tentativas ao administrador de um dispositivo. Não é necessário para aplicativos normais."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"alterar orientação da tela"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Permite que um aplicativo altere a rotação da tela a qualquer momento. Aplicativos normais não devem precisar disso em momento algum."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"enviar sinais de Linux para os aplicativos"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Permite que um aplicativo instale pacotes novos ou atualizados do Android. Aplicativos maliciosos podem usar isso para adicionar novos aplicativos com permissões arbitrariamente avançadas."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"excluir todos os dados de cache do aplicativo"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Permite que um aplicativo libere o espaço de armazenamento do telefone excluindo arquivos no diretório de cache do aplicativo. O acesso é normalmente muito restrito para o processo do sistema."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Mover recursos do aplicativo"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Permite que um aplicativo mova os recursos do aplicativo da mídia interna para a externa e vice-versa."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"ler arquivos de registro do sistema"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Permite que um aplicativo leia os diversos arquivos de registro do sistema. Isso permite que ele descubra informações gerais sobre o que você está fazendo com o telefone, porém esses arquivos não devem conter informações pessoais ou privadas."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"ler/gravar em recursos pertencentes ao diag"</string>
@@ -273,17 +289,17 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que um aplicativo modifique os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar dados do proprietário."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que um aplicativo leia os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para ler os dados de proprietário do telefone."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"ler dados da agenda"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que um aplicativo leia todos os eventos da agenda armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar eventos da sua agenda para outras pessoas."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"gravar dados da agenda"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Permite que um aplicativo modifique os eventos da agenda armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os seus dados da agenda."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou modificar eventos da agenda e enviar e-mail aos convidados"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Permite que um aplicativo adicione ou altere os eventos na sua agenda, que pode enviar e-mail aos convidados. Aplicativos maliciosos podem usar isso para apagar ou modificar os eventos da sua agenda ou para enviar e-mail aos convidados."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"fontes de locais fictícios para teste"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Cria fontes de locais fictícios para teste. Aplicativos maliciosos podem usar isso para substituir o local e/ou o status retornado pelas fontes de locais reais como GPS ou provedores de rede."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"acessar comandos extras do provedor de localização"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="1948144701382451721">"Acessa comandos extras do provedor de localização. Aplicativos maliciosos podem usar isso para interferir na operação do GPS ou de outras fontes de localização."</string>
<string name="permlab_installLocationProvider" msgid="6578101199825193873">"autorização para instalar um provedor de localização"</string>
<string name="permdesc_installLocationProvider" msgid="5449175116732002106">"Cria fontes de locais fictícios para teste. Aplicativos maliciosos podem usar isso para substituir o local e/ou o status retornado pelas fontes de locais reais como GPS ou provedores de rede ou para monitorar e informar sua localização para uma fonte externa."</string>
- <string name="permlab_accessFineLocation" msgid="8116127007541369477">"Localização precisa (GPS)"</string>
+ <string name="permlab_accessFineLocation" msgid="8116127007541369477">"localização exata (GPS)"</string>
<string name="permdesc_accessFineLocation" msgid="7411213317434337331">"Acessa fontes de localização precisa como o GPS (Global Positioning System) no telefone, onde disponível. Aplicativos maliciosos podem usar isso para determinar onde você está e podem consumir energia adicional da bateria."</string>
<string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"local aproximado (com base na rede)"</string>
<string name="permdesc_accessCoarseLocation" msgid="8235655958070862293">"Acessa fontes de localização aproximada como o banco de dados de rede celular para determinar a localização aproximada de um telefone, onde disponível. Aplicativos maliciosos podem usar isso para determinar aproximadamente onde você está."</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Permite que o aplicativo monte e desmonte arquivos de sistema para armazenamento removível."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatar armazenamento externo"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Permite que o aplicativo formate o armazenamento removível."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"obter informações sobre o armazenamento seguro"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Permite que o aplicativo obtenha informações sobre armazenamento seguro."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"criar armazenamento seguro"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Permite que o aplicativo crie um armazenamento seguro."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"destrói o armazenamento seguro"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Permite que o aplicativo destrua o armazenamento seguro."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"conectar/desconectar armazenamento seguro"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Permite que o aplicativo conecte/desconecte o armazenamento seguro."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"renomear o armazenamento seguro"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Permite que o aplicativo renomeie o armazenamento seguro."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"controlar o vibrador"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que o aplicativo controle o vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
@@ -333,12 +359,14 @@
<string name="permdesc_devicePower" msgid="4577331933252444818">"Permite que o aplicativo ative ou desative o telefone."</string>
<string name="permlab_factoryTest" msgid="3715225492696416187">"executar no modo de teste de fábrica"</string>
<string name="permdesc_factoryTest" msgid="8136644990319244802">"Executa como um teste do fabricante de nível inferior, permitindo o acesso completo ao hardware do telefone. Disponível apenas quando um telefone está em execução no modo de teste do fabricante."</string>
- <string name="permlab_setWallpaper" msgid="6627192333373465143">"definir papel de parede"</string>
- <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Permite que o aplicativo defina o papel de parede do sistema."</string>
- <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"definir dicas de tamanho do papel de parede"</string>
- <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Permite que o aplicativo defina as dicas de tamanho do papel de parede do sistema."</string>
+ <string name="permlab_setWallpaper" msgid="6627192333373465143">"definir plano de fundo"</string>
+ <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Permite que o aplicativo defina o plano de fundo do sistema."</string>
+ <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"definir sugestões de tamanho do plano de fundo"</string>
+ <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Permite que o aplicativo defina as dimensões do tamanho do plano de fundo do sistema."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"redefinir o sistema para os padrões de fábrica"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Permite que um aplicativo redefina completamente o sistema para as configurações de fábrica, apagando todos os dados, configuração e aplicativos instalados."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"definir hora"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Permite que um aplicativo altere o horário do telefone."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"definir fuso horário"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Permite que um aplicativo altere o fuso horário do telefone."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"agir como AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"gravar as configurações do Nome do ponto de acesso"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Permite que um aplicativo modifique as configurações de APN, como Proxy e Porta de qualquer APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"alterar conectividade da rede"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Permite que um aplicativo altere o estado da conectividade de rede."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Permite que um aplicativo altere o estado da conectividade de rede."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Alterar conectividade vinculada"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Permite que um aplicativo altere o estado da conectividade de rede vinculada."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"alterar configuração de uso dos dados de segundo plano"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Permite que um aplicativo altere a configuração de uso dos dados de segundo plano."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"visualizar estado da rede Wi-Fi"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Permite que um aplicativo grave novas palavras no dicionário do usuário."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"modificar/excluir conteúdo do cartão SD"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Permite que um aplicativo grave no cartão SD."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"acessar o sistema de arquivos de cache"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Permite que um aplicativo leia e grave no sistema de arquivos de cache."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Limitar senha"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Restringe os tipos de senha que você tem permissão de usar."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Exibir tentativas de login"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Monitora as tentativas malsucedidas de login no aparelho para executar alguma ação."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Redefinir senha"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Força a sua senha para um novo valor, exigindo que o administrador a forneça antes que você possa fazer login."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Forçar bloqueio"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Controla o bloqueio do aparelho, exigindo que a senha seja digitada novamente."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Execute uma redefinição de fábrica, excluindo todos os seus dados sem qualquer confirmação."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Residencial"</item>
<item msgid="869923650527136615">"Celular"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Digite o código PIN"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Digite a senha para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorreto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Pressione Menu para desbloquear."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Desenhe o padrão para desbloquear"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Chamada de emergência"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retornar à chamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correto!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Carregando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -519,11 +563,14 @@
<string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de usuário (e-mail)"</string>
<string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Senha"</string>
<string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Fazer login"</string>
- <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de usuário ou senha inválida."</string>
+ <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de usuário ou senha inválidos."</string>
<string name="lockscreen_glogin_checking_password" msgid="6758890536332363322">"Verificando..."</string>
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Desbloquear"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Som ativado"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Som desativado"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Permite que o aplicativo leia todos os URLs visitados pelo Navegador e todos os favoritos do Navegador."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"gravar histórico e favoritos do Navegador"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Permite que um aplicativo modifique o histórico ou os favoritos do Navegador armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os dados do seu Navegador."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Modifique as permissões de geolocalização do seu navegador"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Permite que um aplicativo modifique as permissões de geolocalização do navegador. Aplicativos maliciosos podem usar isso para permitir o envio de informações de localização a sites arbitrários."</string>
<string name="save_password_message" msgid="767344687139195790">"Deseja que o navegador lembre desta senha?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Agora não"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Lembrar"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 hora atrás"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> horas atrás"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Últimos <xliff:g id="COUNT">%d</xliff:g> dias"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Mês passado"</string>
+ <string name="older" msgid="5211975022815554840">"Mais antigos"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"ontem"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> dias atrás"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"semanas"</string>
<string name="year" msgid="4001118221013892076">"ano"</string>
<string name="years" msgid="6881577717993213522">"anos"</string>
- <string name="every_weekday" msgid="8777593878457748503">"De segunda a sexta-feira"</string>
- <string name="daily" msgid="5738949095624133403">"Diariamente"</string>
- <string name="weekly" msgid="983428358394268344">"Semanalmente na <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Mensalmente"</string>
- <string name="yearly" msgid="1519577999407493836">"Anualmente"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Não é possível reproduzir o vídeo"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Este vídeo não é válido para transmissão com este dispositivo."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Este vídeo não pode ser reproduzido."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Forçar fechamento"</string>
<string name="report" msgid="4060218260984795706">"Informar"</string>
<string name="wait" msgid="7147118217226317732">"Aguardar"</string>
- <string name="debug" msgid="9103374629678531849">"Depurar"</string>
<string name="sendText" msgid="5132506121645618310">"Selecione uma ação para o texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume da mídia"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Nenhuma permissão necessária"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Ocultar"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Mostrar todas"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Carregando..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Armazenamento USB em massa"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"Conectado por USB"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Você conectou o telefone ao computador via USB. Selecione \"Montar\" se quiser copiar arquivos entre o computador e o cartão SD do seu telefone."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Montar"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Não montar"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Você conectou o telefone ao computador via USB. Selecione o botão abaixo se quiser copiar arquivos entre o computador e o cartão SD do seu Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Ativar o armazenamento USB"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Há um problema com o uso do seu cartão SD para armazenamento USB."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"Conectado por USB"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Selecione para copiar arquivos para/do seu computador."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Desativar o armazenamento USB"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Selecione para desativar o armazenamento USB."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Desativar o armazenamento USB"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Antes de desativar o armazenamento USB, desmonte o host USB. Selecione \"Desativar\" para desativar o armazenamento USB."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Desativar"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Cancelar"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Encontramos um problema ao desativar o armazenamento USB. Verifique se desmontou o host USB e tente novamente."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"Armazenamento USB em uso"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Antes de desativar o armazenamento USB, verifique se desconectou (“ejetouâ€) o cartão SD do Android do computador."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Desativar o armazenamento USB"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Houve um problema ao desativar o armazenamento USB. Verifique se desconectou o host USB e tente novamente."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Ativar o armazenamento USB"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Se você ativar o armazenamento USB, alguns aplicativos que estão em uso serão interrompidos e poderão não estar disponíveis até você desativar o armazenamento USB."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Falha de operação de USB"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatar cartão SD"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Tem certeza de que deseja formatar o cartão SD? Todos os dados no seu cartão serão perdidos."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Formatar"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Nenhum atividade correspondente foi encontrada"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"atualizar estatísticas de uso do componente"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Permite a modificação das estatísticas de uso do componente coletadas. Não deve ser usado por aplicativos normais."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Permite a invocação do serviço de recipiente padrão para copiar o conteúdo. Não deve ser usado por aplicativos normais."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Permite a invocação do serviço de recipiente padrão para copiar o conteúdo. Não deve ser usado por aplicativos normais."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Toque duas vezes para ter controle do zoom"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Erro ao aumentar o widget"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Ir"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Criar contato "\n"usando <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"selecionado"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"não selecionado"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login para a conta <xliff:g id="ACCOUNT">%1$s</xliff:g> do <xliff:g id="APPLICATION">%2$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Os aplicativos listados estão solicitando autorização para acessar as credenciais de login <xliff:g id="TYPE">%1$s</xliff:g> para a conta <xliff:g id="ACCOUNT">%2$s</xliff:g> do <xliff:g id="APPLICATION">%3$s</xliff:g>. Deseja conceder essa autorização? Em caso afirmativo, sua resposta será lembrada e essa pergunta não será feita novamente."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"O aplicativo a seguir ou outros aplicativos solicitam permissão para acessar a sua conta, agora e no futuro."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Deseja permitir essa solicitação?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Solicitação de acesso"</string>
<string name="allow" msgid="7225948811296386551">"Permitir"</string>
<string name="deny" msgid="2081879885755434506">"Negar"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Autorização solicitada"</string>
@@ -790,10 +843,22 @@
<string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string>
<string name="sync_binding_label" msgid="3687969138375092423">"Sincronizar"</string>
<string name="accessibility_binding_label" msgid="4148120742096474641">"Acessibilidade"</string>
- <string name="wallpaper_binding_label" msgid="1240087844304687662">"Papel de parede"</string>
- <string name="chooser_wallpaper" msgid="7873476199295190279">"Alterar papel de parede"</string>
+ <string name="wallpaper_binding_label" msgid="1240087844304687662">"Plano de fundo"</string>
+ <string name="chooser_wallpaper" msgid="7873476199295190279">"Alterar plano de fundo"</string>
<string name="pptp_vpn_description" msgid="2688045385181439401">"Protocolo de encapsulamento ponto a ponto"</string>
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Protocolo de encapsulamento de camada 2"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec com base em chave pré-compartilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec com base em certificado"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
+ <string name="reset" msgid="2448168080964209908">"Redefinir"</string>
+ <string name="submit" msgid="1602335572089911941">"Enviar"</string>
+ <string name="description_star" msgid="2654319874908576133">"favorito"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo de carro ativado"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecione para sair do modo de carro."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Vínculo ativo"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Toque para configurar"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Alto uso de dados do celular"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre uso de dados do celular"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados do celular excedido"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Toque para saber mais sobre o uso de dados do celular"</string>
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 4f341cf..abc726c 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"Б"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ñтупа изменены"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Служба данных заблокирована."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Служба ÑкÑтренной помощи заблокирована."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Служба передачи SMS/голоÑовых Ñообщений заблокирована."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Ð’Ñе Ñлужбы передачи SMS/голоÑовых Ñообщений заблокированы."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Служба передачи голоÑовых Ñообщений заблокирована."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Ð’Ñе Ñлужбы передачи голоÑовых Ñообщений заблокированы."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"Служба передачи SMS заблокирована."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Службы передачи голоÑовых Ñообщений/данных заблокированы."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Службы передачи голоÑовых Ñообщений/SMS заблокированы."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Ð’Ñе Ñлужбы передачи голоÑовых Ñообщений/данных/SMS заблокированы."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"ГолоÑÐ¾Ð²Ð°Ñ ÑвÑзь"</string>
<string name="serviceClassData" msgid="872456782077937893">"Данные"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"ФÐКС"</string>
@@ -118,18 +127,19 @@
<string name="low_memory" msgid="6632412458436461203">"ПамÑÑ‚ÑŒ телефона заполнена! Удалите файлы, чтобы оÑвободить меÑто."</string>
<string name="me" msgid="6545696007631404292">"Я"</string>
<string name="power_dialog" msgid="1319919075463988638">"Параметры телефона"</string>
- <string name="silent_mode" msgid="7167703389802618663">"Тихий режим"</string>
+ <string name="silent_mode" msgid="7167703389802618663">"Режим без звука"</string>
<string name="turn_on_radio" msgid="3912793092339962371">"Включить беÑпроводную ÑвÑзь"</string>
<string name="turn_off_radio" msgid="8198784949987062346">"Отключить беÑпроводное Ñоединение"</string>
<string name="screen_lock" msgid="799094655496098153">"Блокировка Ñкрана"</string>
- <string name="power_off" msgid="4266614107412865048">"Выключить ÑвÑзь"</string>
+ <string name="power_off" msgid="4266614107412865048">"Выключение"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Выключение..."</string>
- <string name="shutdown_confirm" msgid="649792175242821353">"Телефон будет отключен."</string>
+ <string name="shutdown_confirm" msgid="649792175242821353">"Телефон будет выключен."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Ðедавние"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Ðет поÑледних приложений."</string>
<string name="global_actions" msgid="2406416831541615258">"Параметры телефона"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Блокировка Ñкрана"</string>
<string name="global_action_power_off" msgid="4471879440839879722">"Отключить питание"</string>
- <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Тихий режим"</string>
+ <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Режим без звука"</string>
<string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Звук ВЫКЛ"</string>
<string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Звук ВКЛЮЧЕÐ"</string>
<string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Режим полета"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"ПозволÑет приложению запуÑкать процеÑÑ Ð¾Ñ‚Ð»Ð°Ð´ÐºÐ¸ другого приложениÑ. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ Ð¾Ñтановки других приложений."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"изменÑÑ‚ÑŒ наÑтройки пользовательÑкого интерфейÑа"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"ПозволÑет приложению изменÑÑ‚ÑŒ текущую конфигурацию, например региональные наÑтройки или размер шрифта."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"перезапуÑкать другие приложениÑ"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"ПозволÑет приложению принудительно перезапуÑкать другие приложениÑ."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"включить режим громкой ÑвÑзи"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"ПозволÑет программе включить режим громкой ÑвÑзи."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"завершать фоновые процеÑÑÑ‹"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"ПозволÑет программе завершать фоновые процеÑÑÑ‹ других программ, даже еÑли памÑÑ‚ÑŒ не заполнена."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"принудительно закрывать другие программы"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"ПозволÑет программе принудительно оÑтанавливать другие программы."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"принудительно закрывать приложениÑ"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"ПозволÑет приложению принудительно закрыть или вернуть в иÑходное ÑоÑтоÑние процеÑÑÑ‹, выполнÑемые в активном режиме. Ðе требуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… приложений."</string>
<string name="permlab_dump" msgid="1681799862438954752">"извлекать данные о внутреннем ÑоÑтоÑнии ÑиÑтемы"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"ПозволÑет изменÑÑ‚ÑŒ Ñобранную ÑтатиÑтику батареи. Ðе предназначено Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼Ð¸ приложениÑми."</string>
<string name="permlab_backup" msgid="470013022865453920">"управление резервным копированием и воÑÑтановлением ÑиÑтемы"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Разрешает приложению контролировать механизмы резервного ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑиÑтемы. Ðе иÑпользуетÑÑ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼Ð¸ приложениÑми."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"Ñоздать резервную копию и воÑÑтановить данные приложениÑ"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"ПозволÑет приложению учаÑтвовать в механизме резервного ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑиÑтемы."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"показывать неавторизованные окна"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Разрешает Ñоздание окон, предназначенных Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½Ð¸Ð¼ пользовательÑким интерфейÑом ÑиÑтемы. Ðе предназначено Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼Ð¸ приложениÑми."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"показывать Ð¾Ð¿Ð¾Ð²ÐµÑ‰ÐµÐ½Ð¸Ñ ÑиÑтемного уровнÑ"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"ПозволÑет выполнÑÑ‚ÑŒ привÑзку к интерфейÑу ввода верхнего уровнÑ. Ðе требуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… приложений."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"ÑвÑзать Ñ Ñ„Ð¾Ð½Ð¾Ð²Ñ‹Ð¼ риÑунком"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Разрешает выполнÑÑ‚ÑŒ привÑзку к интерфейÑу фонового риÑунка верхнего уровнÑ. Ðе требуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… приложений."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"взаимодейÑтвовать Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратором уÑтройÑтва"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"ПозволÑет владельцу отправлÑÑ‚ÑŒ целевые Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтратору уÑтройÑтва. Ðикогда не иÑпользуетÑÑ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼Ð¸ приложениÑми."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"изменÑÑ‚ÑŒ ориентацию Ñкрана"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"ПозволÑет приложению изменÑÑ‚ÑŒ ориентацию Ñкрана в любое времÑ. Ðе требуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… приложений."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"отправлÑÑ‚ÑŒ приложениÑм Ñигналы Linux"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"ПозволÑет приложению уÑтанавливать новые или обновленные пакеты Android. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð¾Ð²Ñ‹Ñ… приложений Ñо Ñколь угодно выÑоким уровнем разрешениÑ."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"удалÑÑ‚ÑŒ вÑе данные из кÑша приложений"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"ПозволÑет приложению оÑвобождать памÑÑ‚ÑŒ телефона Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð² из каталога кÑша приложений. Обычно Ñто разрешаетÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ ÑиÑтемным процеÑÑам."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Перемещать реÑурÑÑ‹ приложениÑ"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"ПозволÑет приложению перемещать реÑурÑÑ‹ Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ñ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½Ð¸Ñ… на внешние ноÑители и наоборот."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"Ñчитывать ÑиÑтемные файлы журналов"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"ПозволÑет приложению Ñчитывать информацию из различных журналов ÑиÑтемы. Приложение может получить ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ работе Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½Ð¾Ð¼, но они не должны Ñодержать какой-либо личной или конфиденциальной информации."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"Ñчитывать/запиÑывать данные в реÑурÑÑ‹, принадлежащие группе диагноÑтики"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"ПозволÑет приложению изменÑÑ‚ÑŒ ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ владельце, Ñохраненные на телефоне. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… владельца."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"Ñчитывать данные о владельце"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"ПозволÑет приложению Ñчитывать ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ владельце, Ñохраненные в памÑти телефона. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ ÑÑ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… владельца."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"Ñчитывать данные календарÑ"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"Ñчитывать мероприÑÑ‚Ð¸Ñ Ð² календаре"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"ПозволÑет приложению Ñчитывать вÑе ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ, Ñохраненные на телефоне. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð¸ ваших Ñобытий ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð¿Ð¾Ñторонним лицам."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"запиÑывать данные календарÑ"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"ПозволÑет приложению изменÑÑ‚ÑŒ ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ, Ñохраненные на телефоне. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñобытий календарÑ."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"добавлÑÑ‚ÑŒ и изменÑÑ‚ÑŒ мероприÑÑ‚Ð¸Ñ Ð² календаре и отправлÑÑ‚ÑŒ пиÑьма гоÑÑ‚Ñм"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"ПозволÑет приложению добавлÑÑ‚ÑŒ и изменÑÑ‚ÑŒ мероприÑÑ‚Ð¸Ñ Ð² вашем календаре, в котором предуÑмотрена Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð¸Ñем гоÑÑ‚Ñм. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ воÑпользоватьÑÑ Ñтим Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼ÐµÑ€Ð¾Ð¿Ñ€Ð¸Ñтий в календаре или отправки пиÑем гоÑÑ‚Ñм."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"копировать иÑточники меÑÑ‚ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Создавать копии иÑточников данных о меÑтоположении Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ·Ð°Ð¿Ð¸Ñи меÑта и/или ÑоÑтоÑниÑ, возвращаемого дейÑтвительными иÑточниками данных о меÑтоположении, такими как GPS или операторы ÑвÑзи."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"получать доÑтуп к дополнительным командам иÑточника данных о меÑтоположении"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"ПозволÑет приложению монтировать и удалÑÑ‚ÑŒ файловые ÑиÑтемы Ñъемных ноÑителей."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"форматировать внешний накопитель"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"ПозволÑет приложению форматировать Ñъемный накопитель."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"получать ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ защищенном хранилище"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"ПозволÑет программе получать ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ защищенном хранилище."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"Ñоздать защищенное хранилище"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"ПозволÑет программам Ñоздавать защищенные хранилища."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"удалÑÑ‚ÑŒ защищенное хранилище"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"ПозволÑет программе удалÑÑ‚ÑŒ защищенные хранилища."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"подключать / отключать защищенное хранилище"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"ПозволÑет программе подключать и отключать защищенные хранилища."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"переименовать защищенное хранилище"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"ПозволÑет программам переименовывать защищенные хранилища."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"управлÑÑ‚ÑŒ вибровызовом"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"ПозволÑет приложению управлÑÑ‚ÑŒ виброзвонком."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"управлÑÑ‚ÑŒ вÑпышкой"</string>
@@ -338,7 +364,9 @@
<string name="permlab_setWallpaperHints" msgid="3600721069353106851">"давать рекомендации по размеру фоновых риÑунков"</string>
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"ПозволÑет данному приложению уÑтанавливать Ñоветы по размеру фоновых риÑунков."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"воÑÑтанавливать параметры ÑиÑтемы по умолчанию, уÑтановленные на заводе-изготовителе"</string>
- <string name="permdesc_masterClear" msgid="5033465107545174514">"ПозволÑет приложению воÑÑтановить Ñтандартные наÑтройки ÑиÑтемы, удалив вÑе данные, конфигурацию и уÑтановленные приложениÑ."</string>
+ <string name="permdesc_masterClear" msgid="5033465107545174514">"ПозволÑет приложению воÑÑтановить заводÑкие наÑтройки ÑиÑтемы, удалив вÑе данные, конфигурацию и уÑтановленные приложениÑ."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"уÑтановить времÑ"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"ПозволÑет программе изменÑÑ‚ÑŒ Ð²Ñ€ÐµÐ¼Ñ Ð½Ð° телефоне."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"наÑтраивать чаÑовой поÑÑ"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"ПозволÑет приложению изменÑÑ‚ÑŒ чаÑовой поÑÑ Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½Ð°."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"выÑтупать в качеÑтве Ñлужбы ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð°ÐºÐºÐ°ÑƒÐ½Ñ‚Ð¾Ð¼"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"запиÑывать наÑтройки имени точки доÑтупа"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"ПозволÑет приложению изменÑÑ‚ÑŒ наÑтройки APN, такие как прокÑи-Ñервер и порт любого APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"изменÑÑ‚ÑŒ наÑтройки Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº Ñети"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"ПозволÑет приложению изменÑÑ‚ÑŒ ÑоÑтоÑние подключаемоÑти Ñети."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"ПозволÑет программе изменÑÑ‚ÑŒ ÑоÑтоÑние Ñетевого канала."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"ИзменÑÑ‚ÑŒ подключение к компьютеру"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"ПозволÑет программе изменÑÑ‚ÑŒ ÑоÑтоÑние подключенного Ñетевого канала."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"изменÑÑ‚ÑŒ наÑтройки иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… в фоновом режиме"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"ПозволÑет приложению изменÑÑ‚ÑŒ наÑтройку иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… в фоновом режиме."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"проÑматривать ÑоÑтоÑние Wi-Fi"</string>
@@ -385,10 +415,22 @@
<string name="permdesc_subscribedFeedsWrite" msgid="8121607099326533878">"ПозволÑет приложению изменÑÑ‚ÑŒ Ñинхронизированные каналы. ВредоноÑные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ иÑпользовать Ñту возможноÑÑ‚ÑŒ Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñинхронизированных каналов."</string>
<string name="permlab_readDictionary" msgid="432535716804748781">"выполнÑÑ‚ÑŒ чтение из пользовательÑкого ÑловарÑ"</string>
<string name="permdesc_readDictionary" msgid="1082972603576360690">"ПозволÑет приложению Ñчитывать любые Ñлова, имена и фразы личного пользованиÑ, которые могут хранитьÑÑ Ð² пользовательÑком Ñловаре."</string>
- <string name="permlab_writeDictionary" msgid="6703109511836343341">"запиÑывать в пользовательÑкий Ñловарь"</string>
- <string name="permdesc_writeDictionary" msgid="2241256206524082880">"ПозволÑет приложению запиÑывать новые Ñлова в пользовательÑкий Ñловарь."</string>
+ <string name="permlab_writeDictionary" msgid="6703109511836343341">"запиÑывать в Ñловарь пользователÑ"</string>
+ <string name="permdesc_writeDictionary" msgid="2241256206524082880">"ПозволÑет приложению запиÑывать новые Ñлова в Ñловарь пользователÑ."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"изменÑÑ‚ÑŒ/удалÑÑ‚ÑŒ Ñодержание SD-карты"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Разрешает приложению запиÑÑŒ на SD-карту"</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"получать доÑтуп к кÑшу файловой ÑиÑтемы"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Разрешает программам доÑтуп Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи и Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ðº кÑшу файловой ÑиÑтемы."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Ограничить пароль"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Ограничить типы паролей, доÑтупных Ð´Ð»Ñ Ð¸ÑпользованиÑ."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"ПроÑмотр попыток входа"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Мониторинг неудачных попыток Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ðº уÑтройÑтву Ð´Ð»Ñ Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð½Ñ‹Ñ… дейÑтвий."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"СброÑить пароль"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Принудительно изменить пароль, который админиÑтратор должен будет Ñообщить вам, чтобы вы Ñмогли выполнить вход."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"ÐŸÑ€Ð¸Ð½ÑƒÐ´Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ°"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Управление блокировкой уÑтройÑтва, требующей ввода паролÑ."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Удалить вÑе данные"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Выполнить ÑÐ±Ñ€Ð¾Ñ Ðº начальным наÑтройкам Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸ÐµÐ¼ вÑех данных без запроÑа подтверждениÑ."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Домашний"</item>
<item msgid="869923650527136615">"Мобильный"</item>
@@ -446,11 +488,11 @@
<string name="phoneTypeIsdn" msgid="8022453193171370337">"ISDN"</string>
<string name="phoneTypeMain" msgid="6766137010628326916">"ОÑновной"</string>
<string name="phoneTypeOtherFax" msgid="8587657145072446565">"Доп. факÑ"</string>
- <string name="phoneTypeRadio" msgid="4093738079908667513">"Радио"</string>
+ <string name="phoneTypeRadio" msgid="4093738079908667513">"Радиотелефон"</string>
<string name="phoneTypeTelex" msgid="3367879952476250512">"ТелекÑ"</string>
<string name="phoneTypeTtyTdd" msgid="8606514378585000044">"Телетайп"</string>
- <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Рабочий мобильный"</string>
- <string name="phoneTypeWorkPager" msgid="649938731231157056">"Рабочий пейджер"</string>
+ <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Рабочий моб."</string>
+ <string name="phoneTypeWorkPager" msgid="649938731231157056">"Раб. пейджер"</string>
<string name="phoneTypeAssistant" msgid="5596772636128562884">"Секретарь"</string>
<string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
<string name="eventTypeBirthday" msgid="2813379844211390740">"День рождениÑ"</string>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Введите PIN-код"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Введите пароль Ð´Ð»Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Ðеверный PIN-код!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Ð”Ð»Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ нажмите \"Меню\", а затем 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ЭкÑÑ‚Ñ€ÐµÐ½Ð½Ð°Ñ Ñлужба"</string>
@@ -493,7 +536,8 @@
<string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Ðажмите \"Меню\", чтобы разблокировать Ñкран или вызвать Ñлужбу ÑкÑтренной помощи."</string>
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Ð”Ð»Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ нажмите \"Меню\"."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Ð”Ð»Ñ Ñ€Ð°Ð·Ð±Ð»Ð¾ÐºÐ¸Ñ€Ð¾Ð²ÐºÐ¸ введите графичеÑкий ключ"</string>
- <string name="lockscreen_emergency_call" msgid="5347633784401285225">"Вызов Ñлужбы ÑкÑтренной помощи"</string>
+ <string name="lockscreen_emergency_call" msgid="5347633784401285225">"ЭкÑтренный вызов"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"ВернутьÑÑ Ðº вызову"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Правильно!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Повторите попытку"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Идет зарÑдка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,7 +547,7 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Ðет SIM-карты."</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"SIM-карта не уÑтановлена."</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"Ð’Ñтавьте SIM-карту."</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"Только вызовы Ñлужбы ÑкÑтренной помощи"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"Только ÑкÑтренный вызов"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"Сеть заблокирована"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM-карта заблокирована Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ кода PUK."</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"См. руководÑтво Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ ÑвÑжитеÑÑŒ Ñо Ñлужбой поддержки."</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Разблокировать"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Вкл. звук"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Откл. звук"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ÐБВ"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"ОчиÑтить"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Разрешает приложению Ñчитывать вÑе URL, поÑещенные браузером, и вÑе его закладки."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"запиÑывать иÑторию и закладки браузера"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Разрешает приложению изменÑÑ‚ÑŒ иÑторию и закладки браузера, Ñохраненные в вашем телефоне. ВредоноÑное ПО может пользоватьÑÑ Ñтим, чтобы Ñтирать или изменÑÑ‚ÑŒ данные вашего браузера."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Изменить Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð° Ð´Ð»Ñ Ð´Ð¾Ñтупа к географичеÑкому меÑтоположению"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"ПозволÑет программе изменÑÑ‚ÑŒ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ð° Ð´Ð»Ñ Ð´Ð¾Ñтупа к географичеÑкому положению. ВредоноÑные программы могут пользоватьÑÑ Ñтим Ð´Ð»Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²ÐºÐ¸ информации о меÑтоположении на некоторые Ñайты."</string>
<string name="save_password_message" msgid="767344687139195790">"Ð’Ñ‹ хотите, чтобы браузер запомнил Ñтот пароль?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Ðе ÑейчаÑ"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Запомнить"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 Ñ‡Ð°Ñ Ð½Ð°Ð·Ð°Ð´"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> ч. назад"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"ПоÑледние <xliff:g id="COUNT">%d</xliff:g> дн."</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Прошлый меÑÑц"</string>
+ <string name="older" msgid="5211975022815554840">"Пред."</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"вчера"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> дн. назад"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"нед."</string>
<string name="year" msgid="4001118221013892076">"г."</string>
<string name="years" msgid="6881577717993213522">"г."</string>
- <string name="every_weekday" msgid="8777593878457748503">"Каждый рабочий день (пн-пт)"</string>
- <string name="daily" msgid="5738949095624133403">"Ежедневно"</string>
- <string name="weekly" msgid="983428358394268344">"Еженедельно, <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"ЕжемеÑÑчно"</string>
- <string name="yearly" msgid="1519577999407493836">"Ежегодно"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Ðе удалоÑÑŒ воÑпроизвеÑти видео"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Это видео не подходит Ð´Ð»Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÐ¾Ð²Ð¾Ð³Ð¾ воÑÐ¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð½Ð° данном уÑтройÑтве."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Ðевозможно воÑпроизвеÑти видео."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Закрыть"</string>
<string name="report" msgid="4060218260984795706">"Отчет"</string>
<string name="wait" msgid="7147118217226317732">"Подождать"</string>
- <string name="debug" msgid="9103374629678531849">"Выполнить отладку"</string>
<string name="sendText" msgid="5132506121645618310">"Выберите дейÑтвие Ð´Ð»Ñ Ñ‚ÐµÐºÑта"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"ГромкоÑÑ‚ÑŒ звонка"</string>
<string name="volume_music" msgid="5421651157138628171">"ГромкоÑÑ‚ÑŒ мультимедиа"</string>
@@ -707,7 +755,7 @@
<string name="volume_notification" msgid="2422265656744276715">"ГромкоÑÑ‚ÑŒ уведомлениÑ"</string>
<string name="volume_unknown" msgid="1400219669770445902">"ГромкоÑÑ‚ÑŒ"</string>
<string name="ringtone_default" msgid="3789758980357696936">"ÐœÐµÐ»Ð¾Ð´Ð¸Ñ Ð¿Ð¾ умолчанию"</string>
- <string name="ringtone_default_with_actual" msgid="8129563480895990372">"ÐœÐµÐ»Ð¾Ð´Ð¸Ñ Ð¿Ð¾ умолчанию (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default_with_actual" msgid="8129563480895990372">"По умолчанию (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="4440324407807468713">"Без звука"</string>
<string name="ringtone_picker_title" msgid="3515143939175119094">"Мелодии"</string>
<string name="ringtone_unknown" msgid="5477919988701784788">"ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¼ÐµÐ»Ð¾Ð´Ð¸Ñ"</string>
@@ -730,22 +778,24 @@
<string name="no_permissions" msgid="7283357728219338112">"Ðе требуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ð¹"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Скрыть"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Показать вÑе"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Идет загрузка…"</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"Запоминающее уÑтройÑтво USB"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"уÑтройÑтво USB подключено"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Телефон подключен к компьютеру через порт USB. ЕÑли необходимо копировать файлы Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° на SD-карту телефона (или наоборот), выберите \"УÑтановить\"."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Смонтировать"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Ðе монтировать"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Телефон подключен к компьютеру через порт USB. Ðажмите кнопку ниже, еÑли необходимо копировать файлы Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° на SD-карту уÑтройÑтва Android (или наоборот)."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Включить USB-накопитель"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"При иÑпользовании SD-карты как USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð° неполадка."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"уÑтройÑтво USB подключено"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Выберите копирование файлов на компьютер или Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Выключить USB-накопитель"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Выберите, чтобы выключить USB-накопитель."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Выключить USB-накопитель"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Перед выключением USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ Ð¾Ð±Ñзательно отключите USB-хоÑÑ‚. Выберите \"Выключить\", чтобы выключить USB-накопитель."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Выключить"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Отмена"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"При выключении USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° неполадка. УбедитеÑÑŒ, что USB-хоÑÑ‚ отключен, и повторите попытку."</string>
- <string name="extmedia_format_title" msgid="8663247929551095854">"Форматировать карту SD"</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-накопитель иÑпользуетÑÑ"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Перед отключением USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ ÑƒÐ±ÐµÐ´Ð¸Ñ‚ÐµÑÑŒ, что SD-карта уÑтройÑтва Android была отключена от компьютера."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Выключить USB-накопитель"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"При выключении USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° неполадка. УбедитеÑÑŒ, что USB-хоÑÑ‚ отключен, и повторите попытку."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Включение USB-накопителÑ"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"При включении USB-Ð½Ð°ÐºÐ¾Ð¿Ð¸Ñ‚ÐµÐ»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ðµ иÑпользуемые Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ прекратить работу и оÑтаватьÑÑ Ð½ÐµÐ´Ð¾Ñтупными до Ð¾Ñ‚ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ USB-накопителÑ."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"Сбой операции USB-подключениÑ"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"ОК"</string>
+ <string name="extmedia_format_title" msgid="8663247929551095854">"ОчиÑтить SD-карту"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Отформатировать карту SD? Ð’Ñе данные, находÑщиеÑÑ Ð½Ð° карте, будут уничтожены."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Формат"</string>
<string name="adb_active_notification_title" msgid="6729044778949189918">"Отладка по USB разрешена"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"ПодходÑщих дейÑтвий не найдено"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"обновлÑÑ‚ÑŒ ÑтатиÑтику иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð¾Ð²"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"ПозволÑет изменÑÑ‚ÑŒ Ñобранную ÑтатиÑтику иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÐºÐ¾Ð¼Ð¿Ð¾Ð½ÐµÐ½Ñ‚Ð¾Ð². Ðе предназначено Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¼Ð¸ приложениÑми."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"ПозволÑет вызывать Ñлужбу контейнера Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÑодержаниÑ. Ðе предназначена Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… программ."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"ПозволÑет вызывать Ñлужбу контейнера Ð´Ð»Ñ ÐºÐ¾Ð¿Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ ÑодержаниÑ. Ðе предназначена Ð´Ð»Ñ Ð¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ñ… программ."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Ðажмите дважды Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ð°Ñштаба"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Ошибка при наполнении виджета"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Выбрать"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Создать контакт"\n"Ñ Ð½Ð¾Ð¼ÐµÑ€Ð¾Ð¼ <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"отмечено"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"не проверено"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"ПеречиÑленные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð°ÑˆÐ¸Ð²Ð°ÑŽÑ‚ разрешение на доÑтуп к региÑтрационном данным аккаунта <xliff:g id="ACCOUNT">%1$s</xliff:g> из <xliff:g id="APPLICATION">%2$s</xliff:g>. Разрешить доÑтуп? ЕÑли да, ответ будет Ñохранен и Ñто Ñообщение больше не будет выводитьÑÑ."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"ПеречиÑленные Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ñ€Ð°ÑˆÐ¸Ð²Ð°ÑŽÑ‚ разрешение на доÑтуп к региÑтрационном данным <xliff:g id="TYPE">%1$s</xliff:g> аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g> из <xliff:g id="APPLICATION">%3$s</xliff:g>. Разрешить доÑтуп? ЕÑли да, ответ будет Ñохранен и Ñто Ñообщение больше не будет выводитьÑÑ."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Одна или неÑколько программ требуют Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к вашему аккаунту ÑÐµÐ¹Ñ‡Ð°Ñ Ð¸ в дальнейшем."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Разрешить Ñтот запроÑ?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ñтупа"</string>
<string name="allow" msgid="7225948811296386551">"Разрешить"</string>
<string name="deny" msgid="2081879885755434506">"Отклонить"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Разрешение запрошено"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Протокол L2TP"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на оÑнове предв. общ. ключа)"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на оÑнове Ñертификата)"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
+ <string name="reset" msgid="2448168080964209908">"СброÑить"</string>
+ <string name="submit" msgid="1602335572089911941">"Отправить"</string>
+ <string name="description_star" msgid="2654319874908576133">"избранное"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Режим громкой ÑвÑзи включен"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Выберите Ð´Ð»Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð° из режима громкой ÑвÑзи."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Подключение активно"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Ðажмите Ð´Ð»Ñ Ð½Ð°Ñтройки"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ð° данных"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Ðажмите, чтобы узнать больше о мобильной передаче данных"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Превышен лимит на мобильные данные"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Ðажмите, чтобы узнать больше о мобильной передаче данных"</string>
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 7028351..2fefa21 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Begränsad åtkomst har ändrats"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Datatjänsten är blockerad."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Räddningstjänsten är blockerad."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Tjänsten röst/SMS är blockerad."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Alla röst-/SMS-tjänster har blockerats."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Rösttjänsten är blockerad."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Alla rösttjänster är blockerade."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMS-tjänsten är blockerad."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Röst- och datatjänster är blockerade."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Röst- och SMS-tjänster är blockerade."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Alla röst-, data- och SMS-tjänster är blockerade."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Röst"</string>
<string name="serviceClassData" msgid="872456782077937893">"Data"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAX"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Stäng av"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Avslutar…"</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Din telefon stängs av."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"Senaste"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Inga nya program."</string>
<string name="global_actions" msgid="2406416831541615258">"Telefonalternativ"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Skärmlås"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Tillåter att ett program aktiverar felsökning för ett annat program. Skadliga program kan använda detta för att avsluta andra program."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"ändra dina gränssnittsinställningar"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Tillåter att ett program ändrar den aktuella konfigurationen, till exempel språk eller övergripande teckenformat."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"starta om andra program"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Tillåter att ett program framtvingar omstart av andra program."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"aktivera trafikläge"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Tillåter att ett program aktiverar trafikläge."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"avbryt bakgrundsprocesser"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Tillåter att ett program avslutar bakgrundsprocesser för andra program även om det inte finns för lite ledigt minne."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"framtvinga avslutning av andra program"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Tillåter att ett program framtvingar avslutning av andra program."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"tvinga program att avsluta"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Tillåter att ett program tvingar en aktivitet som finns i förgrunden att avsluta och gå tillbaka. Behövs inte för vanliga program."</string>
<string name="permlab_dump" msgid="1681799862438954752">"hämta systemets interna status"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Tillåter att samlad batteristatistik ändras. Används inte av vanliga program."</string>
<string name="permlab_backup" msgid="470013022865453920">"kontrollera säkerhetskopiering och återställning av systemet"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Tillåter att programmet styr över systemets mekanism för säkerhetskopiering och återställning. Används inte av vanliga program."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"Säkerhetskopiera och återställ programmets data"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Tillåter att programmet deltar i systemets mekanism för säkerhetskopiering och återställning."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"visa otillåtna fönster"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Tillåter att fönster skapas och används av det interna systemgränssnittet. Används inte av vanliga program."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"visa varningar på systemnivå"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en inmatningsmetod. Ska inte behövas för vanliga program."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"binda till en bakgrund"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Innehavaren tillåts att binda till den översta nivåns gränssnitt för en bakgrund. Ska inte behövas för vanliga program."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"arbeta med en enhetsadministratör"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Tillåter att innehavaren skickar avsikter till en enhetsadministratör. Vanliga program behöver aldrig göra detta."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"ändra bildskärmens rikting"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Tillåter att ett program när som helst ändrar skärmens rotering. Behövs inte för vanliga program."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"skicka Linux-signaler till program"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Tillåter att ett program installerar nya eller uppdaterade Android-paket. Skadliga program kan använda detta för att lägga till nya program med godtyckliga och starka behörigheter."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"ta bort cacheinformation för alla program"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Tillåter att ett program frigör lagringsutrymme i telefonen genom att ta bort filer i programmets katalog för cachelagring. Åtkomst är mycket begränsad, vanligtvis till systemprocesser."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Flytta programresurser"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Tillåter att ett program flyttar programresurser från interna till externa medier och tvärt om."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"läsa systemets loggfiler"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Tillåter att ett program läser från systemets olika loggfiler. Det innebär att programmet kan upptäcka allmän information om vad du gör med telefonen, men den bör inte innehålla personlig eller privat information."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"läsa/skriva till resurser som ägs av diag"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillåter att ett program ändrar information om telefonens ägare som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra ägaruppgifter."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"läsa information om ägare"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillåter att ett program läser information om telefonens ägare som har lagrats på telefonen. Skadliga program kan använda detta för att läsa telefonens ägaruppgifter."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"läsa kalenderinformation"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"läsa kalenderhändelser"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillåter att ett program läser alla händelser i kalendern som har lagrats på din telefon. Skadliga program kan använda detta för att skicka din kalender till andra personer."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"skriva kalenderdata"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Tillåter att ett program ändrar kalenderuppgifterna som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra kalenderuppgifter."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"lägg till och ändra kalenderhändelser och skicka e-post till gäster"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Tillåter att ett program lägger till och ändrar händelser i din kalender, t.ex. genom att skicka e-post till gäster. Skadliga program kan använda detta för att radera eller ändra dina kalenderhändelser och för att skicka e-post till gäster."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"skenplatser för att testa"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Skapa skenplatser för att testa. Skadliga program kan använda detta för att åsidosätta platsen och/eller statusen som returneras av riktiga platser, till exempel GPS- eller nätverksleverantörer."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"få åtkomst till extra kommandon för platsleverantör"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Tillåter att programmet monterar och demonterar filsystem för flyttbara lagringsmedia."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"formatera extern lagring"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Tillåter att programmet formaterar flyttbara lagringsmedia."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"få information om säker lagring"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Tillåter att programmet får information om säker lagring."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"skapa säker lagring"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Tillåter att programmet skapar säker lagring."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"förstör säker lagring"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Tillåter att programmet förstör säker lagring."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"montera/avmontera säker lagring"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Tillåter att programmet monterar/avmonterar säker lagring."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"byt namn på säker lagring"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Tillåter att programmet byter namn på säker lagring."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"kontrollera vibration"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Tillåter att programmet styr vibratorn."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"styra lampa"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Tillåter att programmet ger tips om systemets bakgrundsstorlek."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"återställa systemets fabriksinställningar"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Tillåter att ett program helt återställer systemets fabriksinställningar. Alla data, inställningar och installerade program raderas."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"ange tid"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Tillåter att ett program ändrar telefonens tid."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"ange tidszon"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Tillåter att ett program ändrar telefonens tidszon."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"fungera som AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"skriva inställningar för åtkomstpunktens namn"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Tillåter att ett program ändrar APN-inställningarna, till exempel Proxy och Port för alla APN."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"ändra nätverksanslutning"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Tillåter att ett program ändrar statusens nätverksanslutning."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Tillåter att ett program ändrar statusens för en kopplad nätverksanslutning."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"ändra sammanlänkad anslutning"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Tillåter att ett program ändrar statusens för en sammanlänkad nätverksanslutning."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"ändra inställningar för användning av bakgrundsdata"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Tillåter att ett program ändrar inställningen för användning av bakgrundsdata."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"visa Wi-Fi-status"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Tillåter att ett program skriver in nya ord i användarordlistan."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"ändra/ta bort innehåll på SD-kortet"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Tillåter att ett program skriver till SD-kortet."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"Ã¥tkomst till cachefilsystemet"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Tillåter att ett program läser och skriver till cachefilsystemet."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Begränsa lösenord"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Begränsar vilka typer av lösenord som får användas."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Visa inloggningsförsök"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Övervaka misslyckade inloggningsförsök på enheten för att utföra åtgärder."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Återställ lösenord"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Framtvinga ett nytt värde för ditt lösenord. Kräver att administratören tillhandahåller det innan du kan logga in."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Framtvinga låsning"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollera när enheten låses, vilket kräver att du anger lösenordet igen."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Radera alla data"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Återställ fabriksinställningarna och ta bort alla data utan någon bekräftelse."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hem"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ange lösenord för att låsa upp"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Fel PIN-kod!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Tryck på Menu om du vill låsa upp."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Rita grafiskt lösenord för att låsa upp"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Nödsamtal"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Återgå till samtalet"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Försök igen"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Laddar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"LÃ¥s upp"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Ljud på"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Ljud av"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ta bort"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Tillåter att program läser alla webbadresser som webbläsaren har öppnat och alla webbläsarens bokmärken."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"skriva webbläsarhistorik och bokmärken"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Tillåter att ett program ändrar webbläsarhistoriken och bokmärkena i din telefon. Skadliga program kan använda detta för att ta bort eller ändra data i webbläsaren."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Ändra geografisk plats för webbläsaren"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Tillåter att ett program ändrar webbläsarens behörigheter för geografisk plats. Skadliga program kan använda detta för att tillåta att platsinformation skickas till godtyckliga webbplatser."</string>
<string name="save_password_message" msgid="767344687139195790">"Vill du att webbläsaren ska komma ihåg lösenordet?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Inte nu"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Kom ihåg"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"för 1 timme sedan"</item>
<item quantity="other" msgid="2467273239587587569">"för <xliff:g id="COUNT">%d</xliff:g> timmar sedan"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"De senaste <xliff:g id="COUNT">%d</xliff:g> dagarna"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Föregående månad"</string>
+ <string name="older" msgid="5211975022815554840">"Äldre"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"igår"</item>
<item quantity="other" msgid="2479586466153314633">"för <xliff:g id="COUNT">%d</xliff:g> dagar sedan"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"veckor"</string>
<string name="year" msgid="4001118221013892076">"Ã¥r"</string>
<string name="years" msgid="6881577717993213522">"Ã¥r"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Alla vardagar (mån–fre)"</string>
- <string name="daily" msgid="5738949095624133403">"Varje dag"</string>
- <string name="weekly" msgid="983428358394268344">"Varje vecka på <xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"Varje månad"</string>
- <string name="yearly" msgid="1519577999407493836">"Varje år"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Det går inte att spela upp videon"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Videon kan tyvärr inte spelas upp i den här enheten."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Det går tyvärr inte att spela upp den här videon."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Tvinga fram en stängning"</string>
<string name="report" msgid="4060218260984795706">"Rapportera"</string>
<string name="wait" msgid="7147118217226317732">"Vänta"</string>
- <string name="debug" msgid="9103374629678531849">"Felsökning"</string>
<string name="sendText" msgid="5132506121645618310">"Välj en åtgärd för text"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ringvolym"</string>
<string name="volume_music" msgid="5421651157138628171">"Mediavolym"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Inga behörigheter krävs"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Dölj"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Visa alla"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Läser in…"</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB-masslagring"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB-ansluten"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Du har anslutit telefonen till datorn via USB. Välj Montera om du vill kopiera filer mellan datorn och telefonens SD-kort."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"Montera"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"Montera inte"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Du har anslutit telefonen till datorn via USB. Välj knappen nedan om du vill kopiera filer mellan datorn och SD-kortet i din Android."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"Aktivera USB-lagring"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"Det gick inte att använda ditt SD-kort för USB-lagring."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-ansluten"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Välj om du vill kopiera filer till/från din dator."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Inaktivera USB-lagring"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Välj om USB-lagring ska inaktiveras."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"Inaktivera USB-lagring"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"Innan du inaktiverar USB-lagring måste du kontrollera att du har demonterat USB-värden. Välj Inaktivera om du vill inaktivera USB-lagring."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Inaktivera"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Avbryt"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"Ett problem uppstod när USB-lagringsplatsen skulle inaktiveras. Kontrollera att USB-värden har demonterats och försök igen."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB-lagret används"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"Kontrollera att du har demonterat (\"matat ut\") Android-telefonens SD-kort från datorn, innan du inaktiverar USB-lagring."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"Inaktivera USB-lagring"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"Ett problem uppstod när USB-lagringsplatsen skulle inaktiveras. Kontrollera att USB-värden har demonterats och försök igen."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Aktivera USB-lagring"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"Om du aktiverar USB-lagring avbryts några av de program som körs och de kanske inte blir tillgängliga igen förrän du inaktiverar USB-lagring."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB-åtgärd misslyckades"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"Formatera SD-kort"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"Vill du formatera SD-kortet? Alla data på ditt kort kommer att gå förlorade."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Format"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Inga matchande aktiviteter hittades"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"uppdatera statistik över användning av komponenter"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Tillåter att samlad komponentstatistik ändras. Används inte av vanliga program."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Tillåter att innehåll kopieras genom att standardbehållartjänsten startas. Vanliga program behöver aldrig göra detta."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Tillåter att innehåll kopieras genom att standardbehållartjänsten startas. Vanliga program behöver aldrig göra detta."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Peka två gånger för zoomkontroll"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Fel när widgeten expanderades"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Kör"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"Skapa kontakt"\n"med <xliff:g id="NUMBER">%s</xliff:g>"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"markerad"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"inte markerad"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Programmen begär åtkomst till inloggningsuppgifterna för kontot <xliff:g id="ACCOUNT">%1$s</xliff:g> från <xliff:g id="APPLICATION">%2$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Programmen begär åtkomst till inloggningsuppgifterna <xliff:g id="TYPE">%1$s</xliff:g> för kontot <xliff:g id="ACCOUNT">%2$s</xliff:g> från <xliff:g id="APPLICATION">%3$s</xliff:g>. Vill du bevilja behörighet? Om du gör det kommer vi ihåg det och du blir inte tillfrågad igen."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Följande program begär behörighet till konto, både nu och i framtiden."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Vill du tillåta den här begäran?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"Begäran om åtkomst"</string>
<string name="allow" msgid="7225948811296386551">"Tillåt"</string>
<string name="deny" msgid="2081879885755434506">"Neka"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Begärd behörighet"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer 2 Tunneling Protocol"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"I förväg delad L2TP/IPSec VPN-nyckel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatsbaserad L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
+ <string name="reset" msgid="2448168080964209908">"Återställ"</string>
+ <string name="submit" msgid="1602335572089911941">"Skicka"</string>
+ <string name="description_star" msgid="2654319874908576133">"favorit"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Billäge aktiverat"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Välj om du vill avsluta billäge."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Aktiv sammanlänkning"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Tryck om du vill konfigurera"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hög mobildataanvändning"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryck om du vill veta mer om mobildataanvändning"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Gränsen för mobildata har överskridits"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Tryck om du vill veta mer om mobildataanvändning"</string>
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fa4764e..7066cb0 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"Kısıtlanmış erişim değiştirildi"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"Veri hizmeti engellendi."</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"Acil durum hizmeti engellendi."</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"Ses/SMS hizmeti engellendi."</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"Tüm ses/SMS hizmetleri engellendi."</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"Ses hizmeti engellendi."</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"Tüm Ses hizmetleri engellendi."</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"SMS hizmeti engellendi."</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"Ses/Veri hizmetleri engellendi."</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"Ses/SMS hizmetleri engellendi."</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"Tüm Ses/Veri/SMS hizmetleri engellendi."</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"Ses"</string>
<string name="serviceClassData" msgid="872456782077937893">"Veri"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"FAKS"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"Kapat"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"Kapanıyor…"</string>
<string name="shutdown_confirm" msgid="649792175242821353">"Telefonunuz kapanacak."</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"En Son Görevler"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"Hiçbir yeni uygulama yok."</string>
<string name="global_actions" msgid="2406416831541615258">"Telefon seçenekleri"</string>
<string name="global_action_lock" msgid="2844945191792119712">"Ekran kilidi"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"Bir uygulamanın başka bir uygulama için hata ayıklamayı çalıştırmasına izin verir. Kötü amaçlı uygulamalar bu işlevi başka uygulamaları kapatmak için kullanabilir."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"kullanıcı arayüzü ayarlarınızı değiştirin"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"Uygulamaların güncel yapılandırmayı; örneğin yerel ayarı veya genel yazı tipi boyutunu değiştirmesine izin verir."</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"diğer uygulamaları yeniden başlat"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"Uygulamaların başka uygulamaları zorla yeniden başlatmasına izin verir."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"araç modunu etkinleştir"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"Bir uygulamanın araç modunu etkinleştirmesine izin verir."</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"arka plan iÅŸlemleri son erdir"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"Bellek düşük olmasa dahi, bir uygulamanın diğer uygulamaların arka plan işlemlerini sona erdirmesine izin verir."</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"diğer uygulamaları durmaya zorla"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"Bir uygulamanın başka uygulamaları zorla durdurmasına izin verir."</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"uygulamayı kapanmaya zorla"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"Uygulamaların, ön plandaki herhangi bir etkinliği kapanmaya ve arka plana geçmeye zorlamasına izin verir. Normal uygulamalarda hiçbir zaman gerekmemelidir."</string>
<string name="permlab_dump" msgid="1681799862438954752">"sistemin dahili durumunu al"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"Toplanan pil istatistiklerinin değiştirilmesine izin verir. Normal uygulamalarda kullanılmamalıdır."</string>
<string name="permlab_backup" msgid="470013022865453920">"sistem yedeğini kontrol et ve geri yükle"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"Uygulamaya sistem yedekleme ve geri yükleme mekanizmasını denetleme izni verir. Normal uygulamalar tarafından kullanım için değildir."</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"uygulamanın verilerini yedekle ve geri yükle"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"Uygulamaya, sistem yedekleme ve geri yükleme mekanizmasına katılma izni verir."</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"yetkisiz pencereleri görüntüle"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"Dahili sistem kullanıcı arayüzü tarafından kullanılmak üzere tasarlanmış pencerelerin oluşturulmasına izin verir. Normal uygulamalarda kullanılmaz."</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"sistem düzeyi uyarıları görüntüle"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Tutucunun bir giriş yönteminin en üst düzey arayüzüne bağlanmasına izin verir. Normal uygulamalarda hiçbir zaman gerek duyulmamalıdır."</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"bir duvar kağıdına tabi kıl"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Hesap sahibine bir duvar kağıdının en üst düzey arayüzüne bağlanma izni verir. Normal uygulamalarda hiçbir zaman gerek duyulmamalıdır."</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"bir cihaz yöneticisi ile etkileşimde bulun"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Cihazın sahibinin cihaz yöneticisine amaç göndermesine izin verir. Normal uygulamalarda hiçbir zaman gerek duyulmamalıdır."</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"ekran yönünü değiştir"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"Uygulamaların ekran yönünü istedikleri zaman değiştirmesine izin verir. Normal uygulamalarda hiçbir zaman gerekmemelidir."</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"uygulamalara Linux sinyalleri gönder"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"Uygulamaların yeni veya güncellenmiş Android paketleri yüklemesine izin verir. Kötü amaçlı uygulamalar bunu, kendilerine verilen izin derecesi keyfi olarak değişen yeni uygulamalar eklemek için kullanabilir."</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"tüm uygulama önbelleği verilerini sil"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"Uygulamaların uygulama önbelleği dizinindeki dosyaları silerek telefonda yer açmasına izin verir. Erişim genellikle sistem işlemlerine ve yüksek düzeyde kısıtlı olarak verilir."</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"Uygulama kaynaklarını taşı"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"Bir uygulamanın, uygulama kaynaklarını dahili ve harici ortamlar arasında taşımasına olanak tanır."</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"sistem günlük dosyalarını oku"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"Uygulamaların sistemin çeşitli günlük dosyalarından okumalarına izin verir. Bu, uygulamaların telefon ile neler yaptığınız ile ilgili genel bilgi bulmasına izin verir, ancak bunlar kişisel veya özel bir bilgi içermemelidir."</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"sahibi tanılama olan kaynakları oku/bunlara yaz"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kullanıcı verilerini silmek veya değiştirmek için kullanabilir."</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"sahip verilerini oku"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini okumasına izin verir. Kötü amaçlı uygulamalar bunu telefon sahibi verilerini okumak için kullanabilir."</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"takvim verilerini oku"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"takvim etkinliklerini oku"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"Uygulamaların telefonunuzda depolanan takvim etkinliklerinin tümünü okumasına izin verir. Kötü amaçlı uygulamalar bunu, takvim etkinliklerinizi başkalarına göndermek için kullanabilir."</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"takvim verilerini yaz"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"Uygulamaların telefonunuzda depolanan takvim etkinliklerini değiştirmesine izin verir. Kötü amaçlı uygulamaları bunu takvim verilerinizi silmek veya değiştirmek için kullanabilir."</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"takvim etkinlikleri ekle veya değiştir ve konuklara e-posta gönder"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"Bir uygulamanın takviminize etkinlik ekleyebilmesini veya takviminizdeki etkinlikleri değiştirebilmesini sağlar ve konuklara e-posta gönderebilir. Kötü amaçlı uygulamalar, bunu takvim etkinliklerinizi silmek veya değiştirmek veya konuklara e-posta göndermek için kullanabilir."</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"test için sahte konum kaynakları"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"Test amacıyla sahte konum kaynakları oluşturur. Kötü amaçlı uygulamalar bu işlevi GPS veya Ağ Hizmeti sağlayıcılar gibi gerçek kaynaklardan gelen konum ve/veya durum bilgilerini geçersiz kılmak için kullanabilir."</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"ek konum sağlayıcı komutlarına eriş"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"Uygulamaların çıkarılabilir depolama birimleri için dosya sistemleri ile bağlantı kurmasına ve bağlantıyı kesmesine izin verir."</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"harici depolama birimini biçimlendir"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"Uygulamanın çıkarılabilir depolama birimini biçimlendirmesine izin verir."</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"güvenli depolama birimi hakkında bilgi al"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"Uygulamanın güvenli depolama birimi hakkında bilgi almasına izin verir."</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"güvenli depolama birimi oluştur"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"Uygulamanın güvenli depolama birimi oluşturmasına izin verir."</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"güvenli depolama birimini yok et"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"Uygulamanın güvenli depolama birimini yok etmesine izin verir."</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"güvenli depolama birimini ekle / kaldır"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"Uygulamanın güvenli depolama birimini yeniden eklemesine / kaldırmasına izin verir."</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"güvenli depolama birimini yeniden adlandır"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"Uygulamanın güvenli depolama birimini yeniden adlandırmasına izin verir."</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"titreÅŸimi denetle"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"Uygulamanın titreşimi denetlemesine izin verir."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"flaşı denetle"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Uygulamanın sistem duvar kağıdı boyutu ipuçlarını ayarlamasına izin verir."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"sistemi fabrika değerlerine sıfırla"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Uygulamanın, sistemi tamamen fabrika ayarlarına sıfırlamasına; verileri, yapılandırmayı ve yüklü uygulamaları silmesine izin verir."</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"saati ayarla"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"Bu uygulamanın telefonun saatini değiştirmesine izin verir."</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"saat dilimini ayarla"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"Uygulamaların telefonun saat dilimini değiştirmesine izin verir."</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"Hesap Yönetici Hizmeti gibi davran"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"Erişim Noktası Adı ayarlarını yaz"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"Uygulamaların herhangi bir APN\'nin Proxy ve Bağlantı Noktası gibi APN ayarlarını değiştirmesine izin verir."</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"ağ bağlantısını değiştir"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"Uygulamaların ağ bağlantı durumunu değiştirmesine izin verir."</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"Bir uygulamanın ağ bağlantı durumunu değiştirmesine izin verir."</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"Kullanılan bağlantıyı değiştir"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"Bir uygulamanın bağlanılan ağ bağlantısı durumunu değiştirmesine izin verir."</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"arka plan veri kullanımı ayarını değiştir"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"Uygulamaların arka plan veri kullanımı ayarını değiştirmesine izin verir."</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"Kablosuz durumunu görüntüle"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"Uygulamaların kullanıcı sözlüğüne yeni kelimeler yazmasına izin verir."</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"SD kart içeriklerini değiştir/sil"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"Bir uygulamaya SD karta yazma izni verir."</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"önbellek dosya sistemine eriş"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"Bir uygulamanın önbellek dosya sisteminde okuma yazma yapmasına izin verir."</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"Şifreyi sınırla"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"Kullanmanıza izin verilen şifre türlerini sınırlayın."</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"Oturum açma denemelerini izle"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"Bir işlem gerçekleştirmek için cihazdaki başarısız oturum açma girişimlerini izleyin."</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"Şifre sıfırlama"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"Şifrenizi yeni bir değer alması için zorlayın. Giriş yapabilmeniz için yöneticinin size yeni bir değer sağlamasını gerekecektir."</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"Kilitlemeye zorlama"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"Cihaz kilitlendiÄŸinde, ÅŸifresini yeniden girmenizi gerektiren denetim."</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"Tüm verileri sil"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"Tüm verilerinizi onay olmadan silmek için fabrika ayarlarına sıfırlayın."</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Ev"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="SOURCE">%2$s</xliff:g> ile <xliff:g id="DATE">%1$s</xliff:g> tarihinde"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN kodunu gir"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Kilidi açmak için şifreyi girin"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Yanlış PIN kodu!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Acil durum numarası"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Kilidi açmak için Menü\'ye basın."</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Kilit açmak için deseni çizin"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"Acil durum çağrısı"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Çağrıya dön"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"DoÄŸru!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Üzgünüz, lütfen yeniden deneyin"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Åžarj oluyor (<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>)"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"Kilit Aç"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"Sesi aç"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"Sesi kapat"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"Temizle"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"Uygulamaya Tarayıcının ziyaret etmiş olduğu tüm URL\'leri ve Tarayıcının tüm favorilerini okuma izni verir."</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"Tarayıcı geçmişini ve favorileri yaz"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"Uygulamaya telefonunuzda depolanan Tarayıcı geçmişini veya favorileri değiştirme izni verir. Kötü amaçlı uygulamalar bunu Tarayıcı verilerinizi silmek veya değiştirmek için kullanabilir."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"Tarayıcı\'nın coğrafi konum izinlerini değiştir"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"Bir uygulamanın, Tarayıcı\'nın coğrafi konum izinlerini değiştirmesine izin verir. Kötü amaçlı uygulamalar, bu özelliği konum bilgilerini rastgele web sitelerine göndermek için kullanabilir."</string>
<string name="save_password_message" msgid="767344687139195790">"Tarayıcının bu şifreyi anımsamasını istiyor musunuz?"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"Åžimdi deÄŸil"</string>
<string name="save_password_remember" msgid="6491879678996749466">"Anımsa"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 saat önce"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> saat önce"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"Son <xliff:g id="COUNT">%d</xliff:g> gün"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"Son ay"</string>
+ <string name="older" msgid="5211975022815554840">"Daha eski"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"dün"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> gün önce"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"hafta"</string>
<string name="year" msgid="4001118221013892076">"yıl"</string>
<string name="years" msgid="6881577717993213522">"yıl"</string>
- <string name="every_weekday" msgid="8777593878457748503">"Hafta içi her gün (Pzt-Cum)"</string>
- <string name="daily" msgid="5738949095624133403">"Her gün"</string>
- <string name="weekly" msgid="983428358394268344">"Her hafta <xliff:g id="DAY">%s</xliff:g> günü"</string>
- <string name="monthly" msgid="2667202947170988834">"Aylık"</string>
- <string name="yearly" msgid="1519577999407493836">"Yılda bir"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"Video oynatılamıyor"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"Maalesef, bu video cihaza akışla göndermek için uygun değil."</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"Maalesef bu video oynatılamıyor."</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string>
<string name="report" msgid="4060218260984795706">"Rapor"</string>
<string name="wait" msgid="7147118217226317732">"Bekle"</string>
- <string name="debug" msgid="9103374629678531849">"Hata ayıkla"</string>
<string name="sendText" msgid="5132506121645618310">"Metin için bir işlem seçin"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Zil sesi düzeyi"</string>
<string name="volume_music" msgid="5421651157138628171">"Medya ses düzeyi"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"Ä°zin gerektirmez"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"Gizle"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"Tümünü göster"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"Yükleniyor…"</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB Yığın Depolama"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB bağlandı"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"Telefonunuzu bilgisayarınıza USB ile bağladınız. Bilgisayarınız ve telefonunuzun SD kartı arasında dosya kopyalamak istiyorsanız, \"Bağla\"\'yı seçin."</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"BaÄŸla"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"BaÄŸlama"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"Telefonunuzu USB ile bilgisayarınıza bağladınız. Bilgisayarınız ile Android\'inizin SD kartı arasında dosya kopyalamak istiyorsanız aşağıdaki düğmeyi seçin."</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"USB depolama birimini aç"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"SD kartınızı USB depolama birimi için kullanmada bir sorun var."</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB bağlandı"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"Bilgisayarınıza/bilgisayarınızdan dosya kopyalamak için seçin."</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"USB depolama birimini kapat"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"USB depolama birimini kapatmak için seçin."</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"USB depolama birimini kapat"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"USB depolama birimini kapatmadan önce USB ana makinesinde bağlantıyı kestiğinizden emin olun. USB depolama birimini kapatmak için \"Kapat\"ı seçin."</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"Kapat"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"Ä°ptal"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"USB depolama birimini kapatırken bir sorunla karşılaştık. USB ana makinesinin bağlantısını kestiğinizden emin olduktan sonra tekrar deneyin."</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB depolama birimi kullanılıyor"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"USB depolama birimini kapatmadan önce Android SD kartını bilgisayarınızdan kaldırdığınızdan (\"çıkardığınızdan\") emin olun."</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"USB depolama birimini kapat"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"USB depolama birimini kapatırken bir sorun oluştu. USB ana makinesini kaldırdığınızdan emin olun ve daha sonra tekrar deneyin."</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"USB depolama birimini aç"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"USB depolama birimini açarsanız, kullanmakta olduğunuz bazı uygulamalar durur ve USB depolama birimi kapatılıncaya kadar kullanılamayabilir."</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB işlemi başarısız oldu"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"Tamam"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"SD kartı biçimlendir"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"SD kartı biçimlendirmek istediğinizden emin misiniz? Kartınızdaki tüm veriler yok olacak."</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"Biçimlendir"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"Eşleşen hiçbir etkinlik bulunamadı"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"bileşen kullanım istatistiklerini güncelle"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"Toplanmış bileşen istatistiklerinin değiştirilmesine izin verir. Normal uygulamalarda kullanılmamalıdır."</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"Varsayılan kapsayıcı hizmetin içeriği kopyalamak için çağrılmasına izin verir. Normal uygulamalar tarafından kullanılmaz."</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"Varsayılan kapsayıcı hizmetin içeriği kopyalamak için çağrılmasına izin verir. Normal uygulamalar tarafından kullanılmaz."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"Zum denetimi için iki kez dokun"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"Widget\'ı genişletirken hata oluştu"</string>
<string name="ime_action_go" msgid="8320845651737369027">"Git"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"<xliff:g id="NUMBER">%s</xliff:g>"\n" ile kiÅŸi oluÅŸtur"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"seçildi"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"seçilmedi"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%2$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%1$s</xliff:g> hesabı için giriş bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"Listelenen uygulamalar, <xliff:g id="APPLICATION">%3$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabı için <xliff:g id="TYPE">%1$s</xliff:g> girişi bilgilerine erişim izni istiyor. Bu izni vermek istiyor musunuz? İstiyorsanız yanıtınız kaydedilecek ve tekrar sorulmayacaktır."</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"Aşağıdaki bir veya daha fazla uygulama, şimdi ve ileride hesabınıza erişmek için izin istiyor."</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"Bu isteÄŸe izin vermek istiyor musunuz?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"EriÅŸim Ä°steÄŸi"</string>
<string name="allow" msgid="7225948811296386551">"Ä°zin Ver"</string>
<string name="deny" msgid="2081879885755434506">"Reddet"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"Ä°zin Ä°stendi"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Katman 2 Tünel Protokolü"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN temelli önceden paylaşılmış anahtar"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN temelli sertifika"</string>
+ <string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
+ <string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
+ <string name="submit" msgid="1602335572089911941">"Gönder"</string>
+ <string name="description_star" msgid="2654319874908576133">"favori"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Araba modu etkin"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"Araba modundan çıkmak için seçin."</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"Bağlantı etkin"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"Yapılandırmak için dokunun"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"Yüksek düzeyde mobil veri kullanımı"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"Mobil veri limiti aşıldı"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index ebb7758..455429c 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"B"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"访问å—é™æƒ…况已å‘生å˜åŒ–"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"æ•°æ®æœåŠ¡å·²ç¦ç”¨ã€‚"</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"紧急æœåŠ¡å·²ç¦ç”¨ã€‚"</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"语音/短信æœåŠ¡å·²ç¦ç”¨ã€‚"</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"所有语音/短信æœåŠ¡å‡å·²ç¦ç”¨ã€‚"</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"å·²ç¦ç”¨è¯­éŸ³æœåŠ¡ã€‚"</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"å·²ç¦ç”¨æ‰€æœ‰è¯­éŸ³æœåŠ¡ã€‚"</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"å·²ç¦ç”¨çŸ­ä¿¡æœåŠ¡ã€‚"</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"å·²ç¦ç”¨è¯­éŸ³/æ•°æ®æœåŠ¡ã€‚"</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"å·²ç¦ç”¨è¯­éŸ³/短信æœåŠ¡ã€‚"</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"å·²ç¦ç”¨æ‰€æœ‰è¯­éŸ³/æ•°æ®/短信æœåŠ¡ã€‚"</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"语音"</string>
<string name="serviceClassData" msgid="872456782077937893">"æ•°æ®"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"传真"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"关机"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"正在关机..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"您的手机会关机。"</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"近期任务"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"没有最近的应用程åºã€‚"</string>
<string name="global_actions" msgid="2406416831541615258">"手机选项"</string>
<string name="global_action_lock" msgid="2844945191792119712">"å±å¹•é”定"</string>
@@ -145,7 +155,7 @@
<string name="permgroupdesc_personalInfo" msgid="5488050357388806068">"直接访问手机上存储的è”系人和日历。"</string>
<string name="permgrouplab_location" msgid="635149742436692049">"您的ä½ç½®"</string>
<string name="permgroupdesc_location" msgid="2430258821648348660">"监视您的物ç†ä½ç½®"</string>
- <string name="permgrouplab_network" msgid="5808983377727109831">"网络通讯"</string>
+ <string name="permgrouplab_network" msgid="5808983377727109831">"网络通信"</string>
<string name="permgroupdesc_network" msgid="5035763698958415998">"å…许应用程åºè®¿é—®å„ç§ç½‘络功能。"</string>
<string name="permgrouplab_accounts" msgid="3359646291125325519">"您的å¸æˆ·"</string>
<string name="permgroupdesc_accounts" msgid="4948732641827091312">"访问å¯ç”¨çš„å¸æˆ·ã€‚"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"å…许应用程åºå¯åŠ¨å¯¹å…¶ä»–应用程åºçš„调试。æ¶æ„应用程åºå¯å€Ÿæ­¤ç»ˆæ­¢å…¶ä»–应用程åºã€‚"</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"更改用户界é¢è®¾ç½®"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"å…许应用程åºæ›´æ”¹å½“å‰é…置,例如语言设置或整体的字体大å°ã€‚"</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"é‡æ–°å¯åŠ¨å…¶ä»–应用程åº"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"å…许应用程åºå¼ºè¡Œé‡æ–°å¯åŠ¨å…¶ä»–应用程åºã€‚"</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"å¯ç”¨è½¦è½½æ¨¡å¼"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"å…许应用程åºå¯ç”¨è½¦è½½æ¨¡å¼ã€‚"</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"结æŸåŽå°è¿›ç¨‹"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"无论内存资æºæ˜¯å¦ç´§å¼ ï¼Œéƒ½å…许应用程åºç»“æŸå…¶ä»–应用程åºçš„åŽå°è¿›ç¨‹ã€‚"</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"强行åœæ­¢å…¶ä»–应用程åº"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"å…许应用程åºå¼ºè¡Œåœæ­¢å…¶ä»–应用程åºã€‚"</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"强制应用程åºå…³é—­"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"å…许应用程åºå¼ºåˆ¶å‰ç«¯çš„任何活动关闭并é‡æ–°å¼€å§‹ã€‚普通应用程åºä»Žä¸éœ€è¦ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
<string name="permlab_dump" msgid="1681799862438954752">"检索系统内部状æ€"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"å…许修改收集的电池使用情况统计信æ¯ã€‚普通应用程åºä¸èƒ½ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
<string name="permlab_backup" msgid="470013022865453920">"控制系统备份和还原"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"å…许应用程åºæŽ§åˆ¶ç³»ç»Ÿçš„备份和还原机制。普通应用程åºä¸èƒ½ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"备份与还原应用程åºæ•°æ®"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"å…许应用程åºå‚与系统的备份和还原机制。"</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"显示未授æƒçš„窗å£"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"å…许创建专用于内部系统用户界é¢çš„窗å£ã€‚普通应用程åºä¸èƒ½ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"显示系统级警报"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"å…许手机用户绑定至输入法的顶级界é¢ã€‚普通应用程åºä»Žä¸éœ€è¦ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"绑定到å£çº¸"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"å…许手机用户绑定到å£çº¸çš„顶级界é¢ã€‚应该从ä¸éœ€è¦å°†æ­¤æƒé™æŽˆäºˆæ™®é€šåº”用程åºã€‚"</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"与设备管ç†å™¨äº¤äº’"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"å…许æŒæœ‰å¯¹è±¡å°†æ„å‘å‘é€åˆ°è®¾å¤‡ç®¡ç†å™¨ã€‚普通的应用程åºä¸€å¾‹æ— éœ€æ­¤æƒé™ã€‚"</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"更改å±å¹•æ˜¾ç¤ºæ–¹å‘"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"å…许应用程åºéšæ—¶æ›´æ”¹å±å¹•çš„旋转方å‘。普通应用程åºä»Žä¸éœ€è¦ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"å‘应用程åºå‘é€ Linux ä¿¡å·"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"å…许应用程åºå®‰è£…全新的或更新的 Android 包。æ¶æ„应用程åºå¯èƒ½ä¼šå€Ÿæ­¤æ·»åŠ å…¶å…·æœ‰ä»»æ„æƒé™çš„新应用程åºã€‚"</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"删除所有应用程åºç¼“存数æ®"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"å…许应用程åºé€šè¿‡åˆ é™¤åº”用程åºç¼“存目录中的文件释放手机存储空间。通常此æƒé™åªé€‚用于系统进程。"</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"移动应用程åºèµ„æº"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"å…许应用程åºåœ¨å†…部介质和外部介质之间移动应用程åºèµ„æºã€‚"</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"读å–系统日志文件"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"å…许应用程åºä»Žç³»ç»Ÿçš„å„日志文件中读å–ä¿¡æ¯ã€‚这样应用程åºå¯ä»¥å‘现您的手机使用情况,但这些信æ¯ä¸åº”包å«ä»»ä½•ä¸ªäººä¿¡æ¯æˆ–ä¿å¯†ä¿¡æ¯ã€‚"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"读å–/写入诊断所拥有的资æº"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"å…许应用程åºä¿®æ”¹æ‚¨æ‰‹æœºä¸Šå­˜å‚¨çš„手机所有者数æ®ã€‚æ¶æ„应用程åºå¯å€Ÿæ­¤æ¸…除或修改所有者数æ®ã€‚"</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"读å–所有者数æ®"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"å…许应用程åºè¯»å–您手机上存储的手机所有者数æ®ã€‚æ¶æ„应用程åºå¯å€Ÿæ­¤è¯»å–手机所有者数æ®ã€‚"</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"读å–日历数æ®"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"读å–日历活动"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"å…许应用程åºè¯»å–您手机上存储的所有日历活动。æ¶æ„应用程åºå¯å€Ÿæ­¤å°†æ‚¨çš„日历活动å‘é€ç»™å…¶ä»–人。"</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"写入日历数æ®"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"å…许应用程åºä¿®æ”¹æ‚¨æ‰‹æœºä¸Šå­˜å‚¨çš„日历活动。æ¶æ„应用程åºå¯å€Ÿæ­¤æ¸…除或修改您的日历数æ®ã€‚"</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"添加或修改日历活动以åŠå‘邀请对象å‘é€ç”µå­é‚®ä»¶"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"å…许应用程åºæ·»åŠ æˆ–更改日历中的活动,这å¯èƒ½ä¼šå‘邀请对象å‘é€ç”µå­é‚®ä»¶ã€‚æ¶æ„应用程åºå¯èƒ½ä¼šå€Ÿæ­¤æ¸…除或修改您的日历活动,或者å‘邀请对象å‘é€ç”µå­é‚®ä»¶ã€‚"</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"使用模拟地点æ¥æºè¿›è¡Œæµ‹è¯•"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"创建模拟地点æ¥æºè¿›è¡Œæµ‹è¯•ã€‚æ¶æ„应用程åºå¯èƒ½åˆ©ç”¨æ­¤é€‰é¡¹è¦†ç›–由真实地点æ¥æºï¼ˆå¦‚ GPS 或网络æ供商)传回的地点和/或状æ€ã€‚"</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"访问é¢å¤–çš„ä½ç½®ä¿¡æ¯æ供程åºå‘½ä»¤"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"å…许应用程åºè£…载和å¸è½½å¯ç§»åŠ¨å­˜å‚¨å™¨çš„文件系统。"</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"æ ¼å¼åŒ–外部存储设备"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"å…许应用程åºæ ¼å¼åŒ–å¯ç§»é™¤çš„存储设备。"</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"获å–有关安全存储的信æ¯"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"å…许应用程åºèŽ·å–有关安全存储的信æ¯ã€‚"</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"创建安全存储"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"å…许应用程åºåˆ›å»ºå®‰å…¨å­˜å‚¨ã€‚"</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"清除安全存储"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"å…许应用程åºæ¸…除安全存储。"</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"安装/å¸è½½å®‰å…¨å­˜å‚¨"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"å…许应用程åºå®‰è£…/å¸è½½å®‰å…¨å­˜å‚¨ã€‚"</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"é‡å‘½å安全存储"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"å…许应用程åºé‡å‘½å安全存储。"</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"控制振动器"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"å…许应用程åºæŽ§åˆ¶æŒ¯åŠ¨å™¨ã€‚"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"控制闪光ç¯"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"å…许应用程åºè®¾ç½®æœ‰å…³å£çº¸å¤§å°çš„æ示。"</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"将系统æ¢å¤ä¸ºå‡ºåŽ‚设置"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"å…许应用程åºå°†ç³»ç»Ÿæ¢å¤ä¸ºå‡ºåŽ‚设置,å³æ¸…除所有数æ®ã€é…置以åŠæ‰€å®‰è£…的应用程åºã€‚"</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"设置时间"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"å…许应用程åºæ›´æ”¹æ‰‹æœºçš„时间。"</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"设置时区"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"å…许应用程åºæ›´æ”¹æ‰‹æœºçš„时区。"</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"作为 AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"写入“接入点å称â€è®¾ç½®"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"å…许应用程åºä¿®æ”¹ APN 设置,例如任何 APN 的代ç†å’Œç«¯å£ã€‚"</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"更改网络连接性"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"å…许应用程åºæ›´æ”¹çŠ¶æ€ç½‘络连接性。"</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"å…许应用程åºæ›´æ”¹ç½‘络连接的状æ€ã€‚"</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"更改绑定的连接"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"å…许应用程åºæ›´æ”¹ç»‘定网络连接的状æ€ã€‚"</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"更改背景数æ®ä½¿ç”¨è®¾ç½®"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"å…许应用程åºæ›´æ”¹èƒŒæ™¯æ•°æ®ä½¿ç”¨è®¾ç½®ã€‚"</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"查看 Wi-Fi 状æ€"</string>
@@ -387,8 +417,20 @@
<string name="permdesc_readDictionary" msgid="1082972603576360690">"å…许应用程åºè¯»å–用户在用户è¯å…¸ä¸­å­˜å‚¨çš„ä»»æ„ç§æœ‰å­—è¯ã€å称和短语。"</string>
<string name="permlab_writeDictionary" msgid="6703109511836343341">"写入用户定义的è¯å…¸"</string>
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"å…许应用程åºå‘用户è¯å…¸ä¸­å†™å…¥æ–°è¯ã€‚"</string>
- <string name="permlab_sdcardWrite" msgid="8079403759001777291">"修改/删除 SD å¡å†…容"</string>
+ <string name="permlab_sdcardWrite" msgid="8079403759001777291">"修改/删除 SD å¡ä¸­çš„内容"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"å…许应用程åºå†™å…¥ SD å¡ã€‚"</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"访问缓存文件系统"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"å…许应用程åºè¯»å–和写入缓存文件系统。"</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"é™åˆ¶å¯†ç é€‰æ‹©"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"é™åˆ¶æ‚¨èƒ½å¤Ÿä½¿ç”¨çš„密ç ç±»åž‹ã€‚"</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"监控登录å°è¯•"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"监控对于登录设备和执行æŸé¡¹æ“作的失败å°è¯•ã€‚"</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"é‡ç½®å¯†ç "</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"强行更新密ç ï¼Œæ‚¨éœ€è¦èŽ·å¾—管ç†å‘˜æ供的新密ç æ‰èƒ½ç™»å½•ã€‚"</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"强行é”定"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"控制何时é”定设备,这需è¦æ‚¨é‡æ–°è¾“入密ç ã€‚"</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"清除所有数æ®"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"æ¢å¤å‡ºåŽ‚设置,这会在ä¸æ示确认的情况下删除您的所有数æ®ã€‚"</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"ä½å®…"</item>
<item msgid="869923650527136615">"手机"</item>
@@ -457,7 +499,7 @@
<string name="eventTypeAnniversary" msgid="3876779744518284000">"周年纪念"</string>
<string name="eventTypeOther" msgid="5834288791948564594">"活动"</string>
<string name="emailTypeCustom" msgid="8525960257804213846">"自定义"</string>
- <string name="emailTypeHome" msgid="449227236140433919">"ä½å®…"</string>
+ <string name="emailTypeHome" msgid="449227236140433919">"家用"</string>
<string name="emailTypeWork" msgid="3548058059601149973">"å•ä½"</string>
<string name="emailTypeOther" msgid="2923008695272639549">"其他"</string>
<string name="emailTypeMobile" msgid="119919005321166205">"手机"</string>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"通过 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"时间:<xliff:g id="DATE">%1$s</xliff:g>,方å¼ï¼š<xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN ç "</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"输入密ç è¿›è¡Œè§£é”"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN ç ä¸æ­£ç¡®ï¼"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"è¦è§£é”,请先按 MENU å†æŒ‰ 0。"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"急救或报警电è¯"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按 MENU 解é”。"</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"绘制解é”图案"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"紧急呼救"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通è¯"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正确ï¼"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,请é‡è¯•"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充电 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,7 +547,7 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"没有 SIM å¡"</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"手机中无 SIM å¡"</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"请æ’å…¥ SIM å¡"</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"ä»…é™äºŽæ€¥æ•‘或报警电è¯"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"åªèƒ½ä½¿ç”¨ç´§æ€¥å‘¼å«"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"网络已é”定"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM å¡å·²ç”¨ PUK ç é”定"</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"请å‚阅《用户指å—》或è”系客æœäººå‘˜ã€‚"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"解é”"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"打开声音"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"关闭声音"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"å…许应用程åºè¯»å–用æµè§ˆå™¨è®¿é—®è¿‡çš„所有网å€ï¼Œä»¥åŠæµè§ˆå™¨çš„所有书签。"</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"写入æµè§ˆå™¨çš„历å²è®°å½•å’Œä¹¦ç­¾"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"å…许应用程åºä¿®æ”¹å­˜å‚¨åœ¨æ‰‹æœºä¸­çš„æµè§ˆå™¨åŽ†å²è®°å½•æˆ–书签。æ¶æ„应用程åºå¯å€Ÿæ­¤æ¸…除或修改æµè§ˆå™¨æ•°æ®ã€‚"</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"修改æµè§ˆå™¨çš„地ç†ä½ç½®æƒé™"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"å…许应用程åºä¿®æ”¹æµè§ˆå™¨çš„地ç†ä½ç½®æƒé™ã€‚æ¶æ„应用程åºä¼šåˆ©ç”¨è¿™ä¸€ç‚¹å°†ä½ç½®ä¿¡æ¯å‘é€åˆ°ä»»æ„网站。"</string>
<string name="save_password_message" msgid="767344687139195790">"是å¦å¸Œæœ›æµè§ˆå™¨è®°ä½æ­¤å¯†ç ï¼Ÿ"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"æš‚ä¸ä¿å­˜"</string>
<string name="save_password_remember" msgid="6491879678996749466">"è®°ä½"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 å°æ—¶å‰"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> å°æ—¶å‰"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"过去 <xliff:g id="COUNT">%d</xliff:g> 天"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"上月"</string>
+ <string name="older" msgid="5211975022815554840">"å¾€å‰"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"昨天"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> 天å‰"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"周"</string>
<string name="year" msgid="4001118221013892076">"å¹´"</string>
<string name="years" msgid="6881577717993213522">"å¹´"</string>
- <string name="every_weekday" msgid="8777593878457748503">"æ¯ä¸ªå·¥ä½œæ—¥ï¼ˆå‘¨ä¸€è‡³å‘¨äº”)"</string>
- <string name="daily" msgid="5738949095624133403">"æ¯å¤©"</string>
- <string name="weekly" msgid="983428358394268344">"æ¯å‘¨çš„<xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"æ¯æœˆ"</string>
- <string name="yearly" msgid="1519577999407493836">"æ¯å¹´"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"无法播放视频"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"抱歉,该视频ä¸é€‚åˆåœ¨æ­¤è®¾å¤‡ä¸Šæ’­æ”¾ã€‚"</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"很抱歉,无法播放此视频。"</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
<string name="report" msgid="4060218260984795706">"报告"</string>
<string name="wait" msgid="7147118217226317732">"等待"</string>
- <string name="debug" msgid="9103374629678531849">"调试"</string>
<string name="sendText" msgid="5132506121645618310">"选择è¦å¯¹æ–‡å­—执行的æ“作"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"铃声音é‡"</string>
<string name="volume_music" msgid="5421651157138628171">"媒体音é‡"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"ä¸éœ€è¦ä»»ä½•æƒé™"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>"éšè—"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"全部显示"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"正在载入..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 大容é‡å­˜å‚¨"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB 已连接"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"已通过 USB 连接与计算机。若è¦åœ¨è®¡ç®—机和手机 SD å¡ä¹‹é—´å¤åˆ¶æ–‡ä»¶ï¼Œè¯·é€‰æ‹©â€œè£…è½½â€ã€‚"</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"装载"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"ä¸è£…è½½"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"您已通过 USB 将手机连接至计算机。如果您è¦åœ¨è®¡ç®—机和 Android 手机的 SD å¡ä¹‹é—´å¤åˆ¶æ–‡ä»¶ï¼Œè¯·ç‚¹å‡»ä¸‹é¢çš„按钮。"</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"打开 USB 存储设备"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"使用 SD å¡è¿›è¡Œ USB 存储时出现问题。"</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 已连接"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"选择将文件å¤åˆ¶åˆ°è®¡ç®—机或从计算机å¤åˆ¶åˆ°å­˜å‚¨è®¾å¤‡ã€‚"</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"关闭 USB 存储设备"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"选中以关闭 USB 存储设备。"</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"关闭 USB 存储设备"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"在关闭 USB 存储设备å‰ï¼Œè¯·ç¡®ä¿æ‚¨å·²å¸è½½äº† USB 主设备。选择“关闭â€å…³é—­ USB 存储设备。"</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"关闭"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"å–消"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"关闭 USB 存储设备时é‡åˆ°é—®é¢˜ã€‚请检查是å¦å¸è½½äº† USB 主设备,然åŽé‡è¯•ã€‚"</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"使用中的 USB 存储设备"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"在关闭 USB 存储设备å‰ï¼Œè¯·ç¡®ä¿æ‚¨å·²ä»Žè®¡ç®—机中å¸è½½ï¼ˆâ€œå¼¹å‡ºâ€ï¼‰Android 手机的 SD å¡ã€‚"</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"关闭 USB 存储设备"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"关闭 USB 存储设备时é‡åˆ°é—®é¢˜ã€‚请检查并确ä¿å·²å¸è½½äº† USB 主设备,然åŽé‡è¯•ã€‚"</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"打开 USB 存储设备"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"如果您打开了 USB 存储设备,则您当å‰ä½¿ç”¨çš„æŸäº›åº”用程åºä¼šåœæ­¢ï¼Œè€Œä¸”在您关闭 USB 存储设备å‰å¯èƒ½éƒ½æ— æ³•ä½¿ç”¨ã€‚"</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB æ“作失败"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"确定"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"æ ¼å¼åŒ– SD å¡"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"确定è¦å°† SD å¡æ ¼å¼åŒ–å—?该å¡ä¸Šçš„所有数æ®éƒ½å°†ä¸¢å¤±ã€‚"</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"æ ¼å¼åŒ–"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"找ä¸åˆ°åŒ¹é…的活动"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新组件使用情况统计"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"å…许修改收集的组件使用情况统计。普通应用程åºä¸èƒ½ä½¿ç”¨æ­¤æƒé™ã€‚"</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"å…许调用默认容器æœåŠ¡å¤åˆ¶å†…容。普通的应用程åºæ— éœ€æ­¤æƒé™ã€‚"</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"å…许调用默认容器æœåŠ¡å¤åˆ¶å†…容。普通的应用程åºæ— éœ€æ­¤æƒé™ã€‚"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"åŒå‡»å¯ä»¥è¿›è¡Œç¼©æ”¾æŽ§åˆ¶"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"放大窗å£å°éƒ¨ä»¶æ—¶å‡ºé”™"</string>
<string name="ime_action_go" msgid="8320845651737369027">"开始"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"创建电è¯å·ç ä¸º"\n"<xliff:g id="NUMBER">%s</xliff:g> çš„è”系人"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已选中"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未选中"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"所列应用程åºæ­£åœ¨è¯·æ±‚相应的æƒé™ï¼Œä»¥ä¾¿ä»Ž<xliff:g id="APPLICATION">%2$s</xliff:g>访问 <xliff:g id="ACCOUNT">%1$s</xliff:g> å¸æˆ·çš„登录凭æ®ã€‚是å¦è¦æŽˆäºˆè¿™ç§æƒé™ï¼Ÿå¦‚果授予,系统会记ä½æ‚¨æ‰€åšçš„选择,且ä¸å†æ示。"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"所列应用程åºæ­£åœ¨è¯·æ±‚相应的æƒé™ï¼Œä»¥ä¾¿ä»Ž<xliff:g id="APPLICATION">%3$s</xliff:g>访问 <xliff:g id="ACCOUNT">%2$s</xliff:g> å¸æˆ·çš„<xliff:g id="TYPE">%1$s</xliff:g>登录凭æ®ã€‚是å¦è¦æŽˆäºˆè¿™ç§æƒé™ï¼Ÿå¦‚果授予,系统会记ä½æ‚¨æ‰€åšçš„选择,且ä¸å†æ示。"</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"以下一个或多个应用程åºè¯·æ±‚获得在目å‰å’Œå°†æ¥è®¿é—®æ‚¨å¸æˆ·çš„æƒé™ã€‚"</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"您是å¦åŒæ„此请求?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"访问请求"</string>
<string name="allow" msgid="7225948811296386551">"å…许"</string>
<string name="deny" msgid="2081879885755434506">"æ‹’ç»"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"已请求æƒé™"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"第 2 层隧é“åè®®"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"基于预共享密钥的 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"基于è¯ä¹¦çš„ L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
+ <string name="reset" msgid="2448168080964209908">"é‡ç½®"</string>
+ <string name="submit" msgid="1602335572089911941">"æ交"</string>
+ <string name="description_star" msgid="2654319874908576133">"收è—"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"å·²å¯ç”¨è½¦è½½æ¨¡å¼"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"选择退出车载模å¼"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"绑定生效"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"触摸å¯è¿›è¡Œé…ç½®"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"手机数æ®ä½¿ç”¨è¿‡å¤š"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"轻触以了解有关手机数æ®ä½¿ç”¨çš„详情"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"已超出手机数æ®ä¸Šé™"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"轻触以了解有关手机数æ®ä½¿ç”¨çš„详情"</string>
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 39c081c..1424b51 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1,18 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 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.
+*/
+ -->
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="8340973892742019101">"ä½å…ƒçµ„"</string>
@@ -25,7 +30,7 @@
<string name="untitled" msgid="6071602020171759109">"(未命å)"</string>
<string name="ellipsis" msgid="7899829516048813237">"..."</string>
<string name="emptyPhoneNumber" msgid="7694063042079676517">"(沒有電話號碼)"</string>
- <string name="unknownName" msgid="2277556546742746522">"(未知的)"</string>
+ <string name="unknownName" msgid="2277556546742746522">"(ä¸æ˜Ž)"</string>
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"連線發生å•é¡Œæˆ–錯誤的 MMI 碼。"</string>
@@ -64,8 +69,12 @@
<string name="RestrictedChangedTitle" msgid="5592189398956187498">"å—é™å­˜å–已變更"</string>
<string name="RestrictedOnData" msgid="8653794784690065540">"å·²å°éŽ–資料傳輸æœå‹™ã€‚"</string>
<string name="RestrictedOnEmergency" msgid="6581163779072833665">"å·²å°éŽ–緊急æœå‹™ã€‚"</string>
- <string name="RestrictedOnNormal" msgid="2045364908281990708">"å·²å°éŽ–語音/SMS æœå‹™ã€‚"</string>
- <string name="RestrictedOnAll" msgid="4923139582141626159">"å·²å°éŽ–所有語音/SMS æœå‹™ã€‚"</string>
+ <string name="RestrictedOnNormal" msgid="4953867011389750673">"å·²å°éŽ–語音æœå‹™ã€‚"</string>
+ <string name="RestrictedOnAllVoice" msgid="1459318899842232234">"å·²å°éŽ–所有語音æœå‹™ã€‚"</string>
+ <string name="RestrictedOnSms" msgid="8314352327461638897">"å·²å°éŽ– SMS æœå‹™ã€‚"</string>
+ <string name="RestrictedOnVoiceData" msgid="8244438624660371717">"å·²å°éŽ–語音/資料æœå‹™ã€‚"</string>
+ <string name="RestrictedOnVoiceSms" msgid="1888588152792023873">"å·²å°éŽ–語音/SMS æœå‹™ã€‚"</string>
+ <string name="RestrictedOnAll" msgid="2714924667937117304">"å·²å°éŽ–所有語音/資料/SMS æœå‹™ã€‚"</string>
<string name="serviceClassVoice" msgid="1258393812335258019">"語音"</string>
<string name="serviceClassData" msgid="872456782077937893">"資料"</string>
<string name="serviceClassFAX" msgid="5566624998840486475">"傳真"</string>
@@ -125,6 +134,7 @@
<string name="power_off" msgid="4266614107412865048">"關機"</string>
<string name="shutdown_progress" msgid="2281079257329981203">"關機中..."</string>
<string name="shutdown_confirm" msgid="649792175242821353">"手機å³å°‡é—œæ©Ÿã€‚"</string>
+ <string name="recent_tasks_title" msgid="3691764623638127888">"最新的"</string>
<string name="no_recent_tasks" msgid="279702952298056674">"最近沒有存å–應用程å¼ã€‚"</string>
<string name="global_actions" msgid="2406416831541615258">"電話é¸é …"</string>
<string name="global_action_lock" msgid="2844945191792119712">"螢幕鎖定"</string>
@@ -151,7 +161,7 @@
<string name="permgroupdesc_accounts" msgid="4948732641827091312">"å­˜å–å¯ç”¨å¸³æˆ¶ã€‚"</string>
<string name="permgrouplab_hardwareControls" msgid="7998214968791599326">"硬體控制"</string>
<string name="permgroupdesc_hardwareControls" msgid="4357057861225462702">"在å…æŒè¨­å‚™ä¸Šç›´æŽ¥å­˜å–硬體。"</string>
- <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"撥打電話"</string>
+ <string name="permgrouplab_phoneCalls" msgid="9067173988325865923">"通話次數"</string>
<string name="permgroupdesc_phoneCalls" msgid="7489701620446183770">"監控ã€è¨˜éŒ„與進行通話。"</string>
<string name="permgrouplab_systemTools" msgid="4652191644082714048">"系統工具"</string>
<string name="permgroupdesc_systemTools" msgid="8162102602190734305">"系統低階存å–與控制。"</string>
@@ -185,8 +195,12 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"å…許應用程å¼ç‚ºå…¶ä»–程å¼é–‹å•ŸåµéŒ¯åŠŸèƒ½ã€‚請注æ„:惡æ„程å¼å¯åˆ©ç”¨æ­¤åŠŸèƒ½çµ‚止其他應用程å¼ã€‚"</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"變更介é¢è¨­å®š"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"å…許應用程å¼è®Šæ›´ç›®å‰è¨­å®šï¼Œä¾‹å¦‚:地å€è¨­å®šæˆ–字型大å°ã€‚"</string>
- <string name="permlab_restartPackages" msgid="2386396847203622628">"é‡æ–°å•Ÿå‹•å…¶ä»–應用程å¼"</string>
- <string name="permdesc_restartPackages" msgid="1076364837492936814">"å…許應用程å¼å¼·åˆ¶é‡æ–°å•Ÿå‹•å…¶ä»–應用程å¼ã€‚"</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"啟用行車模å¼"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"å…許應用程å¼å•Ÿç”¨è¡Œè»Šæ¨¡å¼ã€‚"</string>
+ <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"關閉背景程åº"</string>
+ <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"å…許應用程å¼é—œé–‰å…¶ä»–應用程å¼èƒŒæ™¯ç¨‹åº (å³ä½¿è¨˜æ†¶é«”足夠)。"</string>
+ <string name="permlab_forceStopPackages" msgid="1447830113260156236">"強制åœæ­¢æ‡‰ç”¨ç¨‹å¼"</string>
+ <string name="permdesc_forceStopPackages" msgid="7263036616161367402">"å…許應用程å¼å¼·åˆ¶åœæ­¢å…¶ä»–應用程å¼ã€‚"</string>
<string name="permlab_forceBack" msgid="1804196839880393631">"強制關閉應用程å¼"</string>
<string name="permdesc_forceBack" msgid="6534109744159919013">"å…許應用程å¼å¼·åˆ¶é—œé–‰åœ¨å‰ç«¯é‹ä½œçš„活動並返回。一般應用程å¼ä¸éœ€è¦æ­¤åŠŸèƒ½ã€‚"</string>
<string name="permlab_dump" msgid="1681799862438954752">"接收系統內部狀態"</string>
@@ -211,8 +225,6 @@
<string name="permdesc_batteryStats" msgid="5847319823772230560">"å…許修改電池狀態。一般應用程å¼ä¸æœƒä½¿ç”¨æ­¤åŠŸèƒ½ã€‚"</string>
<string name="permlab_backup" msgid="470013022865453920">"控制系統備份與還原"</string>
<string name="permdesc_backup" msgid="4837493065154256525">"å…許應用程å¼æŽ§åˆ¶ç³»çµ±å‚™ä»½èˆ‡é‚„原機制 (ä¸å»ºè­°ä¸€èˆ¬æ‡‰ç”¨ç¨‹å¼ä½¿ç”¨)。"</string>
- <string name="permlab_backup_data" msgid="4057625941707926463">"備份åŠé‚„原應用程å¼è³‡æ–™"</string>
- <string name="permdesc_backup_data" msgid="8274426305151227766">"å…許應用程å¼åŠ å…¥ç³»çµ±å‚™ä»½èˆ‡é‚„原機制。"</string>
<string name="permlab_internalSystemWindow" msgid="2148563628140193231">"顯示未授權視窗"</string>
<string name="permdesc_internalSystemWindow" msgid="5895082268284998469">"å…許內部系統使用介é¢å»ºç«‹è¦–窗。一般應用程å¼ä¸æœƒä½¿ç”¨æ­¤åŠŸèƒ½ã€‚"</string>
<string name="permlab_systemAlertWindow" msgid="3372321942941168324">"顯示系統警示"</string>
@@ -229,6 +241,8 @@
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"å…許æ“有人連çµè‡³è¼¸å…¥æ³•çš„最頂層介é¢ã€‚一般應用程å¼ä¸éœ€ä½¿ç”¨æ­¤é¸é …。"</string>
<string name="permlab_bindWallpaper" msgid="8716400279937856462">"連çµè‡³æ¡Œå¸ƒ"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"å…許æ“有人連çµè‡³æ¡Œå¸ƒçš„最頂層介é¢ï¼Œä¸€èˆ¬æ‡‰ç”¨ç¨‹å¼ä¸éœ€ä½¿ç”¨æ­¤é¸é …。"</string>
+ <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"與è£ç½®ç®¡ç†å“¡äº’å‹•"</string>
+ <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"å…許應用程å¼å°‡èª¿ç”¨è«‹æ±‚ (intent) 傳é€è‡³è£ç½®ç®¡ç†å“¡ï¼›ä¸€èˆ¬æ‡‰ç”¨ç¨‹å¼ä¸éœ€ä½¿ç”¨æ­¤é¸é …。"</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"變更螢幕顯示方å‘"</string>
<string name="permdesc_setOrientation" msgid="6335814461615851863">"å…許應用程å¼éš¨æ™‚變更螢幕顯示方å‘。一般應用程å¼ä¸éœ€è¦æ­¤åŠŸèƒ½ã€‚"</string>
<string name="permlab_signalPersistentProcesses" msgid="4255467255488653854">"å‚³é€ Linux 訊號到應用程å¼"</string>
@@ -247,6 +261,8 @@
<string name="permdesc_installPackages" msgid="526669220850066132">"å…許應用程å¼å®‰è£æ–°çš„ Android 程å¼æˆ–更新。請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½æ–°å¢žå…·æœ‰æ¥µé«˜æ¬Šé™çš„程å¼ã€‚"</string>
<string name="permlab_clearAppCache" msgid="4747698311163766540">"刪除所有應用程å¼å¿«å–資料。"</string>
<string name="permdesc_clearAppCache" msgid="7740465694193671402">"å…許應用程å¼åˆªé™¤å¿«å–目錄裡的檔案,釋放儲存空間。此æ“作通常å—到系統程åºåš´æ ¼é™åˆ¶ã€‚"</string>
+ <string name="permlab_movePackage" msgid="728454979946503926">"移動應用程å¼è³‡æº"</string>
+ <string name="permdesc_movePackage" msgid="6323049291923925277">"å…許應用程å¼å°‡æ‡‰ç”¨ç¨‹å¼è³‡æºå¾žå…§éƒ¨åª’體移到外部媒體,å之亦å¯ã€‚"</string>
<string name="permlab_readLogs" msgid="4811921703882532070">"讀å–系統記錄檔"</string>
<string name="permdesc_readLogs" msgid="2257937955580475902">"å…許應用程å¼è®€å–系統記錄檔。此項æ“作å¯è®“應用程å¼äº†è§£ç›®å‰æ‰‹æ©Ÿæ“作狀態,但內容應ä¸å«ä»»ä½•å€‹äººæˆ–éš±ç§è³‡è¨Šã€‚"</string>
<string name="permlab_diagnostic" msgid="8076743953908000342">"讀寫 diag æ“有的資æº"</string>
@@ -273,10 +289,10 @@
<string name="permdesc_writeOwnerData" msgid="2344055317969787124">"å…許應用程å¼æ›´æ”¹æ‰‹æ©ŸæŒæœ‰è€…的資料。請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½ï¼Œæ¸…除或修改æŒæœ‰è€…的資料。"</string>
<string name="permlab_readOwnerData" msgid="6668525984731523563">"讀å–æŒæœ‰è€…的資料"</string>
<string name="permdesc_readOwnerData" msgid="3088486383128434507">"å…許應用程å¼è®€å–手機æŒæœ‰è€…資料。請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½è®€å–æŒæœ‰è€…的資料。"</string>
- <string name="permlab_readCalendar" msgid="3728905909383989370">"讀å–日曆資料"</string>
+ <string name="permlab_readCalendar" msgid="6898987798303840534">"讀å–日曆活動"</string>
<string name="permdesc_readCalendar" msgid="5533029139652095734">"å…許應用程å¼è®€å–手機上所有日曆資料。請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½å°‡æ‚¨çš„日曆資料傳é€çµ¦å…¶ä»–人。"</string>
- <string name="permlab_writeCalendar" msgid="377926474603567214">"寫入日曆資料"</string>
- <string name="permdesc_writeCalendar" msgid="8674240662630003173">"å…許應用程å¼ç·¨è¼¯æ—¥æ›†è³‡æ–™ã€‚請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½ï¼Œæ¸…除或修改您的日曆資料。"</string>
+ <string name="permlab_writeCalendar" msgid="3894879352594904361">"新增或修改日曆活動,並傳é€é›»å­éƒµä»¶çµ¦ä»–人"</string>
+ <string name="permdesc_writeCalendar" msgid="2988871373544154221">"å…許應用程å¼åœ¨æ‚¨çš„日曆上新增或變更活動,此時,應用程å¼å¯èƒ½æœƒå‚³é€é›»å­éƒµä»¶çµ¦ä»–人。ä¸éŽï¼Œè‹¥å…許的是惡æ„應用程å¼ï¼Œæ—¥æ›†æ´»å‹•å¯èƒ½æœƒå› æ­¤é­åˆ°åˆªé™¤æˆ–竄改,惡æ„應用程å¼ä¹Ÿå¯èƒ½å‚³é€é›»å­éƒµä»¶é¨·æ“¾ä»–人。"</string>
<string name="permlab_accessMockLocation" msgid="8688334974036823330">"模擬ä½ç½®ä¾†æºä»¥ä¾›æ¸¬è©¦"</string>
<string name="permdesc_accessMockLocation" msgid="7648286063459727252">"建立模擬ä½ç½®ä¾†æºä»¥ä¾›æ¸¬è©¦ã€‚請注æ„:惡æ„程å¼å¯èƒ½åˆ©ç”¨æ­¤åŠŸèƒ½è¦†å¯« GPS 或電信業者傳回的ä½ç½®åŠ/或狀態。"</string>
<string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"接收é¡å¤–çš„ä½ç½®æ供者指令"</string>
@@ -305,6 +321,16 @@
<string name="permdesc_mount_unmount_filesystems" msgid="6253263792535859767">"å…許應用程å¼æŽ›è¼‰/å¸è¼‰æŠ½å–å¼å„²å­˜è¨­å‚™çš„檔案系統。"</string>
<string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"將外接å¼å„²å­˜è£ç½®æ ¼å¼åŒ–"</string>
<string name="permdesc_mount_format_filesystems" msgid="574060044906047386">"å…許應用程å¼å°‡å¯ç§»é™¤å¼å„²å­˜è£ç½®æ ¼å¼åŒ–。"</string>
+ <string name="permlab_asec_access" msgid="1070364079249834666">"å–得安全儲存空間的資訊"</string>
+ <string name="permdesc_asec_access" msgid="7691616292170590244">"å…許應用程å¼å–得安全儲存空間的資訊。"</string>
+ <string name="permlab_asec_create" msgid="7312078032326928899">"建立安全儲存空間"</string>
+ <string name="permdesc_asec_create" msgid="7041802322759014035">"å…許應用程å¼å»ºç«‹å®‰å…¨å„²å­˜ç©ºé–“。"</string>
+ <string name="permlab_asec_destroy" msgid="7787322878955261006">"銷毀安全儲存空間"</string>
+ <string name="permdesc_asec_destroy" msgid="5740754114967893169">"å…許應用程å¼éŠ·æ¯€å®‰å…¨å„²å­˜ç©ºé–“。"</string>
+ <string name="permlab_asec_mount_unmount" msgid="7517449694667828592">"掛載/å¸è¼‰å®‰å…¨å„²å­˜ç©ºé–“"</string>
+ <string name="permdesc_asec_mount_unmount" msgid="5438078121718738625">"å…許應用程å¼æŽ›è¼‰/å¸è¼‰å®‰å…¨å„²å­˜ç©ºé–“。"</string>
+ <string name="permlab_asec_rename" msgid="5685344390439934495">"é‡æ–°å‘½å安全儲存空間"</string>
+ <string name="permdesc_asec_rename" msgid="1387881770708872470">"å…許應用程å¼é‡æ–°å‘½å安全儲存空間。"</string>
<string name="permlab_vibrate" msgid="7768356019980849603">"控制震動"</string>
<string name="permdesc_vibrate" msgid="2886677177257789187">"å…許應用程å¼æŽ§åˆ¶éœ‡å‹•ã€‚"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"控制閃光燈"</string>
@@ -339,6 +365,8 @@
<string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"å…許應用程å¼è¨­å®šç³»çµ±æ¡Œå¸ƒå¤§å°æ示。"</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"將系統還原至出廠é è¨­å€¼"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"å…許應用程å¼å°‡æ‰‹æ©Ÿå®Œå…¨é‡è¨­è‡³å‡ºå» è¨­å®šï¼Œæ¸…除所有資料ã€è¨­å®šèˆ‡å·²å®‰è£ç¨‹å¼ã€‚"</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"設定時間"</string>
+ <string name="permdesc_setTime" msgid="667294309287080045">"å…許應用程å¼è®Šæ›´æ‰‹æ©Ÿæ™‚é˜æ™‚間。"</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"設定時å€"</string>
<string name="permdesc_setTimeZone" msgid="1902540227418179364">"å…許應用程å¼è®Šæ›´æ™‚å€ã€‚"</string>
<string name="permlab_accountManagerService" msgid="4829262349691386986">"作為 AccountManagerService"</string>
@@ -358,7 +386,9 @@
<string name="permlab_writeApnSettings" msgid="7823599210086622545">"輸入存å–點å稱設定"</string>
<string name="permdesc_writeApnSettings" msgid="7443433457842966680">"å…許應用程å¼ä¿®æ”¹ APN 設定,例如:Proxy åŠ APN 的連接埠。"</string>
<string name="permlab_changeNetworkState" msgid="958884291454327309">"變更網路連線"</string>
- <string name="permdesc_changeNetworkState" msgid="6278115726355634395">"å…許應用程å¼è®Šæ›´ç¶²è·¯é€£ç·šç‹€æ…‹ã€‚"</string>
+ <string name="permdesc_changeNetworkState" msgid="4199958910396387075">"å…許應用程å¼è®Šæ›´ç¶²è·¯é€£ç·šç‹€æ…‹ã€‚"</string>
+ <string name="permlab_changeTetherState" msgid="2702121155761140799">"變更數據連線"</string>
+ <string name="permdesc_changeTetherState" msgid="8905815579146349568">"å…許應用程å¼è®Šæ›´æ•¸æ“šç¶²è·¯é€£ç·šç‹€æ…‹ã€‚"</string>
<string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"變更背景資料使用設定"</string>
<string name="permdesc_changeBackgroundDataSetting" msgid="1001482853266638864">"å…許應用程å¼è®Šæ›´èƒŒæ™¯è³‡æ–™ä½¿ç”¨è¨­å®šã€‚"</string>
<string name="permlab_accessWifiState" msgid="8100926650211034400">"檢視 Wi-Fi 狀態"</string>
@@ -389,6 +419,18 @@
<string name="permdesc_writeDictionary" msgid="2241256206524082880">"å…許應用程å¼å°‡æ–°å­—詞寫入使用者的字典。"</string>
<string name="permlab_sdcardWrite" msgid="8079403759001777291">"修改/刪除 SD å¡çš„內容"</string>
<string name="permdesc_sdcardWrite" msgid="6643963204976471878">"å…許應用程å¼å¯«å…¥ SD å¡ã€‚"</string>
+ <string name="permlab_cache_filesystem" msgid="5656487264819669824">"å­˜å–å¿«å–檔案系統"</string>
+ <string name="permdesc_cache_filesystem" msgid="1624734528435659906">"å…許應用程å¼è®€å–åŠå¯«å…¥å¿«å–檔案系統。"</string>
+ <string name="policylab_limitPassword" msgid="4307861496302850201">"é™åˆ¶å¯†ç¢¼è¨­å®šè¦å‰‡"</string>
+ <string name="policydesc_limitPassword" msgid="1719877245692318299">"é™åˆ¶å…許使用的密碼類型。"</string>
+ <string name="policylab_watchLogin" msgid="7374780712664285321">"查看登入嘗試記錄"</string>
+ <string name="policydesc_watchLogin" msgid="1961251179624843483">"監視者無法登入è£ç½®åŸ·è¡Œéƒ¨åˆ†å‹•ä½œã€‚"</string>
+ <string name="policylab_resetPassword" msgid="9084772090797485420">"é‡è¨­å¯†ç¢¼"</string>
+ <string name="policydesc_resetPassword" msgid="3332167600331799991">"強制é‡æ–°è¨­å®šå¯†ç¢¼ã€‚您必須å–得以管ç†å“¡æ供的新密碼,æ‰èƒ½ç™»å…¥ã€‚"</string>
+ <string name="policylab_forceLock" msgid="5760466025247634488">"強制鎖定"</string>
+ <string name="policydesc_forceLock" msgid="2819868664946089740">"è£ç½®éŽ–定時å¯å–得控制,但必須é‡æ–°è¼¸å…¥å¯†ç¢¼ã€‚"</string>
+ <string name="policylab_wipeData" msgid="3910545446758639713">"清除所有資料"</string>
+ <string name="policydesc_wipeData" msgid="2314060933796396205">"é‡è¨­ç‚ºåŽŸå» è¨­å®š (系統會刪除所有資料,且ä¸æœƒå‘您進行確èª)。"</string>
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"ä½å®¶é›»è©±"</item>
<item msgid="869923650527136615">"行動電話"</item>
@@ -485,6 +527,7 @@
<string name="contact_status_update_attribution" msgid="5112589886094402795">"é€éŽ <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
<string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>é€éŽã€Œ<xliff:g id="SOURCE">%2$s</xliff:g>ã€"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string>
+ <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"輸入密碼å³å¯è§£éŽ–"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤ï¼"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"如è¦è§£éŽ–,請按 Menu éµï¼Œç„¶å¾ŒæŒ‰ 0。"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
@@ -494,6 +537,7 @@
<string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"按下 Menu éµè§£éŽ–。"</string>
<string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"畫出解鎖圖形"</string>
<string name="lockscreen_emergency_call" msgid="5347633784401285225">"緊急電話"</string>
+ <string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通話"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正確ï¼"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,請å†è©¦ä¸€æ¬¡"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充電 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
@@ -503,7 +547,7 @@
<string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"沒有 SIM å¡ã€‚"</string>
<string name="lockscreen_missing_sim_message" msgid="2186920585695169078">"手機未æ’å…¥ SIM å¡ã€‚"</string>
<string name="lockscreen_missing_sim_instructions" msgid="8874620818937719067">"è«‹æ’å…¥ SIM å¡ã€‚"</string>
- <string name="emergency_calls_only" msgid="6733978304386365407">"åªèƒ½æ’¥æ‰“緊急電話"</string>
+ <string name="emergency_calls_only" msgid="6733978304386365407">"僅å¯æ’¥æ‰“緊急電話"</string>
<string name="lockscreen_network_locked_message" msgid="143389224986028501">"網路已鎖定"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM 的 PUK 已鎖定。"</string>
<string name="lockscreen_sim_puk_locked_instructions" msgid="635967534992394321">"è«‹åƒé–±ã€Šä½¿ç”¨è€…指å—》或è¯çµ¡å®¢æˆ¶æœå‹™ä¸­å¿ƒã€‚"</string>
@@ -524,6 +568,9 @@
<string name="lockscreen_unlock_label" msgid="737440483220667054">"解除å°éŽ–"</string>
<string name="lockscreen_sound_on_label" msgid="9068877576513425970">"開啟音效"</string>
<string name="lockscreen_sound_off_label" msgid="996822825154319026">"關閉音效"</string>
+ <string name="password_keyboard_label_symbol_key" msgid="992280756256536042">"?123"</string>
+ <string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
+ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
<string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
@@ -549,6 +596,8 @@
<string name="permdesc_readHistoryBookmarks" msgid="4981489815467617191">"å…許應用程å¼è®€å–ç€è¦½å™¨æ›¾ç¶“造訪éŽçš„所有網å€ï¼Œä»¥åŠç€è¦½å™¨çš„所有書籤。"</string>
<string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"寫入ç€è¦½å™¨çš„記錄與書籤"</string>
<string name="permdesc_writeHistoryBookmarks" msgid="945571990357114950">"å…許應用程å¼ä¿®æ”¹å„²å­˜åœ¨é›»è©±ä¸Šçš„ç€è¦½è¨˜éŒ„或書籤。請注æ„:惡æ„應用程å¼å¯èƒ½æœƒä½¿ç”¨æ­¤é¸é …來清除或修改您ç€è¦½å™¨çš„資料。"</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="4715212655598275532">"修改ç€è¦½å™¨åœ°ç†è³‡è¨Šçš„權é™"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="4011908282980861679">"å…許應用程å¼ä¿®æ”¹ç€è¦½å™¨çš„地ç†ä½ç½®æ¬Šé™ï¼Œæƒ¡æ„應用程å¼å¯èƒ½æœƒé€éŽæ­¤æ–¹å¼å…許將您的ä½ç½®è³‡è¨Šä»»æ„傳é€çµ¦æŸäº›ç¶²ç«™ã€‚"</string>
<string name="save_password_message" msgid="767344687139195790">"是å¦è¨˜æ†¶æ­¤å¯†ç¢¼ï¼Ÿ"</string>
<string name="save_password_notnow" msgid="6389675316706699758">"ç¾åœ¨ä¸è¦"</string>
<string name="save_password_remember" msgid="6491879678996749466">"記ä½"</string>
@@ -575,6 +624,11 @@
<item quantity="one" msgid="9150797944610821849">"1 å°æ™‚以å‰"</item>
<item quantity="other" msgid="2467273239587587569">"<xliff:g id="COUNT">%d</xliff:g> å°æ™‚以å‰"</item>
</plurals>
+ <plurals name="last_num_days">
+ <item quantity="other" msgid="3069992808164318268">"最近 <xliff:g id="COUNT">%d</xliff:g> 天"</item>
+ </plurals>
+ <string name="last_month" msgid="3959346739979055432">"上個月"</string>
+ <string name="older" msgid="5211975022815554840">"較舊"</string>
<plurals name="num_days_ago">
<item quantity="one" msgid="861358534398115820">"昨天"</item>
<item quantity="other" msgid="2479586466153314633">"<xliff:g id="COUNT">%d</xliff:g> 天以å‰"</item>
@@ -642,11 +696,6 @@
<string name="weeks" msgid="6509623834583944518">"週"</string>
<string name="year" msgid="4001118221013892076">"å¹´"</string>
<string name="years" msgid="6881577717993213522">"å¹´"</string>
- <string name="every_weekday" msgid="8777593878457748503">"æ¯å¤© (週一至週五)"</string>
- <string name="daily" msgid="5738949095624133403">"æ¯å¤©"</string>
- <string name="weekly" msgid="983428358394268344">"æ¯é€±<xliff:g id="DAY">%s</xliff:g>"</string>
- <string name="monthly" msgid="2667202947170988834">"æ¯æœˆ"</string>
- <string name="yearly" msgid="1519577999407493836">"æ¯å¹´"</string>
<string name="VideoView_error_title" msgid="3359437293118172396">"無法播放影片"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="897920883624437033">"很抱歉,影片格å¼ç„¡æ•ˆï¼Œè£ç½®ç„¡æ³•é€²è¡Œä¸²æµè™•ç†ã€‚"</string>
<string name="VideoView_error_text_unknown" msgid="710301040038083944">"很抱歉,此影片無法播放。"</string>
@@ -676,7 +725,7 @@
<string name="cancel" msgid="6442560571259935130">"å–消"</string>
<string name="yes" msgid="5362982303337969312">"確定"</string>
<string name="no" msgid="5141531044935541497">"å–消"</string>
- <string name="dialog_alert_title" msgid="2049658708609043103">"請注æ„"</string>
+ <string name="dialog_alert_title" msgid="2049658708609043103">"注æ„"</string>
<string name="capital_on" msgid="1544682755514494298">"é–‹å•Ÿ"</string>
<string name="capital_off" msgid="6815870386972805832">"關閉"</string>
<string name="whichApplication" msgid="4533185947064773386">"完æˆæ“作需使用"</string>
@@ -695,7 +744,6 @@
<string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
<string name="report" msgid="4060218260984795706">"回報"</string>
<string name="wait" msgid="7147118217226317732">"等待"</string>
- <string name="debug" msgid="9103374629678531849">"åµéŒ¯"</string>
<string name="sendText" msgid="5132506121645618310">"訊æ¯å‚³é€æ–¹å¼"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"鈴è²éŸ³é‡"</string>
<string name="volume_music" msgid="5421651157138628171">"媒體音é‡"</string>
@@ -730,21 +778,23 @@
<string name="no_permissions" msgid="7283357728219338112">"無須許å¯"</string>
<string name="perms_hide" msgid="7283915391320676226"><b>" éš±è—"</b></string>
<string name="perms_show_all" msgid="2671791163933091180"><b>"顯示全部"</b></string>
- <string name="googlewebcontenthelper_loading" msgid="4722128368651947186">"載入中..."</string>
+ <string name="usb_storage_activity_title" msgid="2399289999608900443">"USB 大é‡å„²å­˜è£ç½®"</string>
<string name="usb_storage_title" msgid="5901459041398751495">"USB 已連接"</string>
- <string name="usb_storage_message" msgid="2759542180575016871">"å·²é€éŽ USB 連接手機與電腦。如è¦å¾žé›»è…¦æˆ– SD å¡è¤‡è£½æª”案,請é¸å– [掛載]。"</string>
- <string name="usb_storage_button_mount" msgid="8063426289195405456">"掛載"</string>
- <string name="usb_storage_button_unmount" msgid="6092146330053864766">"ä¸è¦æŽ›è¼‰"</string>
+ <string name="usb_storage_message" msgid="4796759646167247178">"å·²é€éŽ USB 連接手機與電腦。如è¦å¾žé›»è…¦æˆ– Android 系統的 SD å¡è¤‡è£½æª”案,請é¸å–下方按鈕。"</string>
+ <string name="usb_storage_button_mount" msgid="1052259930369508235">"é–‹å•Ÿ USB 儲存è£ç½®"</string>
<string name="usb_storage_error_message" msgid="2534784751603345363">"把 SD å¡ç•¶æˆ USB 儲存è£ç½®æ™‚發生å•é¡Œã€‚"</string>
<string name="usb_storage_notification_title" msgid="8175892554757216525">"USB 已連接"</string>
<string name="usb_storage_notification_message" msgid="7380082404288219341">"é¸å–此項將檔案複製到電腦,或從電腦複製。"</string>
<string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"關閉 USB 儲存è£ç½®"</string>
<string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"é¸å–此處關閉 USB 儲存è£ç½®ã€‚"</string>
- <string name="usb_storage_stop_title" msgid="6014127947456185321">"關閉 USB 儲存è£ç½®"</string>
- <string name="usb_storage_stop_message" msgid="2390958966725232848">"關閉 USB 儲存è£ç½®ä¹‹å‰ï¼Œè«‹ç¢ºå®šå…ˆå¾ž USB Host å¸è¼‰ã€‚é¸å– [關閉] å³å¯é—œé–‰ USB 儲存è£ç½®ã€‚"</string>
- <string name="usb_storage_stop_button_mount" msgid="1181858854166273345">"關閉"</string>
- <string name="usb_storage_stop_button_unmount" msgid="3774611918660582898">"å–消"</string>
- <string name="usb_storage_stop_error_message" msgid="3746037090369246731">"關閉 USB 儲存è£ç½®æ™‚發生å•é¡Œã€‚請檢查您是å¦å·²å¸è¼‰ USB Host,然後å†è©¦ä¸€æ¬¡ã€‚"</string>
+ <string name="usb_storage_stop_title" msgid="660129851708775853">"USB 儲存空間使用中"</string>
+ <string name="usb_storage_stop_message" msgid="3613713396426604104">"關閉 USB 儲存è£ç½®å‰ï¼Œè«‹å‹™å¿…先將 Android 系統的 SD å¡å¾žé›»è…¦ä¸Šå¸ä¸‹ (退出)。"</string>
+ <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"關閉 USB 儲存è£ç½®"</string>
+ <string name="usb_storage_stop_error_message" msgid="143881914840412108">"關閉 USB 儲存è£ç½®æ™‚發生å•é¡Œã€‚請檢查您是å¦å·²å¸è¼‰ USB Host,然後å†è©¦ä¸€æ¬¡ã€‚"</string>
+ <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"é–‹å•Ÿ USB 儲存è£ç½®"</string>
+ <string name="dlg_confirm_kill_storage_users_text" msgid="3202838234780505886">"如果您開啟 USB 儲存è£ç½®ï¼Œå‰‡æ‚¨æ­£åœ¨ä½¿ç”¨çš„æŸäº›æ‡‰ç”¨ç¨‹å¼æœƒåœæ­¢é‹ä½œï¼Œè€Œä¸”å¯èƒ½ç„¡æ³•ä½¿ç”¨ï¼Œå¾…您將 USB 儲存è£ç½®é—œé–‰æ‰æœƒæ¢å¾©æ­£å¸¸ã€‚"</string>
+ <string name="dlg_error_title" msgid="8048999973837339174">"USB æ“作失敗"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"確定"</string>
<string name="extmedia_format_title" msgid="8663247929551095854">"å°‡ SD å¡æ ¼å¼åŒ–"</string>
<string name="extmedia_format_message" msgid="3621369962433523619">"確定è¦å°‡ SD å¡æ ¼å¼åŒ–嗎?該 SD å¡ä¸­çš„所有資料將會éºå¤±ã€‚"</string>
<string name="extmedia_format_button_format" msgid="4131064560127478695">"æ ¼å¼åŒ–"</string>
@@ -769,6 +819,8 @@
<string name="activity_list_empty" msgid="4168820609403385789">"找ä¸åˆ°ç¬¦åˆçš„活動"</string>
<string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新元件使用統計資料"</string>
<string name="permdesc_pkgUsageStats" msgid="891553695716752835">"å…許修改收集到的元件使用統計資料。一般應用程å¼ä¸æœƒä½¿ç”¨æ­¤åŠŸèƒ½ã€‚"</string>
+ <string name="permlab_copyProtectedData" msgid="1660908117394854464">"å…許觸發é è¨­å®¹å™¨æœå‹™ï¼Œä»¥ä¾¿è¤‡è£½å…§å®¹ (ä¸å»ºè­°ä¸€èˆ¬æ‡‰ç”¨ç¨‹å¼ä½¿ç”¨)。"</string>
+ <string name="permdesc_copyProtectedData" msgid="537780957633976401">"å…許觸發é è¨­å®¹å™¨æœå‹™ï¼Œä»¥ä¾¿è¤‡è£½å…§å®¹ (ä¸å»ºè­°ä¸€èˆ¬æ‡‰ç”¨ç¨‹å¼ä½¿ç”¨)。"</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1311810005957319690">"點兩下以進行縮放控制"</string>
<string name="gadget_host_error_inflating" msgid="2613287218853846830">"擴大å°å·¥å…·æ™‚發生錯誤"</string>
<string name="ime_action_go" msgid="8320845651737369027">"開始"</string>
@@ -781,8 +833,9 @@
<string name="create_contact_using" msgid="4947405226788104538">"建立手機號碼為 <xliff:g id="NUMBER">%s</xliff:g>"\n"çš„è¯çµ¡äºº"</string>
<string name="accessibility_compound_button_selected" msgid="5612776946036285686">"已勾é¸"</string>
<string name="accessibility_compound_button_unselected" msgid="8864512895673924091">"未勾é¸"</string>
- <string name="grant_credentials_permission_message_desc" msgid="6883276587034335667">"這些應用程å¼è¦æ±‚å­˜å–「<xliff:g id="APPLICATION">%2$s</xliff:g>ã€çš„ <xliff:g id="ACCOUNT">%1$s</xliff:g> 帳戶登入èªè­‰çš„權é™ï¼Œæ‚¨è¦æŽˆäºˆæ­¤æ¬Šé™å—Žï¼Ÿå¦‚果確定授予,系統會記ä½æ‚¨çš„é¸æ“‡ï¼Œä¸æœƒå†æ¬¡è©¢å•æ‚¨ã€‚"</string>
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc" msgid="3159007601893584687">"這些應用程å¼è¦æ±‚å­˜å–「<xliff:g id="APPLICATION">%3$s</xliff:g>ã€çš„ <xliff:g id="ACCOUNT">%2$s</xliff:g> 帳戶「<xliff:g id="TYPE">%1$s</xliff:g>ã€ç™»å…¥èªè­‰çš„權é™ï¼Œæ‚¨è¦æŽˆäºˆæ­¤æ¬Šé™å—Žï¼Ÿå¦‚果確定授予,系統會記ä½æ‚¨çš„é¸æ“‡ï¼Œä¸æœƒå†æ¬¡è©¢å•æ‚¨ã€‚"</string>
+ <string name="grant_credentials_permission_message_header" msgid="6824538733852821001">"下列一或多個應用程å¼è¦æ±‚ç¾åœ¨åŠå°‡ä¾†çš„帳戶存å–權é™ã€‚"</string>
+ <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"您確定è¦å…許這個è¦æ±‚嗎?"</string>
+ <string name="grant_permissions_header_text" msgid="2722567482180797717">"å­˜å–è¦æ±‚"</string>
<string name="allow" msgid="7225948811296386551">"å…許"</string>
<string name="deny" msgid="2081879885755434506">"拒絕"</string>
<string name="permission_request_notification_title" msgid="5390555465778213840">"å·²è¦æ±‚權é™"</string>
@@ -796,4 +849,16 @@
<string name="l2tp_vpn_description" msgid="3750692169378923304">"第二層通é“通訊å”定"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用é å…ˆå…±ç”¨é‡‘é‘°çš„ L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
+ <string name="upload_file" msgid="2897957172366730416">"é¸æ“‡æª”案"</string>
+ <string name="reset" msgid="2448168080964209908">"é‡è¨­"</string>
+ <string name="submit" msgid="1602335572089911941">"æ交"</string>
+ <string name="description_star" msgid="2654319874908576133">"我的最愛"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"已啟用車用模å¼"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"é¸å–çµæŸè»Šç”¨æ¨¡å¼ã€‚"</string>
+ <string name="tethered_notification_title" msgid="8146103971290167718">"啟用數據連線"</string>
+ <string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string>
+ <string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用é‡"</string>
+ <string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸å³å¯çž­è§£æ›´å¤šæœ‰é—œè¡Œå‹•è³‡æ–™ç”¨é‡çš„詳細資訊"</string>
+ <string name="throttled_notification_title" msgid="6269541897729781332">"å·²é”行動資料上é™"</string>
+ <string name="throttled_notification_message" msgid="4712369856601275146">"輕觸å³å¯çž­è§£æ›´å¤šæœ‰é—œè¡Œå‹•è³‡æ–™ç”¨é‡çš„詳細資訊"</string>
</resources>
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 66f0e82..4672c0e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -71,6 +71,20 @@
<item>@drawable/indicator_code_lock_point_area_default</item>
<item>@drawable/indicator_code_lock_point_area_green</item>
<item>@drawable/indicator_code_lock_point_area_red</item>
+ <!-- SlidingTab drawables shared by InCallScreen and LockScreen -->
+ <item>@drawable/jog_tab_bar_left_end_confirm_gray</item>
+ <item>@drawable/jog_tab_bar_left_end_normal</item>
+ <item>@drawable/jog_tab_bar_left_end_pressed</item>
+ <item>@drawable/jog_tab_bar_right_end_confirm_gray</item>
+ <item>@drawable/jog_tab_bar_right_end_normal</item>
+ <item>@drawable/jog_tab_bar_right_end_pressed</item>
+ <item>@drawable/jog_tab_left_confirm_gray</item>
+ <item>@drawable/jog_tab_left_normal</item>
+ <item>@drawable/jog_tab_left_pressed</item>
+ <item>@drawable/jog_tab_right_confirm_gray</item>
+ <item>@drawable/jog_tab_right_normal</item>
+ <item>@drawable/jog_tab_right_pressed</item>
+ <item>@drawable/jog_tab_target_gray</item>
</array>
<!-- Do not translate. These are all of the color state list resources that should be
@@ -107,6 +121,7 @@
icons in the status bar that are not notifications. -->
<string-array name="status_bar_icon_order">
<item><xliff:g id="id">clock</xliff:g></item>
+ <item><xliff:g id="id">secure</xliff:g></item>
<item><xliff:g id="id">alarm_clock</xliff:g></item>
<item><xliff:g id="id">battery</xliff:g></item>
<item><xliff:g id="id">phone_signal</xliff:g></item>
@@ -126,13 +141,4 @@
<item><xliff:g id="id">ime</xliff:g></item>
</string-array>
- <!-- Do not translate. Each string points to the component name of an ACTION_WEB_SEARCH
- handling activity. On startup if there were no preferred ACTION_WEB_SEARCH handlers,
- the first component from this list which is found to be installed is set as the
- preferred activity. -->
- <string-array name="default_web_search_providers">
- <item>com.google.android.providers.enhancedgooglesearch/.Launcher</item>
- <item>com.android.googlesearch/.GoogleSearch</item>
- <item>com.android.websearch/.Search.1</item>
- </string-array>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a660fd9..8d4fa4e 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -340,6 +340,8 @@
<attr name="editTextStyle" format="reference" />
<!-- Default ExpandableListView style. -->
<attr name="expandableListViewStyle" format="reference" />
+ <!-- ExpandableListView with white background. -->
+ <attr name="expandableListViewWhiteStyle" format="reference" />
<!-- Default Gallery style. -->
<attr name="galleryStyle" format="reference" />
<!-- Default GestureOverlayView style. -->
@@ -394,6 +396,8 @@
<attr name="tabWidgetStyle" format="reference" />
<!-- Default TextView style. -->
<attr name="textViewStyle" format="reference" />
+ <!-- Default WebTextView style. -->
+ <attr name="webTextViewStyle" format="reference" />
<!-- Default WebView style. -->
<attr name="webViewStyle" format="reference" />
<!-- Default style for drop down items. -->
@@ -1396,8 +1400,13 @@
be a dimension (such as "12dip") for a constant width or one of
the special constants. -->
<attr name="layout_width" format="dimension">
- <!-- The view should be as big as its parent (minus padding). -->
+ <!-- The view should be as big as its parent (minus padding).
+ This constant is deprecated starting from API Level 8 and
+ is replaced by {@code match_parent}. -->
<enum name="fill_parent" value="-1" />
+ <!-- The view should be as big as its parent (minus padding).
+ Introduced in API Level 8. -->
+ <enum name="match_parent" value="-1" />
<!-- The view should be only big enough to enclose its content (plus padding). -->
<enum name="wrap_content" value="-2" />
</attr>
@@ -1407,8 +1416,13 @@
be a dimension (such as "12dip") for a constant height or one of
the special constants. -->
<attr name="layout_height" format="dimension">
- <!-- The view should be as big as its parent (minus padding). -->
+ <!-- The view should be as big as its parent (minus padding).
+ This constant is deprecated starting from API Level 8 and
+ is replaced by {@code match_parent}. -->
<enum name="fill_parent" value="-1" />
+ <!-- The view should be as big as its parent (minus padding).
+ Introduced in API Level 8. -->
+ <enum name="match_parent" value="-1" />
<!-- The view should be only big enough to enclose its content (plus padding). -->
<enum name="wrap_content" value="-2" />
</attr>
@@ -1450,7 +1464,7 @@
included in that tag. -->
<declare-styleable name="InputMethod">
<!-- Component name of an activity that allows the user to modify
- the settings for this input method. -->
+ the settings for this service. -->
<attr name="settingsActivity" format="string" />
<!-- Set to true in all of the configurations for which this input
method should be considered an option as the default. -->
@@ -1698,6 +1712,10 @@
space by giving it a layout_weight of 0.5 and setting the weightSum
to 1.0. -->
<attr name="weightSum" format="float" />
+ <!-- When set to true, all children with a weight will be considered having
+ the minimum size of the largest child. If false, all children are
+ measured normally. -->
+ <attr name="useLargestChild" format="boolean" />
</declare-styleable>
<declare-styleable name="ListView">
<!-- Reference to an array resource that will populate the ListView. For static content,
@@ -1846,6 +1864,14 @@
<attr name="layout_span" format="integer" />
</declare-styleable>
<declare-styleable name="TabWidget">
+ <!-- Drawable used to draw the divider between tabs. -->
+ <attr name="divider" />
+ <!-- Determines whether the strip under the tab indicators is drawn or not. -->
+ <attr name="tabStripEnabled" format="boolean" />
+ <!-- Drawable used to draw the left part of the strip underneath the tabs. -->
+ <attr name="tabStripLeft" format="reference" />
+ <!-- Drawable used to draw the right part of the strip underneath the tabs. -->
+ <attr name="tabStripRight" format="reference" />
</declare-styleable>
<declare-styleable name="TextAppearance">
<!-- Text color. -->
@@ -2112,23 +2138,35 @@
is used. -->
<attr name="dropDownAnchor" format="reference" />
<!-- Specifies the basic width of the dropdown. Its value may
- be a dimension (such as "12dip") for a constant width, fill_parent
- to fill the width of the screen, or wrap_content to match the width
- of the anchored view. -->
+ be a dimension (such as "12dip") for a constant width,
+ fill_parent or match_parent to match the width of the
+ screen, or wrap_content to match the width of
+ the anchored view. -->
<attr name="dropDownWidth" format="dimension">
- <!-- The dropdown should fill the width of the screen. -->
+ <!-- The dropdown should fill the width of the screen.
+ This constant is deprecated starting from API Level 8 and
+ is replaced by {@code match_parent}. -->
<enum name="fill_parent" value="-1" />
+ <!-- The dropdown should fit the width of the screen.
+ Introduced in API Level 8. -->
+ <enum name="match_parent" value="-1" />
<!-- The dropdown should fit the width of its anchor. -->
<enum name="wrap_content" value="-2" />
</attr>
- <!-- Specifies the basic width of the dropdown. Its value may
- be a dimension (such as "12dip") for a constant width, fill_parent
- to fill the width of the screen, or wrap_content to match the height of
+ <!-- Specifies the basic height of the dropdown. Its value may
+ be a dimension (such as "12dip") for a constant height,
+ fill_parent or match_parent to fill the height of the
+ screen, or wrap_content to match the height of
the content of the drop down. -->
<attr name="dropDownHeight" format="dimension">
- <!-- The dropdown should fill the width of the screen. -->
+ <!-- The dropdown should fit the height of the screen.
+ This constant is deprecated starting from API Level 8 and
+ is replaced by {@code match_parent}. -->
<enum name="fill_parent" value="-1" />
- <!-- The dropdown should fit the width of its anchor. -->
+ <!-- The dropdown should fit the height of the screen.
+ Introduced in API Level 8. -->
+ <enum name="match_parent" value="-1" />
+ <!-- The dropdown should fit the height of the content. -->
<enum name="wrap_content" value="-2" />
</attr>
<attr name="inputType" />
@@ -2324,6 +2362,11 @@
<attr name="orientation" />
</declare-styleable>
+ <!-- @hide -->
+ <declare-styleable name="WeightedLinearLayout">
+ <attr name="majorWeight" format="float" />
+ <attr name="minorWeight" format="float" />
+ </declare-styleable>
<!-- ========================= -->
<!-- Drawable class attributes -->
@@ -3471,6 +3514,21 @@
<!-- =============================== -->
<eat-comment />
+ <!-- Use <code>device-admin</code> as the root tag of the XML resource that
+ describes a
+ {@link android.app.admin.DeviceAdminReceiver}, which is
+ referenced from its
+ {@link android.app.admin.DeviceAdminReceiver#DEVICE_ADMIN_META_DATA}
+ meta-data entry. Described here are the attributes that can be
+ included in that tag. -->
+ <declare-styleable name="DeviceAdmin">
+ <!-- Control whether the admin is visible to the user, even when it
+ is not enabled. This is true by default. You may want to make
+ it false if your admin does not make sense to be turned on
+ unless some explicit action happens in your app. -->
+ <attr name="visible" />
+ </declare-styleable>
+
<!-- Use <code>wallpaper</code> as the root tag of the XML resource that
describes an
{@link android.service.wallpaper.WallpaperService}, which is
@@ -3479,8 +3537,6 @@
meta-data entry. Described here are the attributes that can be
included in that tag. -->
<declare-styleable name="Wallpaper">
- <!-- Component name of an activity that allows the user to modify
- the current settings for this wallpaper. -->
<attr name="settingsActivity" />
<!-- Reference to a the wallpaper's thumbnail bitmap. -->
@@ -3572,4 +3628,23 @@
<attr name="orientation" />
</declare-styleable>
+ <!-- =============================== -->
+ <!-- LockPatternView class attributes -->
+ <!-- =============================== -->
+ <eat-comment />
+
+ <declare-styleable name="LockPatternView">
+ <!-- Aspect to use when drawing LockPatternView. Choices are "square"(default), "lock_width"
+ or "lock_height" -->
+ <attr name="aspect" format="string" />
+ </declare-styleable>
+
+ <!-- Use <code>recognition-service</code> as the root tag of the XML resource that
+ describes a {@link android.speech.RecognitionService}, which is reference from
+ its {@link android.speech.RecognitionService#SERVICE_META_DATA} meta-data entry.
+ Described here are the attributes that can be included in that tag. -->
+ <declare-styleable name="RecognitionService">
+ <attr name="settingsActivity" />
+ </declare-styleable>
+
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 62529f1..ed7447c 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -78,7 +78,7 @@
turned on by default unless explicitly set to false
by applications. -->
<attr name="allowClearUserData" format="boolean" />
-
+
<!-- Option to indicate this application is only for testing purposes.
For example, it may expose functionality or data outside of itself
that would cause a security hole, but is useful for testing. This
@@ -208,6 +208,10 @@
running on a device that is running in user mode. -->
<attr name="debuggable" format="boolean" />
+ <!-- Flag indicating whether the application requests the VM to operate in
+ the safe mode. -->
+ <attr name="vmSafeMode" format="boolean" />
+
<!-- Flag indicating whether the given application component is available
to other applications. If false, it can only be accessed by
applications with its same user id (which usually means only by
@@ -291,7 +295,7 @@
<!-- Specify whether an activity should be finished when a "close system
windows" request has been made. This happens, for example, when
the home key is pressed, when the device is locked, when a system
- dialog like recent apps is displayed, etc. -->
+ dialog showing recent applications is displayed, etc. -->
<attr name="finishOnCloseSystemDialogs" format="boolean" />
<!-- Specify whether an activity's task should be cleared when it
@@ -476,7 +480,7 @@
the display will rotate based on how the user moves the device. -->
<enum name="sensor" value="4" />
<!-- Always ignore orientation determined by orientation sensor:
- tthe display will not rotate when the user moves the device. -->
+ the display will not rotate when the user moves the device. -->
<enum name="nosensor" value="5" />
</attr>
@@ -512,7 +516,7 @@
<flag name="keyboard" value="0x0010" />
<!-- The keyboard or navigation accessibility has changed, for example
the user has slid the keyboard out to expose it. Note that
- inspite of its name, this applied to any accessibility: keyboard
+ despite its name, this applied to any accessibility: keyboard
or navigation. -->
<flag name="keyboardHidden" value="0x0020" />
<!-- The navigation type has changed. Should never normally happen. -->
@@ -520,9 +524,12 @@
<!-- The screen orientation has changed, that is the user has
rotated the device. -->
<flag name="orientation" value="0x0080" />
- <!-- The screen orientation has changed, that is the user has
- rotated the device. -->
+ <!-- The screen layout has changed. This might be caused by a
+ different display being activated. -->
<flag name="screenLayout" value="0x0100" />
+ <!-- The global user interface mode has changed. For example,
+ going in or out of car mode, night mode changing, etc. -->
+ <flag name="uiMode" value="0x0200" />
<!-- The font scaling factor has changed, that is the user has
selected a new global font size. -->
<flag name="fontScale" value="0x40000000" />
@@ -577,22 +584,56 @@
<!-- Application's requirement for five way navigation -->
<attr name="reqFiveWayNav" format="boolean" />
- <!-- The name of the class implementing <code>BackupAgent</code> to manage
- backup and restore of the application's settings to external storage. -->
+ <!-- The name of the class subclassing <code>BackupAgent</code> to manage
+ backup and restore of the application's data on external storage. -->
<attr name="backupAgent" format="string" />
- <!-- This is not the attribute you are looking for. -->
+ <!-- Whether to allow the application to participate in backup
+ infrastructure. If this attribute is set to <code>false</code>, no backup
+ of the application will ever be performed, even by a full-system backup that
+ would otherwise cause all application data to be saved via adb. The
+ default value of this attribute is <code>true</code>. -->
<attr name="allowBackup" format="boolean" />
<!-- Whether the application in question should be terminated after its
- settings have been restored. The default is to do so. -->
+ settings have been restored during a full-system restore operation.
+ Single-package restore operations will never cause the application to
+ be shut down. Full-system restore operations typically only occur once,
+ when the phone is first set up. Third-party applications will not usually
+ need to use this attribute.
+
+ <p>The default is <code>true</code>, which means that after the application
+ has finished processing its data during a full-system restore, it will be
+ terminated. -->
<attr name="killAfterRestore" format="boolean" />
- <!-- Whether the application needs to have its own Application subclass
- active during restore. The default is to run restore with a minimal
- Application class to avoid interference with application logic. -->
+ <!-- @deprecated This attribute is not used by the Android operating system. -->
<attr name="restoreNeedsApplication" format="boolean" />
+ <!-- Indicate that the application is prepared to attempt a restore of any
+ backed-up dataset, even if the backup is apparently from a newer version
+ of the application than is currently installed on the device. Setting
+ this attribute to <code>true</code> will permit the Backup Manager to
+ attempt restore even when a version mismatch suggests that the data are
+ incompatible. <em>Use with caution!</em>
+
+ <p>The default value of this attribute is <code>false</code>. -->
+ <attr name="restoreAnyVersion" format="boolean" />
+
+ <!-- The default install location defined by an application. -->
+ <attr name="installLocation">
+ <!-- Let the system decide ideal install location -->
+ <enum name="auto" value="0" />
+ <!-- Explicitly request to be installed on internal phone storage
+ only. -->
+ <enum name="internalOnly" value="1" />
+ <!-- Prefer to be installed on SD card. There is no guarantee that
+ the system will honor this request. The application might end
+ up being installed on internal storage if external media
+ is unavailable or too full. -->
+ <enum name="preferExternal" value="2" />
+ </attr>
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
@@ -617,6 +658,7 @@
<attr name="versionName" />
<attr name="sharedUserId" />
<attr name="sharedUserLabel" />
+ <attr name="installLocation" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
@@ -663,6 +705,7 @@
override the component specific values). -->
<attr name="enabled" />
<attr name="debuggable" />
+ <attr name="vmSafeMode" />
<!-- Name of activity to be launched for managing the application's space on the device. -->
<attr name="manageSpaceActivity" />
<attr name="allowClearUserData" />
@@ -671,6 +714,7 @@
<attr name="allowBackup" />
<attr name="killAfterRestore" />
<attr name="restoreNeedsApplication" />
+ <attr name="restoreAnyVersion" />
</declare-styleable>
<!-- The <code>permission</code> tag declares a security permission that can be
@@ -785,7 +829,7 @@
<!-- The <code>uses-feature</code> tag specifies
a specific feature used by the application.
For example an application might specify that it requires
- specific version of open gl. Multiple such attribute
+ specific version of OpenGL. Multiple such attribute
values can be specified by the application.
<p>This appears as a child tag of the root
@@ -915,6 +959,19 @@
<attr name="name" />
</declare-styleable>
+ <!-- Private tag to declare the original package name that this package is
+ based on. Only used for packages installed in the system image. If
+ given, and different than the actual package name, and the given
+ original package was previously installed on the device but the new
+ one was not, then the data for the old one will be renamed to be
+ for the new package.
+
+ <p>This appears as a child tag of the root
+ {@link #AndroidManifest manifest} tag. -->
+ <declare-styleable name="AndroidManifestOriginalPackage" parent="AndroidManifest">
+ <attr name="name" />
+ </declare-styleable>
+
<!-- The <code>provider</code> tag declares a
{@link android.content.ContentProvider} class that is available
as part of the package's application components, supplying structured
@@ -930,6 +987,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="process" />
<attr name="authorities" />
@@ -1008,6 +1066,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<attr name="process" />
@@ -1039,6 +1098,7 @@
is a period then it is appended to your package name. -->
<attr name="name" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<attr name="process" />
@@ -1070,6 +1130,7 @@
<attr name="name" />
<attr name="theme" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="launchMode" />
<attr name="screenOrientation" />
@@ -1122,6 +1183,7 @@
"com.mycompany.MyName". -->
<attr name="targetActivity" format="string" />
<attr name="label" />
+ <attr name="description" />
<attr name="icon" />
<attr name="permission" />
<!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system).
@@ -1313,7 +1375,7 @@
{@link android.content.Intent#setData Intent.setData()}.
<p><em>Note: scheme and host name matching in the Android framework is
case-sensitive, unlike the formal RFC. As a result,
- Uris here should always be normalized to use lower case letters
+ URIs here should always be normalized to use lower case letters
for these elements (as well as other proper Uri normalization).</em></p> -->
<attr name="data" format="string" />
<!-- The MIME type name to assign to the Intent, as per
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 3c0f0a4..b6af6b2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -78,6 +78,7 @@
<!-- For security permissions -->
<color name="perms_dangerous_grp_color">#dd6826</color>
<color name="perms_dangerous_perm_color">#dd6826</color>
+ <color name="shadow">#cc222222</color>
<!-- For search-related UIs -->
<color name="search_url_text_normal">#7fa87f</color>
@@ -89,5 +90,12 @@
<color name="sliding_tab_text_color_active">@android:color/black</color>
<color name="sliding_tab_text_color_shadow">@android:color/black</color>
+ <!-- keyguard tab -->
+ <color name="keyguard_text_color_normal">#ffffff</color>
+ <color name="keyguard_text_color_unlock">#a7d84c</color>
+ <color name="keyguard_text_color_soundoff">#ffffff</color>
+ <color name="keyguard_text_color_soundon">#e69310</color>
+ <color name="keyguard_text_color_decline">#fe0a5a</color>
+
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0b6f97e..64f05fe 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -46,26 +46,54 @@
<!-- XXXXX NOTE THE FOLLOWING RESOURCES USE THE WRONG NAMING CONVENTION.
Please don't copy them, copy anything else. -->
-
- <!-- This string array should be overridden by the device to present a list of network attributes. This is used by the connectivity manager to decide which networks can coexist based on the hardward -->
- <!-- An Array of "[type-name],[associated radio-name],[priority] -->
+
+ <!-- This string array should be overridden by the device to present a list of network
+ attributes. This is used by the connectivity manager to decide which networks can coexist
+ based on the hardware -->
+ <!-- An Array of "[Connection name],[ConnectivityManager connection type],
+ [associated radio-type],[priority] -->
<string-array translatable="false" name="networkAttributes">
- <item>"default,wifi,0"</item>
- <item>"default,mobile,0"</item>
- <item>"mms,mobile,1"</item>
- <item>"supl,mobile,1"</item>
- <item>"dun,mobile,1"</item>
- <item>"hipri,mobile,2"</item>
+ <item>"wifi,1,1,1"</item>
+ <item>"mobile,0,0,0"</item>
+ <item>"mobile_mms,2,0,2"</item>
+ <item>"mobile_supl,3,0,2"</item>
+ <item>"mobile_hipri,5,0,3"</item>
</string-array>
- <!-- This string array should be overridden by the device to present a list of radio attributes. This is used by the connectivity manager to decide which networks can coexist based on the hardware -->
- <!-- An Array of "[radio-name],[priority] -->
- <!-- [# simultaneous connection types]" -->
+ <!-- This string array should be overridden by the device to present a list of radio
+ attributes. This is used by the connectivity manager to decide which networks can coexist
+ based on the hardware -->
+ <!-- An Array of "[ConnectivityManager connectionType],
+ [# simultaneous connection types]" -->
<string-array translatable="false" name="radioAttributes">
- <item>"wifi,1,1"</item>
- <item>"mobile,0,1"</item>
+ <item>"1,1"</item>
+ <item>"0,1"</item>
</string-array>
-
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ USB interfaces. If the device doesn't want to support tething over USB this should
+ be empty. An example would be "usb.*" -->
+ <string-array translatable="false" name="config_tether_usb_regexs">
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi interfaces. If the device doesn't want to support tethering over Wifi this
+ should be empty. An example would be "softap.*" -->
+ <string-array translatable="false" name="config_tether_wifi_regexs">
+ </string-array>
+
+ <!-- Dhcp range (min, max) to use for tethering purposes -->
+ <string-array translatable="false" name="config_tether_dhcp_range">
+ </string-array>
+
+ <!-- Regex array of allowable upstream ifaces for tethering - for example if you want
+ tethering on a new interface called "foo2" add <item>"foo\\d"</item> to the array -->
+ <string-array translatable="false" name="config_tether_upstream_regexs">
+ </string-array>
+
+ <!-- Boolean indicating if we require the use of DUN on mobile for tethering -->
+ <bool translatable="false" name="config_tether_dun_required">true</bool>
+
<!-- Flag indicating whether the keyguard should be bypassed when
the slider is open. This can be set or unset depending how easily
the slider can be opened (for example, in a pocket or purse). -->
@@ -100,11 +128,15 @@
in to AC and 2 to stay on when plugged in to USB. (So 3 for both.) -->
<integer name="config_carDockKeepsScreenOn">1</integer>
- <!-- Control whether being in the desk dock should enable accelerometer based screen orientation -->
+ <!-- Control whether being in the desk dock should enable accelerometer
+ based screen orientation. Note this should probably default to true
+ like car dock, but we haven't had a chance to test it. -->
<bool name="config_deskDockEnablesAccelerometer">false</bool>
- <!-- Control whether being in the car dock should enable accelerometer based screen orientation -->
- <bool name="config_carDockEnablesAccelerometer">false</bool>
+ <!-- Control whether being in the car dock should enable accelerometer based
+ screen orientation. This defaults to true because putting a device in
+ a car dock make the accelerometer more a physical input (like a lid). -->
+ <bool name="config_carDockEnablesAccelerometer">true</bool>
<!-- Indicate whether the lid state impacts the accessibility of
the physical keyboard. 0 means it doesn't, 1 means it is accessible
@@ -118,6 +150,9 @@
closed. The default is 0. -->
<integer name="config_lidNavigationAccessibility">0</integer>
+ <!-- Indicate whether the SD card is accessible without removing the battery. -->
+ <bool name="config_batterySdCardAccessibility">false</bool>
+
<!-- Vibrator pattern for feedback about a long screen/key press -->
<integer-array name="config_longPressVibePattern">
<item>0</item>
@@ -134,6 +169,11 @@
<item>30</item>
</integer-array>
+ <!-- Vibrator pattern for a very short but reliable vibration for soft keyboard tap -->
+ <integer-array name="config_keyboardTapVibePattern">
+ <item>40</item>
+ </integer-array>
+
<!-- Vibrator pattern for feedback about booting with safe mode disabled -->
<integer-array name="config_safeModeDisabledVibePattern">
<item>0</item>
@@ -152,6 +192,14 @@
<item>600</item>
</integer-array>
+ <!-- Vibrator pattern for feedback about hitting a scroll barrier -->
+ <integer-array name="config_scrollBarrierVibePattern">
+ <item>0</item>
+ <item>15</item>
+ <item>10</item>
+ <item>10</item>
+ </integer-array>
+
<bool name="config_use_strict_phone_number_comparation">false</bool>
<!-- Display low battery warning when battery level dips to this value -->
@@ -160,6 +208,15 @@
<!-- Close low battery warning when battery level reaches this value -->
<integer name="config_lowBatteryCloseWarningLevel">20</integer>
+ <!-- Default color for notification LED. -->
+ <color name="config_defaultNotificationColor">#ff00ff00</color>
+
+ <!-- Default LED on time for notification LED in milliseconds. -->
+ <integer name="config_defaultNotificationLedOn">500</integer>
+
+ <!-- Default LED off time for notification LED in milliseconds. -->
+ <integer name="config_defaultNotificationLedOff">2000</integer>
+
<!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
@@ -214,8 +271,40 @@
it will be removed when the lower-level touch driver generates better
data. -->
<bool name="config_filterTouchEvents">false</bool>
+
+ <!-- Enables special filtering code in the framework for raw touch events
+ from the touch driver. This code exists for one particular device,
+ and should not be enabled for any others. -->
+ <bool name="config_filterJumpyTouchEvents">false</bool>
<!-- Component name of the default wallpaper. This will be ImageWallpaper if not
specified -->
<string name="default_wallpaper_component">@null</string>
+
+ <!-- Component name of the service providing network location support. -->
+ <string name="config_networkLocationProvider">@null</string>
+
+ <!-- Component name of the service providing geocoder API support. -->
+ <string name="config_geocodeProvider">@null</string>
+
+ <!-- Boolean indicating if current platform supports bluetooth SCO for off call
+ use cases -->
+ <bool name="config_bluetooth_sco_off_call">true</bool>
+
+ <!-- The default data-use polling period. -->
+ <integer name="config_datause_polling_period_sec">600</integer>
+
+ <!-- The default data-use threshold in bytes. 0 disables-->
+ <integer name="config_datause_threshold_bytes">0</integer>
+
+ <!-- The default reduced-datarate value in kilobits per sec -->
+ <integer name="config_datause_throttle_kbitsps">300</integer>
+
+ <!-- The default iface on which to monitor data use -->
+ <string name="config_datause_iface">rmnet0</string>
+
+ <!-- The default reduced-datarate notification mask -->
+ <!-- 2 means give warning -->
+ <integer name="config_datause_notification_type">2</integer>
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6a3538d..8a92757 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -4,16 +4,16 @@
**
** Copyright 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
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
**
-** http://www.apache.org/licenses/LICENSE-2.0
+** http://www.apache.org/licenses/LICENSE-2.0
**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
@@ -34,4 +34,10 @@
<dimen name="fastscroll_thumb_width">64dp</dimen>
<!-- Height of the fastscroll thumb -->
<dimen name="fastscroll_thumb_height">52dp</dimen>
+ <!-- Default height of a key in the password keyboard -->
+ <dimen name="password_keyboard_key_height">56dip</dimen>
+ <!-- Default correction for the space key in the password keyboard -->
+ <dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen>
+ <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
+ <dimen name="status_bar_edge_ignore">5dp</dimen>
</resources>
diff --git a/core/res/res/values/donottranslate.xml b/core/res/res/values/donottranslate.xml
index 78d4d36..d6d5dbb 100644
--- a/core/res/res/values/donottranslate.xml
+++ b/core/res/res/values/donottranslate.xml
@@ -22,4 +22,6 @@
<string name="default_text_encoding">Latin-1</string>
<!-- @hide DO NOT TRANSLATE. Workaround for resource race condition in lockscreen. -->
<bool name="lockscreen_isPortrait">true</bool>
+ <!-- @hide DO NOT TRANSLATE. Control aspect ratio of lock pattern -->
+ <string name="lock_pattern_view_aspect">square</string>
</resources>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index cac26b9..8b6af71 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -67,4 +67,5 @@
<item type="id" name="addToDictionary" />
<item type="id" name="accountPreferences" />
<item type="id" name="smallIcon" />
+ <item type="id" name="custom" />
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 311930a..98c3a0a 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1214,5 +1214,29 @@
<public type="attr" name="author" id="0x010102b4" />
<public type="attr" name="autoStart" id="0x010102b5" />
-
+
+
+<!-- ===============================================================
+ Resources added in version 8 of the platform (Eclair MR2).
+ =============================================================== -->
+ <eat-comment />
+
+ <public type="attr" name="expandableListViewWhiteStyle" id="0x010102b6" />
+
+<!-- ===============================================================
+ Resources proposed for Froyo.
+ =============================================================== -->
+ <eat-comment />
+ <public type="attr" name="installLocation" id="0x010102b7" />
+ <public type="attr" name="vmSafeMode" id="0x010102b8" />
+ <public type="attr" name="webTextViewStyle" id="0x010102b9" />
+ <public type="attr" name="restoreAnyVersion" id="0x010102ba" />
+ <public type="attr" name="tabStripLeft" id="0x010102bb" />
+ <public type="attr" name="tabStripRight" id="0x010102bc" />
+ <public type="attr" name="tabStripEnabled" id="0x010102bd" />
+
+ <public type="id" name="custom" id="0x0102002b" />
+
+ <public type="anim" name="cycle_interpolator" id="0x010a000c" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index df297b3..1e007d36 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -135,9 +135,17 @@
<!-- Displayed to tell the user that emergency service is blocked by access control. -->
<string name="RestrictedOnEmergency">Emergency service is blocked.</string>
<!-- Displayed to tell the user that normal service is blocked by access control. -->
- <string name="RestrictedOnNormal">Voice/SMS service is blocked.</string>
- <!-- Displayed to tell the user that all voice service is blocked by access control. -->
- <string name="RestrictedOnAll">All voice/SMS services are blocked.</string>
+ <string name="RestrictedOnNormal">Voice service is blocked.</string>
+ <!-- Displayed to tell the user that all emergency and normal voice services are blocked by access control. -->
+ <string name="RestrictedOnAllVoice">All Voice services are blocked.</string>
+ <!-- Displayed to tell the user that sms service is blocked by access control. -->
+ <string name="RestrictedOnSms">SMS service is blocked.</string>
+ <!-- Displayed to tell the user that voice/data service is blocked by access control. -->
+ <string name="RestrictedOnVoiceData">Voice/Data services are blocked.</string>
+ <!-- Displayed to tell the user that voice and sms service are blocked by access control. -->
+ <string name="RestrictedOnVoiceSms">Voice/SMS services are blocked.</string>
+ <!-- Displayed to tell the user that all service is blocked by access control. -->
+ <string name="RestrictedOnAll">All Voice/Data/SMS services are blocked.</string>
<!-- Mappings between TS 27.007 +CFCC/+CLCK "service classes" and human-readable strings--> <skip />
<!-- Example: Service was enabled for: Voice, Data -->
@@ -275,7 +283,9 @@
<!-- Shutdown Confirmation Dialog. When the user chooses to power off the phone, there will be a confirmation dialog. This is the message. -->
<string name="shutdown_confirm">Your phone will shut down.</string>
- <!-- Recent Tasks dialog -->
+ <!-- Recent Tasks dialog: title -->
+ <string name="recent_tasks_title">Recent</string>
+ <!-- Recent Tasks dialog: message when there are no recent applications -->
<string name="no_recent_tasks">No recent applications.</string>
<!-- Title of the Global Actions Dialog -->
@@ -466,10 +476,23 @@
size.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_restartPackages">restart other applications</string>
+ <string name="permlab_enableCarMode">enable car mode</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_restartPackages">Allows an application to
- forcibly restart other applications.</string>
+ <string name="permdesc_enableCarMode">Allows an application to
+ enable the car mode.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_killBackgroundProcesses">kill background processes</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_killBackgroundProcesses">Allows an application to
+ kill background processes of other applications, even if memory
+ isn\'t low.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_forceStopPackages">force stop other applications</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_forceStopPackages">Allows an application to
+ forcibly stop other applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_forceBack">force application to close</string>
@@ -556,11 +579,6 @@
<string name="permdesc_backup">Allows the application to control the system\'s backup and restore mechanism. Not for use by normal applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_backup_data">back up and restore the application\'s data</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_backup_data">Allows the application to participate in the system\'s backup and restore mechanism.</string>
-
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_internalSystemWindow">display unauthorized windows</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_internalSystemWindow">Allows the creation of
@@ -614,6 +632,12 @@
interface of a wallpaper. Should never be needed for normal applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindDeviceAdmin">interact with a device admin</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindDeviceAdmin">Allows the holder to send intents to
+ a device administrator. Should never be needed for normal applications.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setOrientation">change screen orientation</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setOrientation">Allows an application to change
@@ -670,6 +694,11 @@
restricted usually to system process.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_movePackage">Move application resources</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_movePackage">Allows an application to move application resources from internal to external media and vice versa.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readLogs">read system log files</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_readLogs">Allows an application to read from the
@@ -764,18 +793,18 @@
applications can use this to read phone owner data.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_readCalendar">read calendar data</string>
+ <string name="permlab_readCalendar">read calendar events</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_readCalendar">Allows an application to read all
of the calendar events stored on your phone. Malicious applications
can use this to send your calendar events to other people.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_writeCalendar">write calendar data</string>
+ <string name="permlab_writeCalendar">add or modify calendar events and send email to guests</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeCalendar">Allows an application to modify the
- calendar events stored on your phone. Malicious
- applications can use this to erase or modify your calendar data.</string>
+ <string name="permdesc_writeCalendar">Allows an application to add or change the
+ events on your calendar, which may send email to guests. Malicious applications can use this
+ to erase or modify your calendar events or to send email to guests.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_accessMockLocation">mock location sources for testing</string>
@@ -868,6 +897,31 @@
<string name="permdesc_mount_format_filesystems">Allows the application to format removable storage.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_access">get information on secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_access">Allows the application to get information on secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_create">create secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_create">Allows the application to create secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_destroy">destroy secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_destroy">Allows the application to destroy secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_mount_unmount">mount / unmount secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_mount_unmount">Allows the application to mount / unmount secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_asec_rename">rename secure storage</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_asec_rename">Allows the application to rename secure storage.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_vibrate">control vibrator</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_vibrate">Allows the application to control
@@ -983,6 +1037,12 @@
configuration, and installed applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_setTime">set time</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_setTime">Allows an application to change
+ the phone\'s clock time.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_setTimeZone">set time zone</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_setTimeZone">Allows an application to change
@@ -1044,7 +1104,13 @@
<string name="permlab_changeNetworkState">change network connectivity</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_changeNetworkState">Allows an application to change
- the state network connectivity.</string>
+ the state of network connectivity.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_changeTetherState">Change tethered connectivity</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the applicaiton to do this. -->
+ <string name="permdesc_changeTetherState">Allows an application to change
+ the state of tethered network connectivity.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_changeBackgroundDataSetting">change background data usage setting</string>
@@ -1143,6 +1209,40 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_sdcardWrite">Allows an application to write to the SD card.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_cache_filesystem">access the cache filesystem</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_cache_filesystem">Allows an application to read and write the cache filesystem.</string>
+
+ <!-- Policy administration -->
+
+ <!-- Title of policy access to limiting the user's password choices -->
+ <string name="policylab_limitPassword">Limit password</string>
+ <!-- Description of policy access to limiting the user's password choices -->
+ <string name="policydesc_limitPassword">Restrict the types of passwords you
+ are allowed to use.</string>
+ <!-- Title of policy access to watch user login attempts -->
+ <string name="policylab_watchLogin">Watch login attempts</string>
+ <!-- Description of policy access to watch user login attempts -->
+ <string name="policydesc_watchLogin">Monitor failed attempts to login to
+ the device, to perform some action.</string>
+ <!-- Title of policy access to reset user's password -->
+ <string name="policylab_resetPassword">Reset password</string>
+ <!-- Description of policy access to reset user's password -->
+ <string name="policydesc_resetPassword">Force your password
+ to a new value, requiring the administrator give it to you
+ before you can log in.</string>
+ <!-- Title of policy access to force lock the device -->
+ <string name="policylab_forceLock">Force lock</string>
+ <!-- Description of policy access to limiting the user's password choices -->
+ <string name="policydesc_forceLock">Control when device locks,
+ requiring you re-enter its password.</string>
+ <!-- Title of policy access to wipe the user's data -->
+ <string name="policylab_wipeData">Erase all data</string>
+ <!-- Description of policy access to wipe the user's data -->
+ <string name="policydesc_wipeData">Perform a factory reset, deleting
+ all of your data without any confirmation.</string>
+
<!-- The order of these is important, don't reorder without changing Contacts.java --> <skip />
<!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. -->
<string-array name="phoneTypes">
@@ -1317,10 +1417,14 @@
<!-- Attbution of a contact status update, when the time of update is known -->
<string name="contact_status_update_attribution_with_date"><xliff:g id="date" example="3 hours ago">%1$s</xliff:g> via <xliff:g id="source" example="Google Talk">%2$s</xliff:g></string>
- <!-- Instructions telling the user to enter their pin to unlock the keyguard.
+ <!-- Instructions telling the user to enter their SIM PIN to unlock the keyguard.
Displayed in one line in a large font. -->
<string name="keyguard_password_enter_pin_code">Enter PIN code</string>
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard.
+ Displayed in one line in a large font. -->
+ <string name="keyguard_password_enter_password_code">Enter password to unlock</string>
+
<!-- Instructions telling the user that they entered the wrong pin while trying
to unlock the keyguard. Displayed in one line in a large font. -->
<string name="keyguard_password_wrong_pin_code">Incorrect PIN code!</string>
@@ -1351,6 +1455,8 @@
<string name="lockscreen_pattern_instructions">Draw pattern to unlock</string>
<!-- Button at the bottom of the unlock screen to make an emergency call. -->
<string name="lockscreen_emergency_call">Emergency call</string>
+ <!-- Button at the bottom of the unlock screen that lets the user return to a call -->
+ <string name="lockscreen_return_to_call">Return to call</string>
<!-- Shown to confirm that the user entered their lock pattern correctly. -->
<string name="lockscreen_pattern_correct">Correct!</string>
<!-- On the unlock pattern screen, shown when the user enters the wrong lock pattern and must try again. -->
@@ -1447,6 +1553,14 @@
<!-- Displayed on lock screen's right tab - turn sound off -->
<string name="lockscreen_sound_off_label">Sound off</string>
+ <!-- Password keyboard strings. Used by LockScreen and Settings --><skip />
+ <!-- Label for "switch to symbols" key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_symbol_key">\?123</string>
+ <!-- Label for "switch to alphabetic" key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_alpha_key">ABC</string>
+ <!-- Label for ALT modifier key. Must be short to fit on key! -->
+ <string name="password_keyboard_label_alt_key">ALT</string>
+
<!-- A format string for 12-hour time of day, just the hour, not the minute, with lower-case "am" or "pm" (example: "3pm"). -->
<string name="hour_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="pm">%P</xliff:g>"</string>
@@ -1504,8 +1618,8 @@
<string name="factorytest_reboot">Reboot</string>
<!-- Do not translate. WebView User Agent string -->
- <string name="web_user_agent"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s)
- AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17</xliff:g></string>
+ <string name="web_user_agent" translatable="false"><xliff:g id="x">Mozilla/5.0 (Linux; U; Android %s)
+ AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1</xliff:g></string>
<!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
<string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string>
@@ -1537,6 +1651,15 @@
Browser\'s history or bookmarks stored on your phone. Malicious applications
can use this to erase or modify your Browser\'s data.</string>
+ <!-- Title of an application permission, listed so the user can choose whether
+ they want to allow the application to do this. -->
+ <string name="permlab_writeGeolocationPermissions">Modify Browser geolocation permissions</string>
+ <!-- Description of an application permission, listed so the user can choose whether
+ they want to allow the application to do this. -->
+ <string name="permdesc_writeGeolocationPermissions">Allows an application to modify the
+ Browser\'s geolocation permissions. Malicious applications
+ can use this to allow sending location information to arbitrary web sites.</string>
+
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
@@ -1592,6 +1715,17 @@
<item quantity="other"><xliff:g id="count">%d</xliff:g> hours ago</item>
</plurals>
+ <!-- This is used to express that something occurred within the last X days (e.g., Last 7 days). -->
+ <plurals name="last_num_days">
+ <item quantity="other">Last <xliff:g id="count">%d</xliff:g> days</item>
+ </plurals>
+
+ <!-- This is used to express that something has occurred within the last month -->
+ <string name="last_month">Last month</string>
+
+ <!-- This is used to express that something happened longer ago than the previous options -->
+ <string name="older">Older</string>
+
<!-- This is used to express that something occurred some number of days in the past (e.g., 5 days ago). -->
<plurals name="num_days_ago">
<item quantity="one">yesterday</item>
@@ -1702,17 +1836,6 @@
<!-- Appened to express the value is this unit of time. -->
<string name="years">years</string>
- <!-- Calendar spinner item, to select that an event recurs every weekday. -->
- <string name="every_weekday">"Every weekday (Mon\u2013Fri)"</string>
- <!-- Calendar spinner item, to select that an event recurs every day. -->
- <string name="daily">Daily</string>
- <!-- Calendar spinner item, to select that an event recurs every week on a particular day of the week. -->
- <string name="weekly">"Weekly on <xliff:g id="day">%s</xliff:g>"</string>
- <!-- Calendar spinner item, to select that an event recurs every month. -->
- <string name="monthly">Monthly</string>
- <!-- Calendar spinner item, to select that an event recurs every year. -->
- <string name="yearly">Yearly</string>
-
<!-- Title for error alert when a video cannot be played. it can be used by any app. -->
<string name="VideoView_error_title">Cannot play video</string>
@@ -1850,8 +1973,6 @@
<string name="report">Report</string>
<!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. -->
<string name="wait">Wait</string>
- <!-- Button allowing a developer to connect a debugger to an application that is not responding. -->
- <string name="debug">Debug</string>
<!-- Displayed in the title of the chooser for things to do with text that
is to be sent to another application. For example, I can send text through SMS or IM. A dialog with those choices would be shown, and this would be the title. -->
@@ -1898,6 +2019,8 @@
<item quantity="one">Open Wi-Fi network available</item>
<item quantity="other">Open Wi-Fi networks available</item>
</plurals>
+ <!-- Do not translate. Default access point SSID used for tethering -->
+ <string name="wifi_tether_configure_ssid_default" translatable="false">AndroidAP</string>
<!-- Name of the dialog that lets the user choose an accented character to insert -->
<string name="select_character">Insert character</string>
@@ -1929,25 +2052,23 @@
<!-- When installing an application, the less-dangerous permissions are hidden. This is the text to show those. -->
<string name="perms_show_all"><b>Show all</b></string>
- <!-- Shown when there is content loading from the internet into a dialog. -->
- <string name="googlewebcontenthelper_loading">Loading\u2026</string>
-
<!-- USB storage dialog strings -->
- <!-- This is the label for the activity, and should never be visible to the user. -->
+ <!-- This is the title for the activity's window. -->
+ <string name="usb_storage_activity_title">USB Mass Storage</string>
+
<!-- See USB_STORAGE. USB_STORAGE_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to mount. This is the title. -->
<string name="usb_storage_title">USB connected</string>
<!-- See USB_STORAGE. This is the message. -->
- <string name="usb_storage_message">You have connected your phone to your computer via USB. Select \"Mount\" if you want to copy files between your computer and your phone\'s SD card.</string>
+ <string name="usb_storage_message">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string>
<!-- See USB_STORAGE. This is the button text to mount the phone on the computer. -->
- <string name="usb_storage_button_mount">Mount</string>
- <!-- See USB_STORAGE. This is the button text to ignore the plugging in of the phone.. -->
- <string name="usb_storage_button_unmount">Don\'t mount</string>
+ <string name="usb_storage_button_mount">Turn on USB storage</string>
<!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. -->
<string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
<!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title -->
<string name="usb_storage_notification_title">USB connected</string>
<!-- See USB_STORAGE. This is the message. -->
<string name="usb_storage_notification_message">Select to copy files to/from your computer.</string>
+
<!-- USB_STORAGE_STOP: While USB storage is enabled, we show a notification dialog asking if he wants to stop. This is the title -->
<string name="usb_storage_stop_notification_title">Turn off USB storage</string>
<!-- See USB_STORAGE. This is the message. -->
@@ -1956,15 +2077,22 @@
<!-- USB storage stop dialog strings -->
<!-- This is the label for the activity, and should never be visible to the user. -->
<!-- See USB_STORAGE_STOP. USB_STORAGE_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop usb storage. This is the title. -->
- <string name="usb_storage_stop_title">Turn off USB storage</string>
+ <string name="usb_storage_stop_title">USB storage in use</string>
<!-- See USB_STORAGE_STOP. This is the message. -->
- <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted on the USB host. Select \"Turn Off\" to turn off USB storage.</string>
+ <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string>
<!-- See USB_STORAGE_STOP. This is the button text to stop usb storage. -->
- <string name="usb_storage_stop_button_mount">Turn Off</string>
- <!-- See USB_STORAGE_STOP. This is the button text to cancel stoping usb storage. -->
- <string name="usb_storage_stop_button_unmount">Cancel</string>
+ <string name="usb_storage_stop_button_mount">Turn off USB storage</string>
<!-- See USB_STORAGE_STOP_DIALOG. If there was an error stopping, this is the text. -->
- <string name="usb_storage_stop_error_message">We\'ve encountered a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again.</string>
+ <string name="usb_storage_stop_error_message">There was a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again.</string>
+
+ <!-- USB_STORAGE_KILL_STORAGE_USERS dialog -->
+ <string name="dlg_confirm_kill_storage_users_title">Turn on USB storage</string>
+ <!-- USB_STORAGE_KILL_STORAGE_USERS dialog message text -->
+ <string name="dlg_confirm_kill_storage_users_text">If you turn on USB storage, some applications you are using will stop and may be unavailable until you turn off USB storage.</string>
+ <!-- USB_STORAGE_ERROR dialog dialog-->
+ <string name="dlg_error_title">USB operation failed</string>
+ <!-- USB_STORAGE_ERROR dialog ok button-->
+ <string name="dlg_ok">OK</string>
<!-- External media format dialog strings -->
<!-- This is the label for the activity, and should never be visible to the user. -->
@@ -2029,6 +2157,12 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_pkgUsageStats">Allows the modification of collected component usage statistics. Not for use by normal applications.</string>
+ <!-- permission attributes related to default container service -->
+ <!-- Title of an application permission that lets an application use default container service. -->
+ <string name="permlab_copyProtectedData">Allows to invoke default container service to copy content. Not for use by normal applications.</string>
+ <!-- Description of an application permission, used to invoke default container service to copy content. -->
+ <string name="permdesc_copyProtectedData">Allows to invoke default container service to copy content. Not for use by normal applications.</string>
+
<!-- Shown in the tutorial for tap twice for zoom control. -->
<string name="tutorial_double_tap_to_zoom_message_short">Tap twice for zoom control</string>
@@ -2079,16 +2213,9 @@
<!-- Title for the unselected state of a CompoundButton. -->
<string name="accessibility_compound_button_unselected">not checked</string>
- <string name="grant_credentials_permission_message_desc">The
- listed applications are requesting permission to access the login credentials for account <xliff:g id="account" example="foo@gmail.com">%1$s</xliff:g> from
- <xliff:g id="application" example="Google Apps">%2$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
- again.</string>
-
- <string name="grant_credentials_permission_message_with_authtokenlabel_desc">The
- listed applications are requesting permission to access the <xliff:g id="type" example="Contacts">%1$s</xliff:g> login credentials for account <xliff:g id="account" example="foo@gmail.com">%2$s</xliff:g> from
- <xliff:g id="application" example="Google Apps">%3$s</xliff:g>. Do you wish to grant this permission? If so, your answer will be remembered and you will not be prompted
- again.</string>
-
+ <string name="grant_credentials_permission_message_header">The following one or more applications request permission to access your account, now and in the future.</string>
+ <string name="grant_credentials_permission_message_footer">Do you want to allow this request?</string>
+ <string name="grant_permissions_header_text">Access Request</string>
<string name="allow">Allow</string>
<string name="deny">Deny</string>
<string name="permission_request_notification_title">Permission Requested</string>
@@ -2112,4 +2239,38 @@
<string name="l2tp_vpn_description">Layer 2 Tunneling Protocol</string>
<string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string>
<string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string>
+
+ <!-- Localized strings for WebView -->
+ <!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
+ <string name="upload_file">Choose file</string>
+ <!-- Label for <input type="reset"> button in html -->
+ <string name="reset">Reset</string>
+ <!-- Label for <input type="submit"> button in html -->
+ <string name="submit">Submit</string>
+
+ <!-- String describing the Star/Favorite checkbox
+
+ Used by AccessibilityService to announce the purpose of the view.
+ -->
+ <string name="description_star">favorite</string>
+
+ <!-- Strings for car mode notification -->
+ <!-- Shown when car mode is enabled -->
+ <string name="car_mode_disable_notification_title">Car mode enabled</string>
+ <string name="car_mode_disable_notification_message">Select to exit car mode.</string>
+
+ <!-- Strings for tethered notification -->
+ <!-- Shown when the device is tethered -->
+ <string name="tethered_notification_title">Tethering or hotspot active</string>
+ <string name="tethered_notification_message">Touch to configure</string>
+
+ <!-- Strings for throttling notification -->
+ <!-- Shown when the user is in danger of being throttled -->
+ <string name="throttle_warning_notification_title">High mobile data use</string>
+ <string name="throttle_warning_notification_message">Touch to learn more about mobile data use</string>
+
+ <!-- Strings for throttling notification -->
+ <!-- Shown when the users bandwidth is reduced because of excessive data use -->
+ <string name="throttled_notification_title">Mobile data limit exceeded</string>
+ <string name="throttled_notification_message">Touch to learn more about mobile data use</string>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index c95ede7..b5fff96 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -101,6 +101,7 @@
<!-- Standard animations for a non-full-screen window or activity. -->
<style name="Animation.LockScreen">
+ <item name="windowEnterAnimation">@anim/lock_screen_enter</item>
<item name="windowExitAnimation">@anim/lock_screen_exit</item>
</style>
@@ -180,8 +181,8 @@
<!-- A special animation we can use for recent applications,
for devices that can support it (do alpha transformations). -->
<style name="Animation.RecentApplications">
- <item name="windowEnterAnimation">@anim/recent_enter</item>
- <item name="windowExitAnimation">@anim/recent_exit</item>
+ <item name="windowEnterAnimation">@anim/fade_in</item>
+ <item name="windowExitAnimation">@anim/fade_out</item>
</style>
<!-- Status Bar Styles -->
@@ -370,7 +371,7 @@
<style name="Widget.TextView.ListSeparator">
<item name="android:background">@android:drawable/dark_header_dither</item>
- <item name="android:layout_width">fill_parent</item>
+ <item name="android:layout_width">match_parent</item>
<item name="android:layout_height">25dip</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">?textColorSecondary</item>
@@ -401,6 +402,10 @@
<item name="android:childDivider">@android:drawable/divider_horizontal_dark_opaque</item>
</style>
+ <style name="Widget.ExpandableListView.White">
+ <item name="android:childDivider">@android:drawable/divider_horizontal_bright_opaque</item>
+ </style>
+
<style name="Widget.ImageWell">
<item name="android:background">@android:drawable/panel_picture_frame_background</item>
</style>
@@ -498,6 +503,17 @@
<item name="android:scrollbars">horizontal|vertical</item>
</style>
+ <style name="Widget.WebTextView">
+ <item name="android:focusable">true</item>
+ <item name="android:focusableInTouchMode">true</item>
+ <item name="android:clickable">true</item>
+ <item name="android:completionHintView">@android:layout/simple_dropdown_item_1line</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item>
+ <item name="android:completionThreshold">2</item>
+ <item name="android:dropDownSelector">@android:drawable/list_selector_background</item>
+ <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item>
+ </style>
+
<style name="Widget.TabWidget">
<item name="android:textAppearance">@style/TextAppearance.Widget.TabWidget</item>
<item name="ellipsize">marquee</item>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 17ea66a..d585d9e 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -142,6 +142,7 @@
<item name="dropDownListViewStyle">@android:style/Widget.ListView.DropDown</item>
<item name="editTextStyle">@android:style/Widget.EditText</item>
<item name="expandableListViewStyle">@android:style/Widget.ExpandableListView</item>
+ <item name="expandableListViewWhiteStyle">@android:style/Widget.ExpandableListView.White</item>
<item name="galleryStyle">@android:style/Widget.Gallery</item>
<item name="gestureOverlayViewStyle">@android:style/Widget.GestureOverlayView</item>
<item name="gridViewStyle">@android:style/Widget.GridView</item>
@@ -169,6 +170,7 @@
<item name="starStyle">@android:style/Widget.CompoundButton.Star</item>
<item name="tabWidgetStyle">@android:style/Widget.TabWidget</item>
<item name="textViewStyle">@android:style/Widget.TextView</item>
+ <item name="webTextViewStyle">@android:style/Widget.WebTextView</item>
<item name="webViewStyle">@android:style/Widget.WebView</item>
<item name="dropDownItemStyle">@android:style/Widget.DropDownItem</item>
<item name="spinnerDropDownItemStyle">@android:style/Widget.DropDownItem.Spinner</item>
@@ -241,6 +243,7 @@
<item name="textCheckMarkInverse">@android:drawable/indicator_check_mark_dark</item>
<item name="gestureOverlayViewStyle">@android:style/Widget.GestureOverlayView.White</item>
+ <item name="expandableListViewStyle">@android:style/Widget.ExpandableListView.White</item>
<item name="listViewStyle">@android:style/Widget.ListView.White</item>
<item name="listDivider">@drawable/divider_horizontal_bright</item>
<item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator.White</item>
@@ -468,7 +471,6 @@
<!-- Theme for the search input bar. -->
<style name="Theme.SearchBar" parent="Theme.Panel">
- <item name="android:backgroundDimEnabled">true</item>
<item name="windowContentOverlay">@null</item>
</style>
@@ -514,6 +516,10 @@
<!-- Special theme for the recent apps dialog, to allow customization
with overlays. -->
<style name="Theme.Dialog.RecentApplications">
+ <item name="windowFrame">@null</item>
+ <item name="windowBackground">@android:color/transparent</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation.RecentApplications</item>
+ <item name="android:textColor">@android:color/secondary_text_nofocus</item>
</style>
</resources>
diff --git a/core/res/res/xml-land/password_kbd_qwerty.xml b/core/res/res/xml-land/password_kbd_qwerty.xml
new file mode 100755
index 0000000..700c527
--- /dev/null
+++ b/core/res/res/xml-land/password_kbd_qwerty.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="w"/>
+ <Key android:keyLabel="e"/>
+ <Key android:keyLabel="r"/>
+ <Key android:keyLabel="t"/>
+ <Key android:keyLabel="y"/>
+ <Key android:keyLabel="u"/>
+ <Key android:keyLabel="i"/>
+ <Key android:keyLabel="o"/>
+ <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="a" android:horizontalGap="5%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="s"/>
+ <Key android:keyLabel="d"/>
+ <Key android:keyLabel="f"/>
+ <Key android:keyLabel="g"/>
+ <Key android:keyLabel="h"/>
+ <Key android:keyLabel="j"/>
+ <Key android:keyLabel="k"/>
+ <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="z"/>
+ <Key android:keyLabel="x"/>
+ <Key android:keyLabel="c"/>
+ <Key android:keyLabel="v"/>
+ <Key android:keyLabel="b"/>
+ <Key android:keyLabel="n"/>
+ <Key android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p"/>
+ <Key android:keyLabel="=" />
+ <Key android:keyLabel="." android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-land/password_kbd_qwerty_shifted.xml b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..1e37b6c
--- /dev/null
+++ b/core/res/res/xml-land/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="w"/>
+ <Key android:keyLabel="e"/>
+ <Key android:keyLabel="r"/>
+ <Key android:keyLabel="t"/>
+ <Key android:keyLabel="y"/>
+ <Key android:keyLabel="u"/>
+ <Key android:keyLabel="i"/>
+ <Key android:keyLabel="o"/>
+ <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="s"/>
+ <Key android:keyLabel="d"/>
+ <Key android:keyLabel="f"/>
+ <Key android:keyLabel="g"/>
+ <Key android:keyLabel="h"/>
+ <Key android:keyLabel="j"/>
+ <Key android:keyLabel="k"/>
+ <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="z"/>
+ <Key android:keyLabel="x"/>
+ <Key android:keyLabel="c"/>
+ <Key android:keyLabel="v"/>
+ <Key android:keyLabel="b"/>
+ <Key android:keyLabel="n"/>
+ <Key android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p"/>
+ <Key android:keyLabel="+" />
+ <Key android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty.xml b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
new file mode 100755
index 0000000..bae1b42
--- /dev/null
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="=" />
+ <Key android:codes="46" android:keyLabel="."
+ android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..612df9c
--- /dev/null
+++ b/core/res/res/xml-mdpi/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="113" android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:codes="119" android:keyLabel="w"/>
+ <Key android:codes="101" android:keyLabel="e"/>
+ <Key android:codes="114" android:keyLabel="r"/>
+ <Key android:codes="116" android:keyLabel="t"/>
+ <Key android:codes="121" android:keyLabel="y"/>
+ <Key android:codes="117" android:keyLabel="u"/>
+ <Key android:codes="105" android:keyLabel="i"/>
+ <Key android:codes="111" android:keyLabel="o"/>
+ <Key android:codes="112" android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="97" android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:codes="115" android:keyLabel="s"/>
+ <Key android:codes="100" android:keyLabel="d"/>
+ <Key android:codes="102" android:keyLabel="f"/>
+ <Key android:codes="103" android:keyLabel="g"/>
+ <Key android:codes="104" android:keyLabel="h"/>
+ <Key android:codes="106" android:keyLabel="j"/>
+ <Key android:codes="107" android:keyLabel="k"/>
+ <Key android:codes="108" android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:codes="122" android:keyLabel="z"/>
+ <Key android:codes="120" android:keyLabel="x"/>
+ <Key android:codes="99" android:keyLabel="c"/>
+ <Key android:codes="118" android:keyLabel="v"/>
+ <Key android:codes="98" android:keyLabel="b"/>
+ <Key android:codes="110" android:keyLabel="n"/>
+ <Key android:codes="109" android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p" android:isRepeatable="true"/>
+ <Key android:keyLabel="+" />
+ <Key android:codes="46" android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_extension.xml b/core/res/res/xml/password_kbd_extension.xml
new file mode 100755
index 0000000..28b7efe
--- /dev/null
+++ b/core/res/res/xml/password_kbd_extension.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:keyLabel="!" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="\@"/>
+ <Key android:keyLabel="\#"/>
+ <Key android:keyLabel="&amp;"/>
+ <Key android:keyLabel="-"/>
+ <Key android:keyLabel="\'"/>
+ <Key android:keyLabel=":"/>
+ <Key android:keyLabel="&quot;"/>
+ <Key android:keyLabel="/"/>
+ <Key android:keyLabel="\?" android:keyEdgeFlags="right"
+ />
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:keyLabel="1" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="2"/>
+ <Key android:keyLabel="3"/>
+ <Key android:keyLabel="4"/>
+ <Key android:keyLabel="5"/>
+ <Key android:keyLabel="6"/>
+ <Key android:keyLabel="7"/>
+ <Key android:keyLabel="8"/>
+ <Key android:keyLabel="9"/>
+ <Key android:keyLabel="0" android:keyEdgeFlags="right"/>
+ </Row>
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_numeric.xml b/core/res/res/xml/password_kbd_numeric.xml
new file mode 100755
index 0000000..e3f1612
--- /dev/null
+++ b/core/res/res/xml/password_kbd_numeric.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="33.33%p"
+ android:horizontalGap="2px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:codes="49" android:keyIcon="@drawable/sym_keyboard_num1" android:keyEdgeFlags="left"/>
+ <Key android:codes="50" android:keyIcon="@drawable/sym_keyboard_num2"/>
+ <Key android:codes="51" android:keyIcon="@drawable/sym_keyboard_num3"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="52" android:keyIcon="@drawable/sym_keyboard_num4" android:keyEdgeFlags="left"/>
+ <Key android:codes="53" android:keyIcon="@drawable/sym_keyboard_num5"/>
+ <Key android:codes="54" android:keyIcon="@drawable/sym_keyboard_num6"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="55" android:keyIcon="@drawable/sym_keyboard_num7" android:keyEdgeFlags="left"/>
+ <Key android:codes="56" android:keyIcon="@drawable/sym_keyboard_num8"/>
+ <Key android:codes="57" android:keyIcon="@drawable/sym_keyboard_num9"/>
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"/>
+ <Key android:codes="48" android:keyIcon="@drawable/sym_keyboard_num0_no_plus"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_popup_template.xml b/core/res/res/xml/password_kbd_popup_template.xml
new file mode 100644
index 0000000..5ddfd3e
--- /dev/null
+++ b/core/res/res/xml/password_kbd_popup_template.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_qwerty.xml b/core/res/res/xml/password_kbd_qwerty.xml
new file mode 100755
index 0000000..5fa9b8a
--- /dev/null
+++ b/core/res/res/xml/password_kbd_qwerty.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:keyLabel="1" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="2"/>
+ <Key android:keyLabel="3"/>
+ <Key android:keyLabel="4"/>
+ <Key android:keyLabel="5"/>
+ <Key android:keyLabel="6"/>
+ <Key android:keyLabel="7"/>
+ <Key android:keyLabel="8"/>
+ <Key android:keyLabel="9"/>
+ <Key android:keyLabel="0" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="w"/>
+ <Key android:keyLabel="e"/>
+ <Key android:keyLabel="r"/>
+ <Key android:keyLabel="t"/>
+ <Key android:keyLabel="y"/>
+ <Key android:keyLabel="u"/>
+ <Key android:keyLabel="i"/>
+ <Key android:keyLabel="o"/>
+ <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="s"/>
+ <Key android:keyLabel="d"/>
+ <Key android:keyLabel="f"/>
+ <Key android:keyLabel="g"/>
+ <Key android:keyLabel="h"/>
+ <Key android:keyLabel="j"/>
+ <Key android:keyLabel="k"/>
+ <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="z"/>
+ <Key android:keyLabel="x"/>
+ <Key android:keyLabel="c"/>
+ <Key android:keyLabel="v"/>
+ <Key android:keyLabel="b"/>
+ <Key android:keyLabel="n"/>
+ <Key android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="-" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p"/>
+ <Key android:keyLabel="=" />
+ <Key android:keyLabel="."
+ android:keyWidth="10%p"/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_qwerty_shifted.xml b/core/res/res/xml/password_kbd_qwerty_shifted.xml
new file mode 100755
index 0000000..e491aff
--- /dev/null
+++ b/core/res/res/xml/password_kbd_qwerty_shifted.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row android:rowEdgeFlags="top">
+ <Key android:keyLabel="\@" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="\#"/>
+ <Key android:keyLabel="$"/>
+ <Key android:keyLabel="%"/>
+ <Key android:keyLabel="&amp;"/>
+ <Key android:keyLabel="*"/>
+ <Key android:keyLabel="-"/>
+ <Key android:keyLabel="+"/>
+ <Key android:keyLabel="("/>
+ <Key android:keyLabel=")" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="q" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="w"/>
+ <Key android:keyLabel="e"/>
+ <Key android:keyLabel="r"/>
+ <Key android:keyLabel="t"/>
+ <Key android:keyLabel="y"/>
+ <Key android:keyLabel="u"/>
+ <Key android:keyLabel="i"/>
+ <Key android:keyLabel="o"/>
+ <Key android:keyLabel="p" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="a" android:horizontalGap="5%p"
+ android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="s"/>
+ <Key android:keyLabel="d"/>
+ <Key android:keyLabel="f"/>
+ <Key android:keyLabel="g"/>
+ <Key android:keyLabel="h"/>
+ <Key android:keyLabel="j"/>
+ <Key android:keyLabel="k"/>
+ <Key android:keyLabel="l" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyIcon="@drawable/sym_keyboard_shift"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:iconPreview="@drawable/sym_keyboard_feedback_shift"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="z"/>
+ <Key android:keyLabel="x"/>
+ <Key android:keyLabel="c"/>
+ <Key android:keyLabel="v"/>
+ <Key android:keyLabel="b"/>
+ <Key android:keyLabel="n"/>
+ <Key android:keyLabel="m"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:keyboardMode="@+id/mode_normal" android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_symbol_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," />
+ <Key android:keyLabel="_" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"
+ android:keyWidth="20%p"/>
+ <Key android:keyLabel="+" />
+ <Key android:keyLabel="."/>
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"/>
+ </Row>
+
+</Keyboard>
+
diff --git a/core/res/res/xml/password_kbd_symbols.xml b/core/res/res/xml/password_kbd_symbols.xml
new file mode 100755
index 0000000..14a7ec8
--- /dev/null
+++ b/core/res/res/xml/password_kbd_symbols.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:keyLabel="1" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="2"/>
+ <Key android:keyLabel="3"/>
+ <Key android:keyLabel="4"/>
+ <Key android:keyLabel="5"/>
+ <Key android:keyLabel="6"/>
+ <Key android:keyLabel="7"/>
+ <Key android:keyLabel="8"/>
+ <Key android:keyLabel="9"/>
+ <Key android:keyLabel="0" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="\@" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="\#"/>
+ <Key android:keyLabel="$"/>
+ <Key android:keyLabel="%"/>
+ <Key android:keyLabel="&amp;"/>
+ <Key android:keyLabel="*"/>
+ <Key android:keyLabel="-"/>
+ <Key android:keyLabel="+"/>
+ <Key android:keyLabel="("
+ android:popupKeyboard="@xml/password_kbd_popup_template"
+ android:popupCharacters="[{&lt;"
+ />
+ <Key android:keyLabel=")" android:keyEdgeFlags="right"
+ android:popupKeyboard="@xml/password_kbd_popup_template"
+ android:popupCharacters="]}&gt;"
+ />
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="@string/password_keyboard_label_alt_key"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="!"/>
+ <Key android:keyLabel="&quot;"/>
+ <Key android:keyLabel="\'"/>
+ <Key android:keyLabel=":"/>
+ <Key android:keyLabel=";"/>
+ <Key android:keyLabel="/" />
+ <Key android:keyLabel="\?"/>
+ <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"/>
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_alpha_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="," android:keyWidth="10%p"/>
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:keyWidth="40%p"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"/>
+ <Key android:keyLabel="." android:keyWidth="10%p" />
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ />
+ </Row>
+</Keyboard>
diff --git a/core/res/res/xml/password_kbd_symbols_shift.xml b/core/res/res/xml/password_kbd_symbols_shift.xml
new file mode 100755
index 0000000..4b84f4b
--- /dev/null
+++ b/core/res/res/xml/password_kbd_symbols_shift.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="@dimen/password_keyboard_key_height"
+ >
+
+ <Row>
+ <Key android:keyLabel="~" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="`"/>
+ <Key android:keyLabel="|"/>
+ <Key android:keyLabel="•"/>
+ <Key android:keyLabel="√"/>
+ <Key android:keyLabel="Ï€"/>
+ <Key android:keyLabel="÷"/>
+ <Key android:keyLabel="×"/>
+ <Key android:keyLabel="{"/>
+ <Key android:keyLabel="}" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:keyLabel="Â¥" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="£"/>
+ <Key android:keyLabel="¢"/>
+ <Key android:keyLabel="€"/>
+ <Key android:keyLabel="°"/>
+ <Key android:keyLabel="^"/>
+ <Key android:keyLabel="_"/>
+ <Key android:keyLabel="="/>
+ <Key android:keyLabel="["/>
+ <Key android:keyLabel="]" android:keyEdgeFlags="right"/>
+ </Row>
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="@string/password_keyboard_label_alt_key"
+ android:keyWidth="15%p" android:isModifier="true"
+ android:isSticky="true" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="â„¢"/>
+ <Key android:keyLabel="®"/>
+ <Key android:keyLabel="©"/>
+ <Key android:keyLabel="¶"/>
+ <Key android:keyLabel="\\"/>
+ <Key android:keyLabel="&lt;"/>
+ <Key android:keyLabel="&gt;"/>
+ <Key android:codes="-5"
+ android:keyIcon="@drawable/sym_keyboard_delete"
+ android:keyWidth="15%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_delete"
+ android:isRepeatable="true"
+ />
+ </Row>
+
+ <Row android:rowEdgeFlags="bottom">
+ <Key android:codes="-2" android:keyLabel="@string/password_keyboard_label_alpha_key"
+ android:keyWidth="20%p" android:keyEdgeFlags="left"/>
+ <Key android:keyLabel="„" android:keyWidth="10%p" />
+ <Key android:codes="32" android:keyIcon="@drawable/sym_keyboard_space"
+ android:keyWidth="40%p"
+ android:iconPreview="@drawable/sym_keyboard_feedback_space"/>
+ <Key android:keyLabel="…" android:keyWidth="10%p" />
+ <Key android:codes="10" android:keyIcon="@drawable/sym_keyboard_ok"
+ android:keyWidth="20%p" android:keyEdgeFlags="right"
+ android:iconPreview="@drawable/sym_keyboard_feedback_ok"
+ />
+ </Row>
+</Keyboard>
diff --git a/core/tests/ConnectivityManagerTest/Android.mk b/core/tests/ConnectivityManagerTest/Android.mk
new file mode 100644
index 0000000..a1546fa
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ConnectivityManagerTest
+
+#LOCAL_INSTRUMENTATION_FOR := connectivitymanagertest
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/ConnectivityManagerTest/AndroidManifest.xml b/core/tests/ConnectivityManagerTest/AndroidManifest.xml
new file mode 100644
index 0000000..c318577
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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 name must be unique so suffix with "tests" so package loader doesn't ignore us -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.connectivitymanagertest">
+
+ <!-- We add an application tag here just so that we can indicate that
+ this package needs to link against the android.test library,
+ which is needed when building test cases. -->
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="ConnectivityManagerTestActivity"
+ android:label="CMTest">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.TEST" />
+ </intent-filter>
+ </activity>
+ </application>
+ <!--
+ This declares that this app uses the instrumentation test runner targeting
+ the package of browserpowertest. To run the tests use the command:
+ "adb shell am instrument -w com.android.connectivitymanagertest/.ConnectivityManagerTestRunner"
+ -->
+ <instrumentation android:name=".ConnectivityManagerTestRunner"
+ android:targetPackage="com.android.connectivitymanagertest"
+ android:label="Test runner for Connectivity Manager Tests"
+ />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+</manifest>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
new file mode 100644
index 0000000..6475655
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.connectivitymanagertest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import java.util.List;
+import android.widget.LinearLayout;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+
+
+/**
+ * An activity registered with connectivity manager broadcast
+ * provides network connectivity information and
+ * can be used to set device states: Cellular, Wifi, Airplane mode.
+ */
+public class ConnectivityManagerTestActivity extends Activity {
+
+ public static final String LOG_TAG = "ConnectivityManagerTestActivity";
+ public static final int WAIT_FOR_SCAN_RESULT = 5 * 1000; //5 seconds
+ public static final int WIFI_SCAN_TIMEOUT = 20 * 1000;
+ public ConnectivityReceiver mConnectivityReceiver = null;
+ public WifiReceiver mWifiReceiver = null;
+ /*
+ * Track network connectivity information
+ */
+ public State mState;
+ public NetworkInfo mNetworkInfo;
+ public NetworkInfo mOtherNetworkInfo;
+ public boolean mIsFailOver;
+ public String mReason;
+ public boolean mScanResultIsAvailable = false;
+ public ConnectivityManager mCM;
+ public Object wifiObject = new Object();
+ public Object connectivityObject = new Object();
+ public int mWifiState;
+ public NetworkInfo mWifiNetworkInfo;
+ public String mBssid;
+
+ /*
+ * Control Wifi States
+ */
+ public WifiManager mWifiManager;
+
+ /*
+ * Verify connectivity state
+ */
+ public static final int NUM_NETWORK_TYPES = ConnectivityManager.MAX_NETWORK_TYPE + 1;
+ NetworkState[] connectivityState = new NetworkState[NUM_NETWORK_TYPES];
+
+ /**
+ * A wrapper of a broadcast receiver which provides network connectivity information
+ * for all kinds of network: wifi, mobile, etc.
+ */
+ private class ConnectivityReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.v(LOG_TAG, "ConnectivityReceiver: onReceive() is called with " + intent);
+ String action = intent.getAction();
+ if (!action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ Log.v("ConnectivityReceiver", "onReceive() called with " + intent);
+ return;
+ }
+
+ boolean noConnectivity =
+ intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+
+ if (noConnectivity) {
+ mState = State.DISCONNECTED;
+ } else {
+ mState = State.CONNECTED;
+ }
+
+ mNetworkInfo = (NetworkInfo)
+ intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+
+ mOtherNetworkInfo = (NetworkInfo)
+ intent.getParcelableExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO);
+
+ mReason = intent.getStringExtra(ConnectivityManager.EXTRA_REASON);
+ mIsFailOver = intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, false);
+
+ Log.v(LOG_TAG, "mNetworkInfo: " + mNetworkInfo.toString());
+ if (mOtherNetworkInfo != null) {
+ Log.v(LOG_TAG, "mOtherNetworkInfo: " + mOtherNetworkInfo.toString());
+ }
+ recordNetworkState(mNetworkInfo.getType(), mNetworkInfo.getState());
+ if (mOtherNetworkInfo != null) {
+ recordNetworkState(mOtherNetworkInfo.getType(), mOtherNetworkInfo.getState());
+ }
+ notifyNetworkConnectivityChange();
+ }
+ }
+
+ private class WifiReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.v("WifiReceiver", "onReceive() is calleld with " + intent);
+ if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ notifyScanResult();
+ } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mWifiNetworkInfo =
+ (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ Log.v(LOG_TAG, "mWifiNetworkInfo: " + mWifiNetworkInfo.toString());
+ if (mWifiNetworkInfo.getState() == State.CONNECTED) {
+ mBssid = intent.getStringExtra(WifiManager.EXTRA_BSSID);
+ }
+ notifyWifiState();
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+ WifiManager.WIFI_STATE_UNKNOWN);
+ notifyWifiState();
+ }
+ else {
+ return;
+ }
+ }
+ }
+
+ public ConnectivityManagerTestActivity() {
+ mState = State.UNKNOWN;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.v(LOG_TAG, "onCreate, inst=" + Integer.toHexString(hashCode()));
+
+ // Create a simple layout
+ LinearLayout contentView = new LinearLayout(this);
+ contentView.setOrientation(LinearLayout.VERTICAL);
+ setContentView(contentView);
+ setTitle("ConnectivityManagerTestActivity");
+
+
+ // register a connectivity receiver for CONNECTIVITY_ACTION;
+ mConnectivityReceiver = new ConnectivityReceiver();
+ registerReceiver(mConnectivityReceiver,
+ new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+
+ mWifiReceiver = new WifiReceiver();
+ IntentFilter mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ registerReceiver(mWifiReceiver, mIntentFilter);
+
+ // Get an instance of ConnectivityManager
+ mCM = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+ // Get an instance of WifiManager
+ mWifiManager =(WifiManager)getSystemService(Context.WIFI_SERVICE);
+ initializeNetworkStates();
+
+ if (mWifiManager.isWifiEnabled()) {
+ Log.v(LOG_TAG, "Clear Wifi before we start the test.");
+ clearWifi();
+ }
+ }
+
+ // for each network type, initialize network states to UNKNOWN, and no verification flag is set
+ public void initializeNetworkStates() {
+ for (int networkType = NUM_NETWORK_TYPES - 1; networkType >=0; networkType--) {
+ connectivityState[networkType] = new NetworkState();
+ Log.v(LOG_TAG, "Initialize network state for " + networkType + ": " +
+ connectivityState[networkType].toString());
+ }
+ }
+
+ // deposit a network state
+ public void recordNetworkState(int networkType, State networkState) {
+ Log.v(LOG_TAG, "record network state for network " + networkType +
+ ", state is " + networkState);
+ connectivityState[networkType].recordState(networkState);
+ }
+
+ // set the state transition criteria
+ public void setStateTransitionCriteria(int networkType, State initState,
+ int transitionDir, State targetState) {
+ connectivityState[networkType].setStateTransitionCriteria(
+ initState, transitionDir, targetState);
+ }
+
+ // Validate the states recorded
+ public boolean validateNetworkStates(int networkType) {
+ Log.v(LOG_TAG, "validate network state for " + networkType + ": ");
+ return connectivityState[networkType].validateStateTransition();
+ }
+
+ // return result from network state validation
+ public String getTransitionFailureReason(int networkType) {
+ Log.v(LOG_TAG, "get network state transition failure reason for " + networkType + ": " +
+ connectivityState[networkType].toString());
+ return connectivityState[networkType].getReason();
+ }
+
+ private void notifyNetworkConnectivityChange() {
+ synchronized(connectivityObject) {
+ Log.v(LOG_TAG, "notify network connectivity changed");
+ connectivityObject.notifyAll();
+ }
+ }
+ private void notifyScanResult() {
+ synchronized (this) {
+ Log.v(LOG_TAG, "notify that scan results are available");
+ this.notify();
+ }
+ }
+
+ public void notifyWifiState() {
+ synchronized (wifiObject) {
+ Log.v(LOG_TAG, "notify wifi state changed");
+ wifiObject.notify();
+ }
+ }
+
+ // Return true if device is currently connected to mobile network
+ public boolean isConnectedToMobile() {
+ return (mNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE);
+ }
+
+ // Return true if device is currently connected to Wifi
+ public boolean isConnectedToWifi() {
+ return (mNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI);
+ }
+
+ public boolean enableWifi() {
+ return mWifiManager.setWifiEnabled(true);
+ }
+
+ /**
+ * Associate the device to given SSID
+ * If the device is already associated with a WiFi, disconnect and forget it,
+ * We don't verify whether the connection is successful or not, leave this to the test
+ */
+ public boolean connectToWifi(String knownSSID) {
+ //If Wifi is not enabled, enable it
+ if (!mWifiManager.isWifiEnabled()) {
+ Log.v(LOG_TAG, "Wifi is not enabled, enable it");
+ mWifiManager.setWifiEnabled(true);
+ }
+
+ List<ScanResult> netList = mWifiManager.getScanResults();
+ if (netList == null) {
+ // if no scan results are available, start active scan
+ mWifiManager.startScanActive();
+ mScanResultIsAvailable = false;
+ long startTime = System.currentTimeMillis();
+ while (!mScanResultIsAvailable) {
+ if ((System.currentTimeMillis() - startTime) > WIFI_SCAN_TIMEOUT) {
+ return false;
+ }
+ // wait for the scan results to be available
+ synchronized (this) {
+ // wait for the scan result to be available
+ try {
+ this.wait(WAIT_FOR_SCAN_RESULT);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if ((mWifiManager.getScanResults() == null) ||
+ (mWifiManager.getScanResults().size() <= 0)) {
+ continue;
+ }
+ mScanResultIsAvailable = true;
+ }
+ }
+ }
+
+ netList = mWifiManager.getScanResults();
+ for (int i = 0; i < netList.size(); i++) {
+ ScanResult sr= netList.get(i);
+ if (sr.SSID.equals(knownSSID)) {
+ Log.v(LOG_TAG, "found " + knownSSID + " in the scan result list");
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = sr.SSID;
+ config.allowedKeyManagement.set(KeyMgmt.NONE);
+ int networkId = mWifiManager.addNetwork(config);
+ // Connect to network by disabling others.
+ mWifiManager.enableNetwork(networkId, true);
+ mWifiManager.saveConfiguration();
+ mWifiManager.reconnect();
+ break;
+ }
+ }
+
+ List<WifiConfiguration> netConfList = mWifiManager.getConfiguredNetworks();
+ if (netConfList.size() <= 0) {
+ Log.v(LOG_TAG, knownSSID + " is not available");
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Disconnect from the current AP
+ */
+ public boolean disconnectAP() {
+ if (mWifiManager.isWifiEnabled()) {
+ //remove the current network Id
+ WifiInfo curWifi = mWifiManager.getConnectionInfo();
+ if (curWifi == null) {
+ return false;
+ }
+ int curNetworkId = curWifi.getNetworkId();
+ mWifiManager.removeNetwork(curNetworkId);
+ mWifiManager.saveConfiguration();
+
+ // remove other saved networks
+ List<WifiConfiguration> netConfList = mWifiManager.getConfiguredNetworks();
+ if (netConfList != null) {
+ Log.v(LOG_TAG, "remove configured network ids");
+ for (int i = 0; i < netConfList.size(); i++) {
+ WifiConfiguration conf = new WifiConfiguration();
+ conf = netConfList.get(i);
+ mWifiManager.removeNetwork(conf.networkId);
+ }
+ }
+ }
+ mWifiManager.saveConfiguration();
+ return true;
+ }
+ /**
+ * Disable Wifi
+ * @return true if Wifi is disabled successfully
+ */
+ public boolean disableWifi() {
+ return mWifiManager.setWifiEnabled(false);
+ }
+
+ /**
+ * Disconnect from the current Wifi and clear the configuration list
+ */
+ public boolean clearWifi() {
+ if (!disconnectAP()) {
+ return false;
+ }
+ // Disable Wifi
+ if (!mWifiManager.setWifiEnabled(false)) {
+ return false;
+ }
+ // Wait for the actions to be completed
+ try {
+ Thread.sleep(5*1000);
+ } catch (InterruptedException e) {}
+ return true;
+ }
+
+ /**
+ * Set airplane mode
+ */
+ public void setAirplaneMode(Context context, boolean enableAM) {
+ //set the airplane mode
+ Settings.System.putInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON,
+ enableAM ? 1 : 0);
+ // Post the intent
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", enableAM);
+ context.sendBroadcast(intent);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ //Unregister receiver
+ if (mConnectivityReceiver != null) {
+ unregisterReceiver(mConnectivityReceiver);
+ }
+ if (mWifiReceiver != null) {
+ unregisterReceiver(mWifiReceiver);
+ }
+ Log.v(LOG_TAG, "onDestroy, inst=" + Integer.toHexString(hashCode()));
+ }
+}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
new file mode 100644
index 0000000..592be92
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.connectivitymanagertest;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+import com.android.connectivitymanagertest.functional.ConnectivityManagerMobileTest;
+
+import junit.framework.TestSuite;
+
+/**
+ * Instrumentation Test Runner for all connectivity manager tests.
+ *
+ * To run the connectivity manager tests:
+ *
+ * adb shell am instrument -e ssid <ssid> \
+ * -w com.android.connectivitymanagertest/.ConnectivityManagerTestRunner
+ */
+
+public class ConnectivityManagerTestRunner extends InstrumentationTestRunner {
+ @Override
+ public TestSuite getAllTests() {
+ TestSuite suite = new InstrumentationTestSuite(this);
+ suite.addTestSuite(ConnectivityManagerMobileTest.class);
+ return suite;
+ }
+
+ @Override
+ public ClassLoader getLoader() {
+ return ConnectivityManagerTestRunner.class.getClassLoader();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ String testSSID = (String) icicle.get("ssid");
+ if (testSSID != null) {
+ TEST_SSID = testSSID;
+ }
+ }
+
+ public String TEST_SSID = null;
+}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java
new file mode 100644
index 0000000..d586396
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/NetworkState.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.connectivitymanagertest;
+
+import android.net.NetworkInfo.State;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class NetworkState {
+ public static final int TO_DISCONNECTION = 0; // transition to disconnection
+ public static final int TO_CONNECTION = 1; // transition to connection
+ public static final int DO_NOTHING = -1; // no state change
+ private final String LOG_TAG = "NetworkState";
+ private List<State> mStateDepository;
+ private State mTransitionTarget;
+ private int mTransitionDirection;
+ private String mReason = null; // record mReason of state transition failure
+
+ public NetworkState() {
+ mStateDepository = new ArrayList<State>();
+ mTransitionDirection = DO_NOTHING;
+ mTransitionTarget = State.UNKNOWN;
+ }
+
+ public NetworkState(State currentState) {
+ mStateDepository = new ArrayList<State>();
+ mStateDepository.add(currentState);
+ mTransitionDirection = DO_NOTHING;
+ mTransitionTarget = State.UNKNOWN;
+ }
+
+ // Reinitialize the network state
+ public void resetNetworkState() {
+ mStateDepository.clear();
+ mTransitionDirection = DO_NOTHING;
+ mTransitionTarget = State.UNKNOWN;
+ }
+
+ // set the transition criteria, transitionDir could be:
+ // DO_NOTHING, TO_CONNECTION, TO_DISCONNECTION
+ public void setStateTransitionCriteria(State initState, int transitionDir, State targetState) {
+ if (!mStateDepository.isEmpty()) {
+ mStateDepository.clear();
+ }
+ mStateDepository.add(initState);
+ mTransitionDirection = transitionDir;
+ mTransitionTarget = targetState;
+ Log.v(LOG_TAG, "setStateTransitionCriteria: " + printStates());
+ }
+
+ public void recordState(State currentState) {
+ mStateDepository.add(currentState);
+ }
+
+ // Verify state transition
+ public boolean validateStateTransition() {
+ Log.v(LOG_TAG, "print state depository: " + printStates());
+ if (mTransitionDirection == DO_NOTHING) {
+ if (mStateDepository.isEmpty()) {
+ Log.v(LOG_TAG, "no state is recorded");
+ mReason = "no state is recorded.";
+ return false;
+ } else if (mStateDepository.size() > 1) {
+ Log.v(LOG_TAG, "no broadcast is expected, " +
+ "instead broadcast is probably received");
+ mReason = "no broadcast is expected, instead broadcast is probably received";
+ return false;
+ } else if (mStateDepository.get(0) != mTransitionTarget) {
+ Log.v(LOG_TAG, mTransitionTarget + " is expected, but it is " +
+ mStateDepository.get(0));
+ mReason = mTransitionTarget + " is expected, but it is " + mStateDepository.get(0);
+ return false;
+ }
+ return true;
+ } else if (mTransitionDirection == TO_CONNECTION) {
+ Log.v(LOG_TAG, "transition to CONNECTED");
+ return transitToConnection();
+ } else {
+ Log.v(LOG_TAG, "transition to DISCONNECTED");
+ return transitToDisconnection();
+ }
+ }
+
+ /*
+ * Transition from CONNECTED -> DISCONNECTED:
+ * CONNECTED->DISCONNECTING->DISCONNECTED
+ * return false if any state transition is not valid and save a message in mReason
+ */
+ public boolean transitToDisconnection () {
+ mReason = "states: " + printStates();
+ if (mStateDepository.get(0) != State.CONNECTED) {
+ mReason += " initial state should be CONNECTED, but it is " +
+ mStateDepository.get(0) + ".";
+ return false;
+ }
+ State lastState = mStateDepository.get(mStateDepository.size() - 1);
+ if ( lastState != mTransitionTarget) {
+ mReason += " the last state should be DISCONNECTED, but it is " + lastState;
+ return false;
+ }
+ for (int i = 1; i < mStateDepository.size() - 1; i++) {
+ State preState = mStateDepository.get(i-1);
+ State curState = mStateDepository.get(i);
+ if ((preState == State.CONNECTED) && ((curState == State.DISCONNECTING) ||
+ (curState == State.DISCONNECTED))) {
+ continue;
+ } else if ((preState == State.DISCONNECTING) && (curState == State.DISCONNECTED)) {
+ continue;
+ } else if ((preState == State.DISCONNECTED) && (curState == State.DISCONNECTED)) {
+ continue;
+ } else {
+ mReason += " Transition state from " + preState.toString() + " to " +
+ curState.toString() + " is not valid.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // DISCONNECTED->CONNECTING->CONNECTED
+ public boolean transitToConnection() {
+ mReason = "states: " + printStates();
+ if (mStateDepository.get(0) != State.DISCONNECTED) {
+ mReason += " initial state should be DISCONNECTED, but it is " +
+ mStateDepository.get(0) + ".";
+ return false;
+ }
+ State lastState = mStateDepository.get(mStateDepository.size() - 1);
+ if ( lastState != mTransitionTarget) {
+ mReason += "The last state should be " + mTransitionTarget + ", but it is " + lastState;
+ return false;
+ }
+ for (int i = 1; i < mStateDepository.size(); i++) {
+ State preState = mStateDepository.get(i-1);
+ State curState = mStateDepository.get(i);
+ if ((preState == State.DISCONNECTED) && ((curState == State.CONNECTING) ||
+ (curState == State.CONNECTED) || (curState == State.DISCONNECTED))) {
+ continue;
+ } else if ((preState == State.CONNECTING) && (curState == State.CONNECTED)) {
+ continue;
+ } else if ((preState == State.CONNECTED) && (curState == State.CONNECTED)) {
+ continue;
+ } else {
+ mReason += " Transition state from " + preState.toString() + " to " +
+ curState.toString() + " is not valid.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public List<State> getTransitionStates() {
+ return mStateDepository;
+ }
+
+ // return state failure mReason
+ public String getReason() {
+ return mReason;
+ }
+
+ public String printStates() {
+ StringBuilder stateBuilder = new StringBuilder("");
+ for (int i = 0; i < mStateDepository.size(); i++) {
+ stateBuilder.append(" ").append(mStateDepository.get(i).toString()).append("->");
+ }
+ return stateBuilder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder(" ");
+ builder.append("mTransitionDirection: ").append(Integer.toString(mTransitionDirection)).
+ append("; ").append("states:").
+ append(printStates()).append("; ");
+ return builder.toString();
+ }
+}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
new file mode 100644
index 0000000..cdaefc8
--- /dev/null
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.connectivitymanagertest.functional;
+
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+
+import android.content.Intent;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.app.Instrumentation;
+import android.os.Handler;
+import android.os.Message;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.NetworkInfo.DetailedState;
+import android.net.wifi.WifiManager;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.ActivityInstrumentationTestCase2;
+import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
+import com.android.connectivitymanagertest.NetworkState;
+import android.util.Log;
+
+public class ConnectivityManagerMobileTest
+ extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
+ private static final String LOG_TAG = "ConnectivityManagerMobileTest";
+ private static final String PKG_NAME = "com.android.connectivitymanagertest";
+ private static final long STATE_TRANSITION_SHORT_TIMEOUT = 5 * 1000;
+ private static final long STATE_TRANSITION_LONG_TIMEOUT = 30 * 1000;
+
+ private String TEST_ACCESS_POINT;
+ private ConnectivityManagerTestActivity cmActivity;
+ private WakeLock wl;
+
+ public ConnectivityManagerMobileTest() {
+ super(PKG_NAME, ConnectivityManagerTestActivity.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ cmActivity = getActivity();
+ ConnectivityManagerTestRunner mRunner =
+ (ConnectivityManagerTestRunner)getInstrumentation();
+ TEST_ACCESS_POINT = mRunner.TEST_SSID;
+ PowerManager pm = (PowerManager)getInstrumentation().
+ getContext().getSystemService(Context.POWER_SERVICE);
+ wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "CMWakeLock");
+ wl.acquire();
+ // Each test case will start with cellular connection
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ verifyCellularConnection();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ cmActivity.finish();
+ Log.v(LOG_TAG, "tear down ConnectivityManagerTestActivity");
+ wl.release();
+ cmActivity.clearWifi();
+ super.tearDown();
+ }
+
+ // help function to verify 3G connection
+ public void verifyCellularConnection() {
+ NetworkInfo extraNetInfo = cmActivity.mNetworkInfo;
+ assertEquals("network type is not MOBILE", ConnectivityManager.TYPE_MOBILE,
+ extraNetInfo.getType());
+ assertTrue("not connected to cellular network", extraNetInfo.isConnected());
+ assertTrue("no data connection", cmActivity.mState.equals(State.CONNECTED));
+ }
+
+ // Wait for network connectivity state: CONNECTING, CONNECTED, SUSPENDED,
+ // DISCONNECTING, DISCONNECTED, UNKNOWN
+ private void waitForNetworkState(int networkType, State expectedState, long timeout) {
+ long startTime = System.currentTimeMillis();
+ // In case the broadcast is already sent out, no need to wait
+ if (cmActivity.mCM.getNetworkInfo(networkType).getState() == expectedState) {
+ return;
+ } else {
+ while (true) {
+ if ((System.currentTimeMillis() - startTime) > timeout) {
+ assertFalse("Wait for network state timeout", true);
+ }
+ Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType +
+ " to be " + expectedState.toString());
+ synchronized (cmActivity.connectivityObject) {
+ try {
+ cmActivity.connectivityObject.wait(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if ((cmActivity.mNetworkInfo.getType() != networkType) ||
+ (cmActivity.mNetworkInfo.getState() != expectedState)) {
+ Log.v(LOG_TAG, "network state for " + cmActivity.mNetworkInfo.getType() +
+ "is: " + cmActivity.mNetworkInfo.getState());
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Wait for Wifi state: WIFI_STATE_DISABLED, WIFI_STATE_DISABLING, WIFI_STATE_ENABLED,
+ // WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN
+ private void waitForWifiState(int expectedState, long timeout) {
+ long startTime = System.currentTimeMillis();
+ if (cmActivity.mWifiState == expectedState) {
+ return;
+ } else {
+ while (true) {
+ if ((System.currentTimeMillis() - startTime) > timeout) {
+ assertFalse("Wait for Wifi state timeout", true);
+ }
+ Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState);
+ synchronized (cmActivity.wifiObject) {
+ try {
+ cmActivity.wifiObject.wait(5*1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ if (cmActivity.mWifiState != expectedState) {
+ Log.v(LOG_TAG, "Wifi state is: " + cmActivity.mWifiNetworkInfo.getState());
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // Test case 1: Test enabling Wifi without associating with any AP
+ @LargeTest
+ public void test3GToWifiNotification() {
+ // To avoid UNKNOWN state when device boots up
+ cmActivity.enableWifi();
+ try {
+ Thread.sleep(2 * STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ cmActivity.disableWifi();
+ // As Wifi stays in DISCONNECTED, the connectivity manager will not broadcast
+ // any network connectivity event for Wifi
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE, networkInfo.getState(),
+ NetworkState.DO_NOTHING, State.CONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.DO_NOTHING, State.DISCONNECTED);
+ // Eanble Wifi
+ cmActivity.enableWifi();
+ try {
+ Thread.sleep(2 * STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // validate state and broadcast
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "the state for WIFI is changed");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue("state validation fail", false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "the state for MOBILE is changed");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue("state validation fail", false);
+ }
+ // Verify that the device is still connected to MOBILE
+ verifyCellularConnection();
+ }
+
+ // Test case 2: test connection to a given AP
+ @LargeTest
+ public void testConnectToWifi() {
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+ //Prepare for connectivity verification
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE, networkInfo.getState(),
+ NetworkState.TO_DISCONNECTION, State.DISCONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.TO_CONNECTION, State.CONNECTED);
+
+ // Enable Wifi and connect to a test access point
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+
+ waitForWifiState(WifiManager.WIFI_STATE_ENABLED, STATE_TRANSITION_LONG_TIMEOUT);
+ Log.v(LOG_TAG, "wifi state is enabled");
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // validate states
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "Mobile state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue(false);
+ }
+ }
+
+ // Test case 3: connect to Wifi with known AP
+ @LargeTest
+ public void testConnectToWifWithKnownAP() {
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+ // Connect to TEST_ACCESS_POINT
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+ waitForWifiState(WifiManager.WIFI_STATE_ENABLED, STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // Disable Wifi
+ Log.v(LOG_TAG, "Disable Wifi");
+ if (!cmActivity.disableWifi()) {
+ Log.v(LOG_TAG, "disable Wifi failed");
+ return;
+ }
+
+ // Wait for the Wifi state to be DISABLED
+ waitForWifiState(WifiManager.WIFI_STATE_DISABLED, STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ //Prepare for connectivity state verification
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
+ networkInfo.getState(), NetworkState.DO_NOTHING,
+ State.DISCONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.TO_CONNECTION, State.CONNECTED);
+
+ // Enable Wifi again
+ Log.v(LOG_TAG, "Enable Wifi again");
+ cmActivity.enableWifi();
+
+ // Wait for Wifi to be connected and mobile to be disconnected
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // validate wifi states
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ }
+
+ // Test case 4: test disconnect Wifi
+ @LargeTest
+ public void testDisconnectWifi() {
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+
+ // connect to Wifi
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // Wait for a few seconds to avoid the state that both Mobile and Wifi is connected
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
+ networkInfo.getState(),
+ NetworkState.TO_CONNECTION,
+ State.CONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.TO_DISCONNECTION, State.DISCONNECTED);
+
+ // clear Wifi
+ cmActivity.clearWifi();
+
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // validate states
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "Mobile state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue(false);
+ }
+ }
+
+ // Test case 5: test connectivity from 3G to airplane mode, then to 3G again
+ @LargeTest
+ public void testDataConnectionWith3GToAmTo3G() {
+ //Prepare for state verification
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
+ networkInfo.getState(),
+ NetworkState.TO_DISCONNECTION,
+ State.DISCONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ assertEquals(State.DISCONNECTED, networkInfo.getState());
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.DO_NOTHING, State.DISCONNECTED);
+
+ // Enable airplane mode
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // Validate the state transition
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "Mobile state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue(false);
+ }
+
+ // reset state recorder
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
+ networkInfo.getState(),
+ NetworkState.TO_CONNECTION,
+ State.CONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.DO_NOTHING, State.DISCONNECTED);
+
+ // disable airplane mode
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
+
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // Validate the state transition
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "Mobile state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue(false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ }
+
+ // Test case 6: test connectivity with airplane mode Wifi connected
+ @LargeTest
+ public void testDataConnectionOverAMWithWifi() {
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+ // Eanble airplane mode
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
+
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
+ networkInfo.getState(),
+ NetworkState.DO_NOTHING,
+ State.DISCONNECTED);
+ networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI, networkInfo.getState(),
+ NetworkState.TO_CONNECTION, State.CONNECTED);
+
+ // Connect to Wifi
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // validate state and broadcast
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "state validate for Wifi failed");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue("State validation failed", false);
+ }
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
+ Log.v(LOG_TAG, "state validation for Mobile failed");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_MOBILE));
+ assertTrue("state validation failed", false);
+ }
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
+ }
+
+ // Test case 7: test connectivity while transit from Wifi->AM->Wifi
+ @LargeTest
+ public void testDataConnectionWithWifiToAMToWifi () {
+ // Connect to TEST_ACCESS_POINT
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+ // Connect to Wifi
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // Enable airplane mode without clearing Wifi
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
+
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // Prepare for state validation
+ NetworkInfo networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ assertEquals(State.DISCONNECTED, networkInfo.getState());
+ cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_WIFI,
+ networkInfo.getState(), NetworkState.TO_CONNECTION, State.CONNECTED);
+
+ // Disable airplane mode
+ cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
+
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ // validate the state transition
+ if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_WIFI)) {
+ Log.v(LOG_TAG, "Wifi state transition validation failed.");
+ Log.v(LOG_TAG, "reason: " +
+ cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
+ assertTrue(false);
+ }
+ }
+
+ // Test case 8: test wifi state change while connecting/disconnecting to/from an AP
+ @LargeTest
+ public void testWifiStateChange () {
+ assertNotNull("SSID is null", TEST_ACCESS_POINT);
+ //Connect to TEST_ACCESS_POINT
+ assertTrue("failed to connect to " + TEST_ACCESS_POINT,
+ cmActivity.connectToWifi(TEST_ACCESS_POINT));
+ waitForWifiState(WifiManager.WIFI_STATE_ENABLED, STATE_TRANSITION_LONG_TIMEOUT);
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+ assertNotNull("Not associated with any AP",
+ cmActivity.mWifiManager.getConnectionInfo().getBSSID());
+
+ try {
+ Thread.sleep(STATE_TRANSITION_SHORT_TIMEOUT);
+ } catch (Exception e) {
+ Log.v(LOG_TAG, "exception: " + e.toString());
+ }
+
+ // Disconnect from the current AP
+ Log.v(LOG_TAG, "disconnect from the AP");
+ if (!cmActivity.disconnectAP()) {
+ Log.v(LOG_TAG, "failed to disconnect from " + TEST_ACCESS_POINT);
+ }
+
+ // Verify the connectivity state for Wifi is DISCONNECTED
+ waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
+ STATE_TRANSITION_LONG_TIMEOUT);
+
+ if (!cmActivity.disableWifi()) {
+ Log.v(LOG_TAG, "disable Wifi failed");
+ return;
+ }
+ waitForWifiState(WifiManager.WIFI_STATE_DISABLED, STATE_TRANSITION_LONG_TIMEOUT);
+ }
+}
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
new file mode 100644
index 0000000..245c67c
--- /dev/null
+++ b/core/tests/coretests/Android.mk
@@ -0,0 +1,23 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src) \
+ $(call all-java-files-under, DisabledTestApp/src) \
+ $(call all-java-files-under, EnabledTestApp/src)
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-common
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := FrameworksCoreTests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
new file mode 100644
index 0000000..a77717f
--- /dev/null
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -0,0 +1,1210 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.coretests"
+ android:sharedUserId="com.android.uid.test">
+
+ <permission android:name="com.android.frameworks.coretests.permission.TEST_GRANTED"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_testGranted"
+ android:description="@string/permdesc_testGranted">
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
+ </permission>
+ <permission android:name="com.android.frameworks.coretests.permission.TEST_DENIED"
+ android:protectionLevel="normal"
+ android:label="@string/permlab_testDenied"
+ android:description="@string/permdesc_testDenied" />
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
+ <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+ <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+ <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_LOGS"/>
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_SMS"/>
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SMS"/>
+ <uses-permission android:name="android.permission.TEST_GRANTED" />
+ <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
+
+ <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+
+ <!-- location test permissions -->
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.HARDWARE_TEST" />
+
+ <!-- package manager test permissions -->
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
+ <uses-permission android:name="android.permission.MOVE_PACKAGE" />
+
+ <!--os storage test permissions -->
+ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
+ <uses-permission android:name="android.permission.ASEC_ACCESS" />
+ <uses-permission android:name="android.permission.ASEC_CREATE" />
+ <uses-permission android:name="android.permission.ASEC_DESTROY" />
+ <uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT" />
+ <uses-permission android:name="android.permission.ASEC_RENAME" />
+ <uses-permission android:name="android.permission.SHUTDOWN" />
+
+ <application android:theme="@style/Theme">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="StubTestBrowserActivity" android:label="Stubbed Test Browser">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.FOR_TESTS_ONLY"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.test.TestBrowserTests" android:label="Test Browser Tests">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.UNIT_TEST"/>
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.DescendantFocusability" android:label="DescendantFocusability">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.FocusAfterRemoval" android:label="FocusAfterRemoval">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.RequestFocus" android:label="RequestFocus">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.ListOfButtons" android:label="ListOfButtons">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.LinearLayoutGrid" android:label="LinearLayoutGrid">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.ListOfEditTexts" android:label="ListOfEditTexts">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.ListOfInternalSelectionViews" android:label="ListOfInternalSelectionViews">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.ListWithFooterViewAndNewLabels" android:label="FocusListWithFooter">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.ListWithMailMessages" android:label="ListWithMailMessages">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.HorizontalFocusSearch" android:label="HorizontalFocusSearch">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.VerticalFocusSearch" android:label="VerticalFocusSearch">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.AdjacentVerticalRectLists" android:label="AdjacentVerticalRectLists">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.focus.GoneParentFocusedChild" android:label="GoneParentFocusedChild">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.frame.FrameLayoutGravity" android:label="FrameLayoutGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.frame.FrameLayoutMargin" android:label="FrameLayoutMargin">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.BaselineAlignmentCenterGravity" android:label="BaselineAlignmentCenterGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.BaselineButtons" android:label="BaselineButtons">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.FillInWrap" android:label="FillInWrap">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.BaselineAlignmentZeroWidthAndWeight" android:label="Baseline0WidthAndWeight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.HorizontalOrientationVerticalAlignment" android:label="HorizontalOrientationVerticalAlignment">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.LLEditTextThenButton" android:label="LLEditTextThenButton">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.LLOfButtons1" android:label="LLOfButtons1">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.LinearLayoutEditTexts" android:label="LinearLayoutEditTexts">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.LLOfButtons2" android:label="LLOfButtons2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.LLOfTwoFocusableInTouchMode" android:label="LLOfTwoFocusableInTouchMode">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.Weight" android:label="Weight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.linear.WeightSum" android:label="WeightSum">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.AdjacentListsWithAdjacentISVsInside" android:label="AdjacentListsWithAdjacentISVsInside">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListDividers" android:label="ListDividers">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListViewHeight" android:label="ListViewHeight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.FixedWidth" android:label="CellFixedWidth">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.Weight" android:label="CellWeight">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.HorizontalGravity" android:label="CellHorizontalGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.VerticalGravity" android:label="CellVerticalGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.AddColumn" android:label="AddColumnInTable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.layout.table.CellSpan" android:label="CellSpan">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.ButtonAboveTallInternalSelectionView" android:label="ButtonAboveTallInternalSelectionView">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.ButtonsWithTallTextViewInBetween" android:label="scrollButtonsWithTallTextViewInBetween">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.RequestRectangleVisible" android:label="ScrollToChildRect">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.RequestRectangleVisibleWithInternalScroll" android:label="ScrollToChildRectWithInternalScroll">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.ScrollViewButtonsAndLabels" android:label="ScrollViewButtonsAndLabels">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.ShortButtons" android:label="scrollShortButtons">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.scroll.TallTextAboveButton" android:label="scrollTallTextAboveButton">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.Include" android:label="IncludeTag">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.Merge" android:label="MergeTag">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.StubbedView" android:label="ViewStub">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.RunQueue" android:label="RunQueue">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.Visibility" android:label="Visibility">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.VisibilityCallback" android:label="VisibilityCallback">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.BigCache" android:label="BigCache">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.ZeroSized" android:label="ZeroSized">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.Disabled" android:label="Disabled">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.PopupWindowVisibility" android:label="PopupWindowVisibility">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.PreDrawListener" android:label="PreDrawListener">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.GlobalFocusChange" android:label="GlobalFocusChange">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListSetSelection" android:label="ListSetSelection">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListSimple" android:label="ListSimple">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListFilter" android:label="ListFilter">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListScrollListener" android:label="ListScrollListener">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListThrasher" android:label="ListThrasher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListTakeFocusFromSide" android:label="ListTakeFocusFromSide">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListBottomGravity" android:label="ListBottomGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListBottomGravityMany" android:label="ListBottomGravityMany">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+
+ <activity android:name="android.widget.listview.ListButtonsDiagonalAcrossItems" android:label="ListButtonsDiagonalAcrossItems">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListTopGravity" android:label="ListTopGravity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListTopGravityMany" android:label="ListTopGravityMany">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListEndingWithMultipleSeparators" android:label="ListEndingWithMultipleSeparators">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListGetSelectedView" android:label="ListGetSelectedView">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListInHorizontal" android:label="ListInHorizontal">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListInVertical" android:label="ListInVertical">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListInterleaveFocusables" android:label="ListInterleaveFocusables">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfItemsShorterThanScreen" android:label="ListOfItemsShorterThanScreen">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfItemsTallerThanScreen" android:label="ListOfItemsTallerThanScreen">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfThinItems" android:label="ListOfThinItems">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfShortTallShort" android:label="ListOfShortTallShort">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfShortShortTallShortShort" android:label="ListOfShortShortTallShortShort">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithOffScreenNextSelectable" android:label="ListWithOffScreenNextSelectable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithFirstScreenUnSelectable" android:label="ListWithFirstScreenUnSelectable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+
+ <activity android:name="android.widget.listview.ListWithSeparators" android:label="ListWithSeparators">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithHeaders" android:label="ListWithHeaders">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithEditTextHeader" android:label="ListWithEditTextHeader">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithNoFadingEdge" android:label="ListWithNoFadingEdge">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithScreenOfNoSelectables" android:label="ListWithScreenOfNoSelectables">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListItemFocusablesFarApart" android:label="ListItemFocusablesFarApart">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListItemFocusableAboveUnfocusable" android:label="ListItemFocusableAboveUnfocusable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListItemFocusablesClose" android:label="ListItemFocusablesClose">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListLastItemPartiallyVisible" android:label="ListLastItemPartiallyVisible">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListItemsExpandOnSelection" android:label="ListItemsExpandOnSelection">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithOnItemSelectedAction" android:label="ListWithOnItemSelectedAction">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListItemISVAndButton" android:label="ListItemISVAndButton">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListOfTouchables" android:label="ListOfTouchables">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListRecyclerProfiling" android:label="ListRecyclerProfiling">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListHeterogeneous" android:label="ListHeterogeneous">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListHorizontalFocusWithinItemWins" android:label="ListHorizontalFocusWithinItemWins">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListManagedCursor" android:label="ListManagedCursor">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithEmptyView" android:label="ListWithEmptyView">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridInHorizontal" android:label="GridInHorizontal">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridPadding" android:label="GridPadding">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridInVertical" android:label="GridInVertical">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridScrollListener" android:label="GridScrollListener">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridThrasher" android:label="GridThrasher">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSimple" android:label="GridSimple">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridDelete" android:label="GridDelete">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSetSelection" android:label="GridSetSelection">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSetSelectionMany" android:label="GridSetSelectionMany">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSetSelectionStackFromBottom" android:label="GridSetSelectionStackFromBottom">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSetSelectionStackFromBottomMany" android:label="GridSetSelectionStackFromBottomMany">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridStackFromBottom" android:label="GridStackFromBottom">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridStackFromBottomMany" android:label="GridStackFromBottomMany">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridVerticalSpacing" android:label="GridVerticalSpacing">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridVerticalSpacingStackFromBottom" android:label="GridVerticalSpacingStackFromBottom">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.gridview.GridSingleColumn" android:label="GridSingleColumn">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.menu.ListContextMenu" android:label="ListContextMenu">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.ViewGroupChildren" android:label="ViewGroup Children">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.RemoteViewsActivity" android:label="RemoteViewsActicity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.BitmapDrawable" android:label="BitmapDrawable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.DrawableBgMinSize" android:label="DrawableBgMinSize">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.MutateDrawable" android:label="MutateDrawable">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.app.TranslucentFancyActivity" android:label="TranslucentFancyActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.Longpress" android:label="Longpress">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.expandablelistview.ExpandableListWithHeaders" android:label="ExpandableListWithHeaders">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.listview.ListWithDisappearingItemBug" android:label="ListWithDisappearingItemBug">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.menu.MenuWith1Item" android:label="MenuWith1Item">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".menus.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
+ android:screenOrientation="portrait">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.view.menu.MenuLayoutLandscape" android:label="MenuLayoutLandscape"
+ android:screenOrientation="landscape">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.expandablelistview.InflatedExpandableListView" android:label="ExpandableListView Inflated">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.expandablelistview.ExpandableListSimple" android:label="ExpandableListSimple">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.os.BrightnessLimit" android:label="BrightnessLimit">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name="android.widget.AutoCompleteTextViewSimple"
+ android:label="AutoCompleteTextViewSimple">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ <service android:name="android.accessibilityservice.AccessibilityTestService">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService" />
+ </intent-filter>
+ </service>
+
+ <activity android:name="android.widget.RadioGroupActivity" android:label="RadioGroupActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+
+
+ <!-- Activity-level metadata -->
+ <meta-data android:name="com.android.frameworks.coretests.isApp" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference"
+ android:resource="@xml/metadata_app" />
+
+ <activity android:name="AndroidPerformanceTests" android:label="Android Performance Tests">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.UNIT_TEST" />
+ </intent-filter>
+ </activity>
+
+ <!-- Application components used for activity tests -->
+
+ <activity android:name="android.app.activity.TestedActivity"
+ android:process=":remoteActivity">
+ </activity>
+ <activity android:name="android.app.activity.LocalActivity" android:multiprocess="true">
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
+ </activity>
+ <activity android:name="android.app.activity.TestedScreen"
+ android:process=":remoteScreen">
+ </activity>
+ <activity android:name="android.app.activity.LocalScreen" android:multiprocess="true">
+ </activity>
+ <activity android:name="android.app.activity.ClearTop" android:multiprocess="true"
+ android:launchMode="singleTop">
+ </activity>
+ <activity android:name="android.app.activity.LocalDialog" android:multiprocess="true"
+ android:theme="@android:style/Theme.Dialog">
+ </activity>
+ <activity android:name="android.app.activity.SubActivityScreen">
+ </activity>
+ <activity android:name="android.app.activity.RemoteSubActivityScreen"
+ android:process=":remoteActivity">
+ </activity>
+ <activity android:name="android.app.activity.LaunchpadActivity" android:multiprocess="true">
+ </activity>
+ <activity android:name="android.app.activity.LaunchpadTabActivity" android:multiprocess="true">
+ </activity>
+
+ <receiver android:name="android.app.activity.AbortReceiver">
+ <intent-filter android:priority="1">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.LocalReceiver">
+ <intent-filter android:priority="-1">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ALL" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REPEAT" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_FAIL_REGISTER" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_FAIL_BIND" />
+ </intent-filter>
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
+ </receiver>
+ <receiver android:name="android.app.activity.ResultReceiver">
+ <intent-filter>
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_RESULT" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.LocalGrantedReceiver"
+ android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED">
+ <intent-filter android:priority="-1">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL_GRANTED" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.LocalDeniedReceiver"
+ android:permission="com.android.frameworks.coretests.permission.TEST_DENIED">
+ <intent-filter android:priority="-1">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_LOCAL_DENIED" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.RemoteReceiver"
+ android:process=":remoteReceiver">
+ <intent-filter android:priority="2">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ABORT" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_ALL" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REPEAT" />
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.RemoteGrantedReceiver"
+ android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED">
+ <intent-filter android:priority="2">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_GRANTED" />
+ </intent-filter>
+ </receiver>
+ <receiver android:name="android.app.activity.RemoteDeniedReceiver"
+ android:permission="com.android.frameworks.coretests.permission.TEST_DENIED">
+ <intent-filter android:priority="2">
+ <action android:name="com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED" />
+ </intent-filter>
+ </receiver>
+ <service android:name="android.app.activity.LocalService">
+ <intent-filter>
+ <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL" />
+ </intent-filter>
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
+ </service>
+ <service android:name="android.app.activity.LocalDeniedService"
+ android:permission="com.android.frameworks.coretests.permission.TEST_DENIED">
+ <intent-filter>
+ <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED" />
+ </intent-filter>
+ </service>
+ <service android:name="android.app.activity.LocalGrantedService"
+ android:permission="com.android.frameworks.coretests.permission.TEST_GRANTED">
+ <intent-filter>
+ <action android:name="com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED" />
+ </intent-filter>
+ </service>
+
+ <provider android:name="android.app.activity.LocalProvider"
+ android:authorities="com.android.frameworks.coretests.LocalProvider">
+ <meta-data android:name="com.android.frameworks.coretests.string" android:value="foo" />
+ <meta-data android:name="com.android.frameworks.coretests.boolean" android:value="true" />
+ <meta-data android:name="com.android.frameworks.coretests.integer" android:value="100" />
+ <meta-data android:name="com.android.frameworks.coretests.color" android:value="#ff000000" />
+ <meta-data android:name="com.android.frameworks.coretests.float" android:value="100.1" />
+ <meta-data android:name="com.android.frameworks.coretests.reference" android:resource="@xml/metadata" />
+ </provider>
+
+ <!-- Application components used for content tests -->
+ <provider android:name="android.content.MemoryFileProvider"
+ android:authorities="android.content.MemoryFileProvider"
+ android:process=":MemoryFileProvider">
+ </provider>
+
+ <!-- Application components used for os tests -->
+
+ <service android:name="android.os.MessengerService"
+ android:process=":messengerService" />
+
+ <!-- Used by BinderThreadPriorityTest -->
+ <service android:name="android.os.BinderThreadPriorityService"
+ android:process=":BinderThreadPriorityService" />
+
+ <!-- Application components used for search manager tests -->
+
+ <activity android:name="android.app.activity.SearchableActivity"
+ android:label="Searchable Activity">
+ <intent-filter>
+ <action android:name="android.intent.action.SEARCH" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="android.app.searchable"
+ android:resource="@xml/searchable" />
+ </activity>
+
+ <provider android:name="android.app.SuggestionProvider"
+ android:authorities="android.app.SuggestionProvider">
+ </provider>
+
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.frameworks.coretests"
+ android:label="Frameworks Core Tests" />
+</manifest>
diff --git a/core/tests/coretests/DisabledTestApp/Android.mk b/core/tests/coretests/DisabledTestApp/Android.mk
new file mode 100644
index 0000000..a5daedf
--- /dev/null
+++ b/core/tests/coretests/DisabledTestApp/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := DisabledTestApp
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/DisabledTestApp/AndroidManifest.xml b/core/tests/coretests/DisabledTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5bd840f
--- /dev/null
+++ b/core/tests/coretests/DisabledTestApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.disabled_app"
+ android:sharedUserId="com.android.uid.test">
+
+ <application enabled="false">
+
+ <!-- Used to test package component enabling and disabling -->
+ <activity android:name=".DisabledActivity" android:enabled="false" >
+ </activity>
+ <activity android:name=".EnabledActivity" >
+ </activity>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/DisabledTestApp/src/com/android/frameworks/coretests/disabled_app/EnabledActivity.java b/core/tests/coretests/DisabledTestApp/src/com/android/frameworks/coretests/disabled_app/EnabledActivity.java
new file mode 100644
index 0000000..e676231
--- /dev/null
+++ b/core/tests/coretests/DisabledTestApp/src/com/android/frameworks/coretests/disabled_app/EnabledActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.disabled_app;
+
+import android.app.Activity;
+
+/**
+ * Empty Activity for testing
+ */
+
+public class EnabledActivity extends Activity {
+
+}
diff --git a/core/tests/coretests/EnabledTestApp/Android.mk b/core/tests/coretests/EnabledTestApp/Android.mk
new file mode 100644
index 0000000..4b986d3
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := EnabledTestApp
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/EnabledTestApp/AndroidManifest.xml b/core/tests/coretests/EnabledTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..72d933a
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/AndroidManifest.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.enabled_app"
+ android:sharedUserId="com.android.uid.test">
+
+ <application>
+
+ <!-- Used to test package component enabling and disabling -->
+ <activity android:name=".DisabledActivity" android:enabled="false" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.frameworks.coretests.enabled_app.TEST_CATEGORY" />
+ </intent-filter>
+ </activity>
+ <service android:name=".DisabledService" android:enabled="false" >
+ </service>
+ <receiver android:name=".DisabledReceiver" android:enabled="false" >
+ <intent-filter>
+ <action android:name="android.intent.action.ENABLED_APP_DISABLED_RECEIVER" />
+ </intent-filter>
+ </receiver>
+ <provider android:name=".DisabledProvider" android:enabled="false"
+ android:authorities="com.android.frameworks.coretests.enabled_app.DisabledProvider"
+ android:process=":disabled.provider.process" />
+ <activity android:name=".EnabledActivity" >
+ </activity>
+ <service android:name=".EnabledService" android:enabled="true" >
+ </service>
+ <receiver android:name=".EnabledReceiver" >
+ <intent-filter>
+ <action android:name="android.intent.action.ENABLED_APP_ENABLED_RECEIVER" />
+ </intent-filter>
+ </receiver>
+ <provider android:name=".EnabledProvider"
+ android:authorities="com.android.frameworks.coretests.enabled_app.EnabledProvider"
+ android:process=":enabled.provider.process" />
+ </application>
+</manifest>
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledActivity.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledActivity.java
new file mode 100644
index 0000000..325adfe
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.app.Activity;
+
+/**
+ * Empty Activity for testing
+ */
+
+public class DisabledActivity extends Activity {
+
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledProvider.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledProvider.java
new file mode 100644
index 0000000..26781c4
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty ContentProvider for testing
+ */
+
+public class DisabledProvider extends ContentProvider {
+
+ public boolean onCreate() {
+ return false;
+ }
+
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledReceiver.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledReceiver.java
new file mode 100644
index 0000000..b06d2ce
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Empty IntentReceiver for testing
+ */
+
+public class DisabledReceiver extends BroadcastReceiver {
+
+ public void onReceive(Context context, Intent intent) {
+
+ }
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledService.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledService.java
new file mode 100644
index 0000000..ac66ae5
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/DisabledService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.app.Service;
+import android.os.IBinder;
+import android.content.Intent;
+
+/**
+ * Empty Service for testing
+ */
+
+public class DisabledService extends Service {
+
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledActivity.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledActivity.java
new file mode 100644
index 0000000..1b0ac6d
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.app.Activity;
+
+/**
+ * Empty Activity for testing
+ */
+
+public class EnabledActivity extends Activity {
+
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledProvider.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledProvider.java
new file mode 100644
index 0000000..8da70b4
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledProvider.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty ContentProvider for testing
+ */
+
+public class EnabledProvider extends ContentProvider {
+
+ public boolean onCreate() {
+ return false;
+ }
+
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledReceiver.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledReceiver.java
new file mode 100644
index 0000000..6cee98d
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Empty IntentReceiver for testing
+ */
+
+public class EnabledReceiver extends BroadcastReceiver {
+
+ public void onReceive(Context context, Intent intent) {
+
+ }
+}
diff --git a/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledService.java b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledService.java
new file mode 100644
index 0000000..7d05db0
--- /dev/null
+++ b/core/tests/coretests/EnabledTestApp/src/com/android/frameworks/coretests/enabled_app/EnabledService.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.enabled_app;
+
+import android.app.Service;
+import android.os.IBinder;
+import android.content.Intent;
+
+/**
+ * Empty Service for testing
+ */
+
+public class EnabledService extends Service {
+
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/core/tests/coretests/apks/Android.mk b/core/tests/coretests/apks/Android.mk
new file mode 100644
index 0000000..4670e21
--- /dev/null
+++ b/core/tests/coretests/apks/Android.mk
@@ -0,0 +1,5 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# build sub packages
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/coretests/apks/install_decl_perm/Android.mk b/core/tests/coretests/apks/install_decl_perm/Android.mk
new file mode 100644
index 0000000..c38e981
--- /dev/null
+++ b/core/tests/coretests/apks/install_decl_perm/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_decl_perm
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml b/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml
new file mode 100644
index 0000000..9a1f0ff
--- /dev/null
+++ b/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_decl_perm">
+
+ <permission android:name="com.android.frameworks.coretests.NORMAL"
+ android:permissionGroup="android.permission-group.COST_MONEY"
+ android:protectionLevel="normal"
+ android:label="test normal perm" />
+
+ <permission android:name="com.android.frameworks.coretests.DANGEROUS"
+ android:permissionGroup="android.permission-group.COST_MONEY"
+ android:protectionLevel="dangerous"
+ android:label="test dangerous perm" />
+
+ <permission android:name="com.android.frameworks.coretests.SIGNATURE"
+ android:permissionGroup="android.permission-group.COST_MONEY"
+ android:protectionLevel="signature"
+ android:label="test signature perm" />
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml b/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_loc_auto/Android.mk b/core/tests/coretests/apks/install_loc_auto/Android.mk
new file mode 100644
index 0000000..2deb978
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_auto/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_loc_auto
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml b/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml
new file mode 100644
index 0000000..5a903e2
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="auto"
+ package="com.android.frameworks.coretests.install_loc">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml b/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_loc_internal/Android.mk b/core/tests/coretests/apks/install_loc_internal/Android.mk
new file mode 100644
index 0000000..784bf0a
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_internal/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_loc_internal
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml b/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml
new file mode 100644
index 0000000..2568f37
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.coretests.install_loc">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml b/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_loc_sdcard/Android.mk b/core/tests/coretests/apks/install_loc_sdcard/Android.mk
new file mode 100644
index 0000000..4eea322
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_sdcard/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_loc_sdcard
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml b/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml
new file mode 100644
index 0000000..647f4e5
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="preferExternal"
+ package="com.android.frameworks.coretests.install_loc">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml b/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_loc_unspecified/Android.mk b/core/tests/coretests/apks/install_loc_unspecified/Android.mk
new file mode 100644
index 0000000..206c99f
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_unspecified/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_loc_unspecified
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml b/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml
new file mode 100644
index 0000000..07b1eb3
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_loc">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml b/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_use_perm_good/Android.mk b/core/tests/coretests/apks/install_use_perm_good/Android.mk
new file mode 100644
index 0000000..1a07fc8
--- /dev/null
+++ b/core/tests/coretests/apks/install_use_perm_good/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_use_perm_good
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml b/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml
new file mode 100644
index 0000000..dadce7d
--- /dev/null
+++ b/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_use_perm_good">
+
+ <uses-permission android:name="com.android.frameworks.coretests.NORMAL" />
+ <uses-permission android:name="com.android.frameworks.coretests.DANGEROUS" />
+ <uses-permission android:name="com.android.frameworks.coretests.SIGNATURE" />
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml b/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/assets/text.txt b/core/tests/coretests/assets/text.txt
new file mode 100644
index 0000000..3d8c519
--- /dev/null
+++ b/core/tests/coretests/assets/text.txt
@@ -0,0 +1 @@
+OneTwoThreeFourFiveSixSevenEightNineTen \ No newline at end of file
diff --git a/core/tests/coretests/res/drawable-hdpi/big_drawable_background.9.png b/core/tests/coretests/res/drawable-hdpi/big_drawable_background.9.png
new file mode 100644
index 0000000..53470b8
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/big_drawable_background.9.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/black_square.png b/core/tests/coretests/res/drawable-hdpi/black_square.png
new file mode 100644
index 0000000..7752103
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/black_square.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/black_square_stretchable.9.png b/core/tests/coretests/res/drawable-hdpi/black_square_stretchable.9.png
new file mode 100644
index 0000000..4988163
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/black_square_stretchable.9.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/drawable_background.9.png b/core/tests/coretests/res/drawable-hdpi/drawable_background.9.png
new file mode 100644
index 0000000..f692d38
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/drawable_background.9.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/sym_now_playing_pause_1.png b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_pause_1.png
new file mode 100644
index 0000000..9edb064
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_pause_1.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_backward_1.png b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_backward_1.png
new file mode 100644
index 0000000..c4b6b92
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_backward_1.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_forward_1.png b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_forward_1.png
new file mode 100644
index 0000000..03140f5
--- /dev/null
+++ b/core/tests/coretests/res/drawable-hdpi/sym_now_playing_skip_forward_1.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-mdpi/big_drawable_background.9.png b/core/tests/coretests/res/drawable-mdpi/big_drawable_background.9.png
new file mode 100644
index 0000000..aded635
--- /dev/null
+++ b/core/tests/coretests/res/drawable-mdpi/big_drawable_background.9.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-mdpi/black_square.png b/core/tests/coretests/res/drawable-mdpi/black_square.png
new file mode 100644
index 0000000..1bfe0a2
--- /dev/null
+++ b/core/tests/coretests/res/drawable-mdpi/black_square.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-mdpi/black_square_stretchable.9.png b/core/tests/coretests/res/drawable-mdpi/black_square_stretchable.9.png
new file mode 100644
index 0000000..1fcbeb1
--- /dev/null
+++ b/core/tests/coretests/res/drawable-mdpi/black_square_stretchable.9.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable-mdpi/drawable_background.9.png b/core/tests/coretests/res/drawable-mdpi/drawable_background.9.png
new file mode 100644
index 0000000..1337ba9
--- /dev/null
+++ b/core/tests/coretests/res/drawable-mdpi/drawable_background.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_pause_1.png
index f537b3b..2f19d08 100644
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png
+++ b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_pause_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_backward_1.png
index f1f2ff5..f945a81 100644
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png
+++ b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_backward_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_forward_1.png
index 087e650..da9361a 100644
--- a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png
+++ b/core/tests/coretests/res/drawable-mdpi/sym_now_playing_skip_forward_1.png
Binary files differ
diff --git a/core/tests/coretests/res/drawable/bitmap_drawable.xml b/core/tests/coretests/res/drawable/bitmap_drawable.xml
new file mode 100644
index 0000000..35673ec
--- /dev/null
+++ b/core/tests/coretests/res/drawable/bitmap_drawable.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:gravity="top|right" />
diff --git a/core/res/res/drawable/spinnerbox_arrows.xml b/core/tests/coretests/res/drawable/box.xml
index 276a0f0..6849bd3 100644
--- a/core/res/res/drawable/spinnerbox_arrows.xml
+++ b/core/tests/coretests/res/drawable/box.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/* //device/apps/common/res/drawable/spinnerbox_arrows.xml
+/* //device/apps/common/res/drawable/box.xml
**
** Copyright 2007, The Android Open Source Project
**
@@ -18,10 +18,9 @@
*/
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_single="true" android:drawable="@drawable/spinnerbox_arrow_single" />
- <item android:state_first="true" android:drawable="@drawable/spinnerbox_arrow_first" />
- <item android:state_last="true" android:drawable="@drawable/spinnerbox_arrow_last" />
- <item android:state_middle="true" android:drawable="@drawable/spinnerbox_arrow_middle" />
- <item android:state_pressed="true" android:drawable="@drawable/spinnerbox_arrow_single" />
-</selector>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="#00000000"/>
+ <stroke android:width="1dp" color="#ff000000"/>
+ <padding android:left="1dp" android:top="1dp"
+ android:right="1dp" android:bottom="1dp" />
+</shape>
diff --git a/core/tests/coretests/res/layout/add_column_in_table.xml b/core/tests/coretests/res/layout/add_column_in_table.xml
new file mode 100644
index 0000000..05f55a8
--- /dev/null
+++ b/core/tests/coretests/res/layout/add_column_in_table.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TableLayout android:id="@+id/table"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0">
+
+ <TableRow>
+ <TextView
+ android:text="@string/table_layout_a"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_b"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_c"
+ android:padding="3dip" />
+ </TableRow>
+
+ </TableLayout>
+
+ <Button android:id="@+id/add_row_button"
+ android:text="@string/add_row_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/autocompletetextview_simple.xml b/core/tests/coretests/res/layout/autocompletetextview_simple.xml
new file mode 100644
index 0000000..1a20076
--- /dev/null
+++ b/core/tests/coretests/res/layout/autocompletetextview_simple.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <AutoCompleteTextView
+ android:id="@+id/autocompletetextview1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="text|textAutoComplete"
+ android:completionThreshold="1" />
+ />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
new file mode 100644
index 0000000..acbb10b
--- /dev/null
+++ b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:visibility="gone">
+ <android.widget.layout.linear.ExceptionTextView
+ android:id="@+id/routeToField"
+ android:textSize="16sp"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:lines="2"
+ android:autoText="false"
+ android:capitalize="none"
+ android:maxLines="1" />
+ <Button
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="fill_vertical"
+ android:text="@string/side_button_label" />
+ </LinearLayout>
+ <Button android:id="@+id/show"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:text="@string/show" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/baseline_buttons.xml b/core/tests/coretests/res/layout/baseline_buttons.xml
new file mode 100644
index 0000000..3e364bd
--- /dev/null
+++ b/core/tests/coretests/res/layout/baseline_buttons.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <TextView android:id="@+id/currenttime"
+ android:text="@string/time"
+ android:textSize="12sp"
+ android:textStyle="bold"
+ android:layout_gravity="bottom"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center">
+
+ <ImageButton android:id="@+id/prev"
+ android:src="@drawable/sym_now_playing_skip_backward_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton android:id="@+id/pause"
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton android:id="@+id/next"
+ android:src="@drawable/sym_now_playing_skip_forward_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <TextView android:id="@+id/totaltime"
+ android:textSize="12sp"
+ android:textStyle="bold"
+ android:gravity="right"
+ android:text="@string/time"
+ android:layout_gravity="bottom"
+ android:layout_weight="1"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/baseline_center_gravity.xml b/core/tests/coretests/res/layout/baseline_center_gravity.xml
new file mode 100644
index 0000000..dd1318d
--- /dev/null
+++ b/core/tests/coretests/res/layout/baseline_center_gravity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <Button android:id="@+id/button1"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/side_button_label" />
+ <Button android:id="@+id/button2"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/side_button_label" />
+ <Button android:id="@+id/button3"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/side_button_label" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/brightness_limit.xml b/core/tests/coretests/res/layout/brightness_limit.xml
new file mode 100644
index 0000000..46e5767
--- /dev/null
+++ b/core/tests/coretests/res/layout/brightness_limit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Tries to set brightness to 0. See corresponding Java code. -->
+
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/go"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/go"/>
+
diff --git a/core/tests/coretests/res/layout/descendant_focusability.xml b/core/tests/coretests/res/layout/descendant_focusability.xml
new file mode 100644
index 0000000..0cb75fd
--- /dev/null
+++ b/core/tests/coretests/res/layout/descendant_focusability.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+ <LinearLayout
+ android:id="@+id/beforeDescendants"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:descendantFocusability="beforeDescendants"
+ >
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/afterDescendants"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:descendantFocusability="afterDescendants"
+ >
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/blocksDescendants"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:descendantFocusability="blocksDescendants"
+ >
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/disabled.xml b/core/tests/coretests/res/layout/disabled.xml
new file mode 100644
index 0000000..4b41248
--- /dev/null
+++ b/core/tests/coretests/res/layout/disabled.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates changing view visibility. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/clickableParent"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button android:id="@+id/disabledButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disabled_button"/>
+
+ <Button android:id="@+id/disabledButtonA"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disabled_button"/>
+
+ <Button android:id="@+id/disabledButtonB"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disabled_button"/>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/drawable_background_minimum_size.xml b/core/tests/coretests/res/layout/drawable_background_minimum_size.xml
new file mode 100644
index 0000000..ea0cbfa
--- /dev/null
+++ b/core/tests/coretests/res/layout/drawable_background_minimum_size.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <Button
+ android:id="@+id/change_backgrounds"
+ android:text="@string/change_backgrounds"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <!-- Different views and layouts that should initially be small but still contain
+ some content (hence the minimal text inside each of the layouts). Each of these
+ will each be tested to make sure they expand to at least the minimum size
+ recommended by different backgrounds -->
+
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/minimal_text" />
+
+ <LinearLayout
+ android:id="@+id/linear_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/minimal_text" />
+
+ </LinearLayout>
+
+ <RelativeLayout
+ android:id="@+id/relative_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/minimal_text" />
+
+ </RelativeLayout>
+
+ <FrameLayout
+ android:id="@+id/frame_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/minimal_text" />
+
+ </FrameLayout>
+
+ <AbsoluteLayout
+ android:id="@+id/absolute_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/minimal_text" />
+
+ </AbsoluteLayout>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/fill_in_wrap.xml b/core/tests/coretests/res/layout/fill_in_wrap.xml
new file mode 100644
index 0000000..1c3f811
--- /dev/null
+++ b/core/tests/coretests/res/layout/fill_in_wrap.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:gravity="center_vertical">
+
+ <Button
+ android:layout_width="100dip"
+ android:layout_height="wrap_content"
+ android:gravity="left|center_vertical"
+ />
+
+ <LinearLayout android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingLeft="0dip"
+ android:paddingRight="0dip"
+ android:paddingTop="2dip"
+ android:paddingBottom="2dip"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:addStatesFromChildren="true"
+ android:background="@android:drawable/edit_text"
+ android:gravity="center_vertical">
+
+ <EditText android:id="@+id/data"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="8dip"
+ android:paddingBottom="4dip"
+ android:layout_gravity="center_vertical"
+ android:background="@null"
+ />
+
+ <ImageButton
+ style="@android:style/Widget.Button.Inset"
+ android:src="@android:drawable/ic_delete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="2dip"
+ android:gravity="center"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/focus_after_removal.xml b/core/tests/coretests/res/layout/focus_after_removal.xml
new file mode 100644
index 0000000..f4e388d
--- /dev/null
+++ b/core/tests/coretests/res/layout/focus_after_removal.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout android:id="@+id/leftLayout"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/topLeftButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/left_top" />
+ <Button android:id="@+id/bottomLeftButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/left_bottom" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/topRightButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/right_top" />
+ <Button android:id="@+id/bottomRightButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/right_bottom" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/focus_listener.xml b/core/tests/coretests/res/layout/focus_listener.xml
new file mode 100644
index 0000000..6faa21c
--- /dev/null
+++ b/core/tests/coretests/res/layout/focus_listener.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/left"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/left_top" />
+
+ <Button android:id="@+id/right"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="3dip"
+ android:text="@string/left_bottom" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/framelayout_gravity.xml b/core/tests/coretests/res/layout/framelayout_gravity.xml
new file mode 100644
index 0000000..e89fce5
--- /dev/null
+++ b/core/tests/coretests/res/layout/framelayout_gravity.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button android:id="@+id/left"
+ android:layout_gravity="left"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/right"
+ android:layout_gravity="right"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/center_horizontal"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/left_center_vertical"
+ android:layout_gravity="center_vertical|left"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/right_center_vertical"
+ android:layout_gravity="center_vertical|right"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/center"
+ android:layout_gravity="center"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/left_bottom"
+ android:layout_gravity="bottom|left"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/right_bottom"
+ android:layout_gravity="bottom|right"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/center_horizontal_bottom"
+ android:layout_gravity="bottom|center_horizontal"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/core/tests/coretests/res/layout/framelayout_margin.xml b/core/tests/coretests/res/layout/framelayout_margin.xml
new file mode 100644
index 0000000..9120bcb
--- /dev/null
+++ b/core/tests/coretests/res/layout/framelayout_margin.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button android:id="@+id/left"
+ android:layout_gravity="left"
+ android:layout_marginLeft="12dip"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/right"
+ android:layout_gravity="right"
+ android:layout_marginRight="12dip"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/top"
+ android:layout_gravity="top|center_horizontal"
+ android:layout_marginTop="12dip"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/bottom"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginBottom="12dip"
+ android:text="@string/view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/core/tests/coretests/res/layout/grid_in_horizontal.xml b/core/tests/coretests/res/layout/grid_in_horizontal.xml
new file mode 100644
index 0000000..62d7bcb
--- /dev/null
+++ b/core/tests/coretests/res/layout/grid_in_horizontal.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridView android:id="@+id/grid"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:padding="10dip"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:numColumns="auto_fit"
+ android:columnWidth="60dp"
+ android:stretchMode="columnWidth" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/grid_in_vertical.xml b/core/tests/coretests/res/layout/grid_in_vertical.xml
new file mode 100644
index 0000000..fea459a
--- /dev/null
+++ b/core/tests/coretests/res/layout/grid_in_vertical.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridView android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:numColumns="auto_fit" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/grid_padding.xml b/core/tests/coretests/res/layout/grid_padding.xml
new file mode 100644
index 0000000..bd666e1
--- /dev/null
+++ b/core/tests/coretests/res/layout/grid_padding.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<GridView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/grid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dip"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:numColumns="auto_fit"
+ android:columnWidth="50dp" />
diff --git a/core/tests/coretests/res/layout/grid_scroll_listener.xml b/core/tests/coretests/res/layout/grid_scroll_listener.xml
new file mode 100644
index 0000000..82b1058
--- /dev/null
+++ b/core/tests/coretests/res/layout/grid_scroll_listener.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridView android:id="@+id/grid"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:padding="10dip"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:numColumns="auto_fit"
+ android:columnWidth="60dp"
+ android:stretchMode="columnWidth"
+ android:gravity="center"/>
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/grid_thrasher.xml b/core/tests/coretests/res/layout/grid_thrasher.xml
new file mode 100644
index 0000000..346acff
--- /dev/null
+++ b/core/tests/coretests/res/layout/grid_thrasher.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <GridView android:id="@+id/grid"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dip"
+ android:verticalSpacing="10dp"
+ android:horizontalSpacing="10dp"
+ android:numColumns="auto_fit"
+ android:columnWidth="60dp"
+ android:stretchMode="columnWidth"
+ android:gravity="center"/>
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/include_button.xml b/core/tests/coretests/res/layout/include_button.xml
new file mode 100644
index 0000000..5857fa3
--- /dev/null
+++ b/core/tests/coretests/res/layout/include_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/included_button"
+ android:text="@string/include_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
diff --git a/core/tests/coretests/res/layout/include_button_with_size.xml b/core/tests/coretests/res/layout/include_button_with_size.xml
new file mode 100644
index 0000000..0cd8eaa
--- /dev/null
+++ b/core/tests/coretests/res/layout/include_button_with_size.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/included_button"
+ android:text="@string/include_button"
+ android:layout_width="23dip"
+ android:layout_height="23dip" />
diff --git a/core/tests/coretests/res/layout/include_tag.xml b/core/tests/coretests/res/layout/include_tag.xml
new file mode 100644
index 0000000..b2f6ab1
--- /dev/null
+++ b/core/tests/coretests/res/layout/include_tag.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <include
+ layout="@layout/include_button" />
+
+ <include
+ layout="@layout/include_button"
+ android:id="@+id/included_button_overriden"
+ android:layout_width="237dip"
+ android:layout_height="123dip" />
+
+ <include
+ layout="@layout/include_button"
+ android:id="@+id/included_button_visibility"
+ android:visibility="invisible" />
+
+ <include
+ layout="@layout/include_button_with_size"
+ android:id="@+id/included_button_with_size" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/inflated_expandablelistview.xml b/core/tests/coretests/res/layout/inflated_expandablelistview.xml
new file mode 100644
index 0000000..6f683e5
--- /dev/null
+++ b/core/tests/coretests/res/layout/inflated_expandablelistview.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ExpandableListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/elv"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent" />
diff --git a/core/tests/coretests/res/layout/layout_five.xml b/core/tests/coretests/res/layout/layout_five.xml
new file mode 100644
index 0000000..9923eaf
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_five.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <TextView android:id="@+id/text" android:text="@string/layout_five_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/layout_four.xml b/core/tests/coretests/res/layout/layout_four.xml
new file mode 100644
index 0000000..e5a78bc
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_four.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text" android:text="@string/layout_four_text_text"/>
diff --git a/core/tests/coretests/res/layout/layout_one.xml b/core/tests/coretests/res/layout/layout_one.xml
new file mode 100644
index 0000000..6966246
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_one.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<view xmlns:android="http://schemas.android.com/apk/res/android" class="android.view.InflateTest$ViewOne" android:id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/>
diff --git a/core/tests/coretests/res/layout/layout_six.xml b/core/tests/coretests/res/layout/layout_six.xml
new file mode 100644
index 0000000..b78082d
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_six.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView android:id="@+id/text" android:text="@string/layout_six_text_text" android:layout_width="match_parent" android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/layout_tag.xml b/core/tests/coretests/res/layout/layout_tag.xml
new file mode 100644
index 0000000..7fb0489
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_tag.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+ class="android.view.InflateTest$ViewOne"
+ android:id="@+id/viewOne" android:tag="MyTag"
+ android:layout_width="match_parent" android:layout_height="match_parent"/>
diff --git a/core/tests/coretests/res/layout/layout_three.xml b/core/tests/coretests/res/layout/layout_three.xml
new file mode 100644
index 0000000..7242fc8
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_three.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view1" android:layout_width="match_parent" android:layout_height="match_parent"/>
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view2" android:layout_width="match_parent" android:layout_height="match_parent"/>
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view3" android:layout_width="match_parent" android:layout_height="match_parent"/>
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view4" android:layout_width="match_parent" android:layout_height="match_parent"/>
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view5" android:layout_width="match_parent" android:layout_height="match_parent"/>
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/view6" android:layout_width="match_parent" android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/layout_two.xml b/core/tests/coretests/res/layout/layout_two.xml
new file mode 100644
index 0000000..9fb7d3b
--- /dev/null
+++ b/core/tests/coretests/res/layout/layout_two.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <view class="android.view.InflateTest$ViewOne" android:id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/>
+</LinearLayout>
+
diff --git a/core/tests/coretests/res/layout/linear_layout_buttons.xml b/core/tests/coretests/res/layout/linear_layout_buttons.xml
new file mode 100644
index 0000000..bcb28e7
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_buttons.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:text="@string/keypad_one"/>
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:text="@string/keypad_two"/>
+
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:text="@string/keypad_three"/>
+
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
new file mode 100644
index 0000000..ab76e29
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"/>
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:text="@string/keypad_one"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_grid.xml b/core/tests/coretests/res/layout/linear_layout_grid.xml
new file mode 100644
index 0000000..67b6877
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_grid.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/baseline_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout android:id="@+id/column1"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_one"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_two"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_three"/>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/column2"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_four"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_five"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_six"/>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/column3"
+ android:layout_width="match_parent"
+ android:layout_height="0sp"
+ android:layout_weight="1"
+ android:orientation="horizontal">
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_seven"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_eight"/>
+
+ <Button
+ android:layout_width="0sp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:text="@string/keypad_nine"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_listview_height.xml b/core/tests/coretests/res/layout/linear_layout_listview_height.xml
new file mode 100644
index 0000000..873b2d2
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_listview_height.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Outer linear layout providing vertical layout -->
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <!-- The control buttons -->
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" >
+
+ <Button
+ android:id="@+id/button1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/linear_listheight_fixed"/>
+
+ <Button
+ android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/linear_listheight_fill"/>
+
+ <Button
+ android:id="@+id/button3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/linear_listheight_hide"/>
+ </LinearLayout>
+
+ <!-- The list view -->
+ <ListView
+ android:id="@+id/inner_list"
+ android:layout_width="200dip"
+ android:layout_height="match_parent"
+ android:background="@android:drawable/spinner_dropdown_background"
+ android:divider="@android:drawable/divider_horizontal_bright" />
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_spinner_then_button.xml b/core/tests/coretests/res/layout/linear_layout_spinner_then_button.xml
new file mode 100644
index 0000000..53c0280
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_spinner_then_button.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner android:id="@+id/reminder_value"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.7"
+ android:entries="@array/reminder_minutes_labels"/>
+
+ <Button android:id="@+id/reminder_remove"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.3"
+ android:text="@string/show" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_textviews.xml b/core/tests/coretests/res/layout/linear_layout_textviews.xml
new file mode 100644
index 0000000..ccec213
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_textviews.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:background="#FFFF0000"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <EditText
+ android:id="@+id/editText1"
+ android:text="@string/text"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ <EditText
+ android:id="@+id/editText2"
+ android:text="@string/text"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/linear_layout_weight.xml b/core/tests/coretests/res/layout/linear_layout_weight.xml
new file mode 100644
index 0000000..bb138df
--- /dev/null
+++ b/core/tests/coretests/res/layout/linear_layout_weight.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:background="#FFFFFFFF"
+ android:orientation="horizontal"
+ android:layout_width="87dip"
+ android:layout_height="wrap_content">
+
+ <View
+ android:id="@+id/child1"
+ android:background="#FFFF0000"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ <View
+ android:id="@+id/child2"
+ android:background="#FF00FF00"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ <View
+ android:background="#FF0000FF"
+ android:id="@+id/child3"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+ <View
+ android:background="#FFFF00FF"
+ android:id="@+id/child4"
+ android:layout_width="20dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1.0" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_dividers.xml b/core/tests/coretests/res/layout/list_dividers.xml
new file mode 100644
index 0000000..93810b4
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_dividers.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ListView android:id="@android:id/list"
+ android:paddingRight="15dip"
+ android:layout_marginLeft="50dip"
+ android:layout_width="150dip"
+ android:layout_height="300dip" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_filter.xml b/core/tests/coretests/res/layout/list_filter.xml
new file mode 100644
index 0000000..0ab7101
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_filter.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout android:id="@+id/frame"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:drawSelectorOnTop="false" />
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/hide"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/hide" />
+
+ <Button android:id="@+id/show"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show" />
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_in_horizontal.xml b/core/tests/coretests/res/layout/list_in_horizontal.xml
new file mode 100644
index 0000000..b770628
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_in_horizontal.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TableRow>
+
+ <ListView android:id="@+id/list" />
+
+ <ImageButton android:src="@drawable/sym_now_playing_pause_1" />
+
+ </TableRow>
+
+</TableLayout>
diff --git a/core/tests/coretests/res/layout/list_in_vertical.xml b/core/tests/coretests/res/layout/list_in_vertical.xml
new file mode 100644
index 0000000..f951cb7
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_in_vertical.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <ListView android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/list_recycler_profiling.xml b/core/tests/coretests/res/layout/list_recycler_profiling.xml
new file mode 100644
index 0000000..3fc6bd6
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_recycler_profiling.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0" />
+
+ <ImageButton android:id="@+id/pause"
+ android:src="@drawable/sym_now_playing_pause_1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_scroll_listener.xml b/core/tests/coretests/res/layout/list_scroll_listener.xml
new file mode 100644
index 0000000..58ab55f
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_scroll_listener.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"/>
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_take_focus_from_side.xml b/core/tests/coretests/res/layout/list_take_focus_from_side.xml
new file mode 100644
index 0000000..ee40019
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_take_focus_from_side.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:drawSelectorOnTop="false"/>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/side_button_label" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_thrasher.xml b/core/tests/coretests/res/layout/list_thrasher.xml
new file mode 100644
index 0000000..58ab55f
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_thrasher.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"/>
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_with_button_above.xml b/core/tests/coretests/res/layout/list_with_button_above.xml
new file mode 100644
index 0000000..18bcf6b
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_with_button_above.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Simple layout with a button and a list below, can be used to build more
+ sophisticated test cases by adding stuff to the list programatically.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <Button android:id="@+id/button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/button_above_list_label"/>
+
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:paddingTop="2dip"
+ android:drawSelectorOnTop="false"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_with_disappearing_item_bug_item.xml b/core/tests/coretests/res/layout/list_with_disappearing_item_bug_item.xml
new file mode 100644
index 0000000..82af6ca
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_with_disappearing_item_bug_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- A layout is needed to reprod the bug. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight">
+
+ <TextView
+ android:id="@+id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:gravity="center_vertical"
+ android:paddingLeft="27dip"
+ />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/list_with_empty_view.xml b/core/tests/coretests/res/layout/list_with_empty_view.xml
new file mode 100644
index 0000000..23bb658
--- /dev/null
+++ b/core/tests/coretests/res/layout/list_with_empty_view.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <ListView android:id="@android:id/list"
+ android:drawSelectorOnTop="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ <TextView android:id="@+id/empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="36sp"
+ android:textColor="#999"
+ android:visibility="gone"
+ android:text="@string/empty_list" />
+
+</FrameLayout>
diff --git a/core/tests/coretests/res/layout/longpress.xml b/core/tests/coretests/res/layout/longpress.xml
new file mode 100644
index 0000000..3a69fae
--- /dev/null
+++ b/core/tests/coretests/res/layout/longpress.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View android:id="@+id/simple_view"
+ android:background="@drawable/blue"
+ android:layout_width="20dip"
+ android:layout_height="20dip"
+ android:focusable="true" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/mail_message.xml b/core/tests/coretests/res/layout/mail_message.xml
new file mode 100644
index 0000000..7f15e4f
--- /dev/null
+++ b/core/tests/coretests/res/layout/mail_message.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- A layout similar to a gmail message. useful for setting up tests, like
+ a list with a list of messages.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/subject"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <WebView android:id="@+id/body"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/merge_child.xml b/core/tests/coretests/res/layout/merge_child.xml
new file mode 100644
index 0000000..9972924
--- /dev/null
+++ b/core/tests/coretests/res/layout/merge_child.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</merge>
diff --git a/core/tests/coretests/res/layout/merge_tag.xml b/core/tests/coretests/res/layout/merge_tag.xml
new file mode 100644
index 0000000..9eec493
--- /dev/null
+++ b/core/tests/coretests/res/layout/merge_tag.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:text="@string/include_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <include layout="@layout/merge_child" />
+
+</merge>
diff --git a/core/tests/coretests/res/layout/popup_window_visibility.xml b/core/tests/coretests/res/layout/popup_window_visibility.xml
new file mode 100644
index 0000000..3aa714d
--- /dev/null
+++ b/core/tests/coretests/res/layout/popup_window_visibility.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <FrameLayout android:id="@+id/frame"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Spinner android:id="@+id/spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <AutoCompleteTextView android:id="@+id/auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/hide"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/hide" />
+
+ <Button android:id="@+id/show"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show" />
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/pre_draw_listener.xml b/core/tests/coretests/res/layout/pre_draw_listener.xml
new file mode 100644
index 0000000..f431f14
--- /dev/null
+++ b/core/tests/coretests/res/layout/pre_draw_listener.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <ScrollView
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+
+ <view class="android.view.PreDrawListener$MyLinearLayout" android:id="@+id/frame"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </ScrollView>
+
+ <Button android:id="@+id/go"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/go" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/radiogroup_checkedchild.xml b/core/tests/coretests/res/layout/radiogroup_checkedchild.xml
new file mode 100644
index 0000000..db88f4d
--- /dev/null
+++ b/core/tests/coretests/res/layout/radiogroup_checkedchild.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <RadioButton
+ android:id="@+id/value_one"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textColor="#555555"
+ android:checked="true"
+ android:text="@string/visibility_1_view_1" />
+
+ <RadioButton
+ android:id="@+id/value_two"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textColor="#555555"
+ android:text="@string/visibility_1_view_2" />
+
+ <RadioButton
+ android:id="@+id/value_three"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:textColor="#555555"
+ android:text="@string/visibility_1_view_3" />
+
+</RadioGroup>
diff --git a/core/tests/coretests/res/layout/remote_view_host.xml b/core/tests/coretests/res/layout/remote_view_host.xml
new file mode 100644
index 0000000..19d0a73
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_host.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/remote_view_test_bad_1.xml b/core/tests/coretests/res/layout/remote_view_test_bad_1.xml
new file mode 100644
index 0000000..bdac697
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_test_bad_1.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <EditText android:id="@+id/edit"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/remote_view_test_bad_2.xml b/core/tests/coretests/res/layout/remote_view_test_bad_2.xml
new file mode 100644
index 0000000..630e603
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_test_bad_2.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <WebView android:id="@+id/web"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/remote_view_test_good.xml b/core/tests/coretests/res/layout/remote_view_test_good.xml
new file mode 100644
index 0000000..ce9755b
--- /dev/null
+++ b/core/tests/coretests/res/layout/remote_view_test_good.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView android:id="@+id/image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <FrameLayout android:id="@+id/frame"
+ android:layout_width="10dip"
+ android:layout_height="10dip" />
+
+ <RelativeLayout android:id="@+id/relative"
+ android:layout_width="10dip"
+ android:layout_height="10dip" />
+
+ <AbsoluteLayout android:id="@+id/absolute"
+ android:layout_width="10dip"
+ android:layout_height="10dip" />
+
+ <ProgressBar android:id="@+id/progress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageButton android:id="@+id/image_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <Button android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/scroll_to_rect_with_internal_scroll.xml b/core/tests/coretests/res/layout/scroll_to_rect_with_internal_scroll.xml
new file mode 100644
index 0000000..b6ec479
--- /dev/null
+++ b/core/tests/coretests/res/layout/scroll_to_rect_with_internal_scroll.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/scroll_view_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates scrolling with a ScrollView. -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none">
+
+ <LinearLayout
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+
+ <Button android:id="@+id/scrollToBlob"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/scroll_top_button"/>
+
+ <TextView android:id="@+id/blob"
+ android:layout_width="match_parent"
+ android:layout_height="80dip"
+ android:layout_marginTop="500dip"
+ android:layout_marginBottom="5dip"/>
+
+
+ </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/scroll_to_rectangle.xml b/core/tests/coretests/res/layout/scroll_to_rectangle.xml
new file mode 100644
index 0000000..55d057d
--- /dev/null
+++ b/core/tests/coretests/res/layout/scroll_to_rectangle.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/layout/scroll_view_1.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates scrolling with a ScrollView. -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none">
+
+ <LinearLayout
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+
+ <Button android:id="@+id/scrollToRectFromTop"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/scroll_top_button"/>
+
+ <Button android:id="@+id/scrollToRectFromTop2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="10dip"
+ android:text="@string/scroll_top_button2"/>
+
+ <TextView android:id="@+id/topBlob"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"/>
+
+ <TextView android:id="@+id/childToMakeVisible"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:text="@string/scroll_to_me"/>
+
+ <TextView android:id="@+id/bottomBlob"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"/>
+
+ <Button android:id="@+id/scrollToRectFromBottom2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="10dip"
+ android:text="@string/scroll_bottom_button2"/>
+
+ <Button android:id="@+id/scrollToRectFromBottom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/scroll_bottom_button"/>
+
+
+ </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/scrollview_linear_layout.xml b/core/tests/coretests/res/layout/scrollview_linear_layout.xml
new file mode 100644
index 0000000..a3c12ce
--- /dev/null
+++ b/core/tests/coretests/res/layout/scrollview_linear_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none">
+
+ <LinearLayout
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ </LinearLayout>
+
+
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/scrollview_with_webviews.xml b/core/tests/coretests/res/layout/scrollview_with_webviews.xml
new file mode 100644
index 0000000..e0c0c66
--- /dev/null
+++ b/core/tests/coretests/res/layout/scrollview_with_webviews.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/scrollView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="none">
+
+ <LinearLayout
+ android:id="@+id/layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+
+ <WebView android:id="@+id/wb1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <Button android:id="@+id/button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <WebView android:id="@+id/wb2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+
+</ScrollView>
diff --git a/core/tests/coretests/res/layout/table_layout_cell_span.xml b/core/tests/coretests/res/layout/table_layout_cell_span.xml
new file mode 100644
index 0000000..fceef0a
--- /dev/null
+++ b/core/tests/coretests/res/layout/table_layout_cell_span.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TableRow>
+ <TextView android:id="@+id/a"
+ android:text="@string/table_layout_a"
+ android:background="#FFFF0000"
+ android:padding="3dip" />
+ <TextView android:id="@+id/b"
+ android:text="@string/table_layout_b"
+ android:background="#FF00FF00"
+ android:padding="3dip" />
+ <TextView android:id="@+id/c"
+ android:text="@string/table_layout_c"
+ android:background="#FF0000FF"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <TextView android:id="@+id/spanThenCell"
+ android:text="@string/table_layout_d"
+ android:layout_span="2"
+ android:gravity="center_horizontal"
+ android:background="#FF0000FF"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_e"
+ android:background="#FF00FF00"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <TextView
+ android:text="@string/table_layout_f"
+ android:background="#FFFF00FF"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_g"
+ android:background="#FF00FF00"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_h"
+ android:background="#FFFF0000"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <TextView
+ android:text="@string/table_layout_a"
+ android:background="#FF00FF00"
+ android:padding="3dip" />
+ <TextView android:id="@+id/cellThenSpan"
+ android:text="@string/table_layout_b"
+ android:layout_span="2"
+ android:gravity="center_horizontal"
+ android:background="#FF0000FF"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <TextView android:id="@+id/span"
+ android:text="@string/table_layout_g"
+ android:layout_span="3"
+ android:gravity="center_horizontal"
+ android:background="#FFC0C0C0"
+ android:padding="3dip" />
+ </TableRow>
+</TableLayout>
diff --git a/core/tests/coretests/res/layout/table_layout_fixed_width.xml b/core/tests/coretests/res/layout/table_layout_fixed_width.xml
new file mode 100644
index 0000000..507701e
--- /dev/null
+++ b/core/tests/coretests/res/layout/table_layout_fixed_width.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TableRow>
+ <View android:id="@+id/fixed_height"
+ android:layout_height="48dip"
+ android:background="#FF909090" />
+ <TextView android:id="@+id/fixed_width"
+ android:layout_width="150dip"
+ android:text="@string/table_layout_export"
+ android:background="#FFFF0000"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_export_shortcut"
+ android:background="#FF00FFFF"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <View
+ android:layout_height="48dip"
+ android:background="#FF909090" />
+ <TextView android:id="@+id/non_fixed_width"
+ android:text="@string/table_layout_export"
+ android:background="#FFFF0000"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_export_shortcut"
+ android:background="#FF00FFFF"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+
+</TableLayout>
diff --git a/core/tests/coretests/res/layout/table_layout_horizontal_gravity.xml b/core/tests/coretests/res/layout/table_layout_horizontal_gravity.xml
new file mode 100644
index 0000000..fb72d81
--- /dev/null
+++ b/core/tests/coretests/res/layout/table_layout_horizontal_gravity.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:stretchColumns="1">
+
+ <TableRow>
+ <TextView android:id="@+id/reference"
+ android:layout_column="1"
+ android:background="#FF0000FF"
+ android:text="@string/table_layout_open"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_open_shortcut"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <!-- Horizontally centers the content of the cell -->
+ <TextView android:id="@+id/center"
+ android:layout_column="1"
+ android:text="@string/table_layout_save_as"
+ android:background="#FFFF0000"
+ android:layout_gravity="center_horizontal"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_save_as_shortcut"
+ android:background="#FFFF00FF"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <View
+ android:layout_height="24dip"
+ android:background="#FF909090" />
+ <!-- Aligns the content of the cell to the bottom right -->
+ <TextView android:id="@+id/bottomRight"
+ android:text="@string/table_layout_export"
+ android:background="#FFFF0000"
+ android:layout_gravity="right|bottom"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_export_shortcut"
+ android:background="#FF00FFFF"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+
+
+ <TableRow>
+ <TextView android:id="@+id/left"
+ android:layout_column="1"
+ android:background="#FF0000FF"
+ android:text="@string/table_layout_open"
+ android:padding="3dip" />
+ <TextView
+ android:text="@string/table_layout_open_shortcut"
+ android:gravity="right"
+ android:padding="3dip" />
+ </TableRow>
+</TableLayout>
diff --git a/core/tests/coretests/res/layout/table_layout_vertical_gravity.xml b/core/tests/coretests/res/layout/table_layout_vertical_gravity.xml
new file mode 100644
index 0000000..ae17ada
--- /dev/null
+++ b/core/tests/coretests/res/layout/table_layout_vertical_gravity.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TableRow>
+ <View android:id="@+id/reference1"
+ android:layout_height="96dip"
+ android:background="#FF909090" />
+ <TextView
+ android:background="#FF0000FF"
+ android:text="@string/table_layout_open"
+ android:padding="3dip" />
+ <TextView android:id="@+id/cell_top"
+ android:background="#FF00FF00"
+ android:layout_gravity="top"
+ android:text="@string/table_layout_open_shortcut"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <View android:id="@+id/reference2"
+ android:layout_height="96dip"
+ android:background="#FF909090" />
+ <TextView
+ android:background="#FF0000FF"
+ android:text="@string/table_layout_open"
+ android:padding="3dip" />
+ <TextView android:id="@+id/cell_center"
+ android:background="#FF00FF00"
+ android:layout_gravity="center_vertical"
+ android:text="@string/table_layout_open_shortcut"
+ android:padding="3dip" />
+ </TableRow>
+
+ <TableRow>
+ <View android:id="@+id/reference3"
+ android:layout_height="96dip"
+ android:background="#FF909090" />
+ <TextView
+ android:background="#FF0000FF"
+ android:text="@string/table_layout_open"
+ android:padding="3dip" />
+ <TextView android:id="@+id/cell_bottom"
+ android:background="#FF00FF00"
+ android:layout_gravity="bottom"
+ android:text="@string/table_layout_open_shortcut"
+ android:padding="3dip" />
+ </TableRow>
+
+</TableLayout>
diff --git a/core/tests/coretests/res/layout/table_layout_weight.xml b/core/tests/coretests/res/layout/table_layout_weight.xml
new file mode 100644
index 0000000..ba4fade
--- /dev/null
+++ b/core/tests/coretests/res/layout/table_layout_weight.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TableRow android:id="@+id/row"
+ android:weightSum="0.9">
+ <View android:id="@+id/cell1"
+ android:layout_width="0dip"
+ android:layout_weight="0.3"
+ android:layout_height="190dip"
+ android:background="#FF0000FF" />
+ <View android:id="@+id/cell2"
+ android:layout_width="0dip"
+ android:layout_weight="0.3"
+ android:layout_height="190dip"
+ android:background="#FFFFFFFF" />
+ <View android:id="@+id/cell3"
+ android:layout_width="0dip"
+ android:layout_weight="0.3"
+ android:layout_height="190dip"
+ android:background="#FFFF0000" />
+ </TableRow>
+
+</TableLayout>
diff --git a/core/res/res/layout/contact_header_name.xml b/core/tests/coretests/res/layout/translucent_background.xml
index 4a53252..c4a1acf 100644
--- a/core/res/res/layout/contact_header_name.xml
+++ b/core/tests/coretests/res/layout/translucent_background.xml
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,16 +14,11 @@
limitations under the License.
-->
-<!-- In the default locale, the "Name" field is a single TextView -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/name"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textStyle="bold"
- android:singleLine="true"
- android:ellipsize="end"
- android:layout_gravity="center_vertical"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
- />
+<!-- Demonstrates an activity with a fancy translucent background.
+ See corresponding Java code com.android.sdk.app.TranslucentBackground.java. -->
+
+<!-- This screen consists of a single text field that displays some text. -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:gravity="center_vertical|center_horizontal"
+ android:text="@string/translucent_background"/>
diff --git a/core/tests/coretests/res/layout/viewgroupchildren.xml b/core/tests/coretests/res/layout/viewgroupchildren.xml
new file mode 100644
index 0000000..22595ed
--- /dev/null
+++ b/core/tests/coretests/res/layout/viewgroupchildren.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates adding/removing views from ViewGroup. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/group"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/viewstub.xml b/core/tests/coretests/res/layout/viewstub.xml
new file mode 100644
index 0000000..8b32d8f
--- /dev/null
+++ b/core/tests/coretests/res/layout/viewstub.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button android:id="@+id/vis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_vis" />
+
+ <ViewStub android:id="@+id/viewStub"
+ android:layout="@layout/linear_layout_buttons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ViewStub android:id="@+id/viewStubWithId"
+ android:inflatedId="@+id/stub_inflated"
+ android:layout="@layout/linear_layout_buttons"
+ android:layout_width="47dip"
+ android:layout_height="127dip" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/visibility.xml b/core/tests/coretests/res/layout/visibility.xml
new file mode 100644
index 0000000..7edfa33
--- /dev/null
+++ b/core/tests/coretests/res/layout/visibility.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates changing view visibility. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:background="@drawable/box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/refUp"
+ android:background="@drawable/red"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_1"/>
+
+ <TextView android:id="@+id/victim"
+ android:background="@drawable/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_2"/>
+
+ <TextView android:id="@+id/refDown"
+ android:background="@drawable/blue"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_3"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/vis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_vis"/>
+
+ <Button android:id="@+id/invis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_invis"/>
+
+ <Button android:id="@+id/gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_gone"/>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/visibility_callback.xml b/core/tests/coretests/res/layout/visibility_callback.xml
new file mode 100644
index 0000000..9034b3f
--- /dev/null
+++ b/core/tests/coretests/res/layout/visibility_callback.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates changing view visibility. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:background="@drawable/box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/refUp"
+ android:background="@drawable/red"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_1"/>
+
+ <FrameLayout android:id="@+id/parent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <view class="android.view.VisibilityCallback$MonitoredTextView"
+ android:id="@+id/victim"
+ android:background="@drawable/green"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_2"/>
+ </FrameLayout>
+
+ <TextView android:id="@+id/refDown"
+ android:background="@drawable/blue"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_view_3"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <Button android:id="@+id/vis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_vis"/>
+
+ <Button android:id="@+id/invis"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_invis"/>
+
+ <Button android:id="@+id/gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_gone"/>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/weight_sum.xml b/core/tests/coretests/res/layout/weight_sum.xml
new file mode 100644
index 0000000..f8921ec
--- /dev/null
+++ b/core/tests/coretests/res/layout/weight_sum.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout android:id="@+id/container" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:weightSum="1.0"
+ android:gravity="center_horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button android:id="@+id/child"
+ android:layout_width="0dip"
+ android:layout_weight="0.5"
+ android:layout_height="wrap_content"
+ android:text="@string/visibility_1_vis"/>
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/with_bitmap_background.xml b/core/tests/coretests/res/layout/with_bitmap_background.xml
new file mode 100644
index 0000000..01605d5
--- /dev/null
+++ b/core/tests/coretests/res/layout/with_bitmap_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2008, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout android:id="@+id/container" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/bitmap_drawable">
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/zero_sized.xml b/core/tests/coretests/res/layout/zero_sized.xml
new file mode 100644
index 0000000..f1c94f8
--- /dev/null
+++ b/core/tests/coretests/res/layout/zero_sized.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Demonstrates changing view visibility. See corresponding Java code. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View android:id="@+id/dimension"
+ android:background="#ffff0000"
+ android:layout_width="42dip"
+ android:layout_height="42dip" />
+
+ <View android:id="@+id/noWidth"
+ android:background="#ff00ff00"
+ android:layout_width="0dip"
+ android:layout_height="42dip" />
+
+ <View android:id="@+id/noHeight"
+ android:background="#ff0000ff"
+ android:layout_width="42dip"
+ android:layout_height="0dip" />
+
+ <View android:id="@+id/noDimension"
+ android:background="#ffffffff"
+ android:layout_width="0dip"
+ android:layout_height="0dip" />
+
+</LinearLayout>
diff --git a/core/tests/coretests/res/raw/alt_ip_only.crt b/core/tests/coretests/res/raw/alt_ip_only.crt
new file mode 100644
index 0000000..3ac9f5a
--- /dev/null
+++ b/core/tests/coretests/res/raw/alt_ip_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsjCCAZqgAwIBAgIJALrC37YAXFIeMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMzk0NloYDzIwNjQxMDE1MjEzOTQ2WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALr8s/4Abpby
+IYks5YCJE2nbWH7kj6XbwnRzsVP9RVC33bPoQ1M+2ZY24HqkigjQS/HEXR0s0bYh
+dewNUnTj1uGyGs6cYzsbu7x114vmVYqjxUo3hKjwfYiPeF6f3IE1vpLI7I2G32gq
+Zwm9c1/vXNHIdWQxCpFcuPA8P3YGfoApFX4pQPFplBUNAQqnjdmA68cbxxMC+1F3
+mX42D7iIEVwyVpah5HjyxjIZQlf3X7QBj0bCmkL+ibIHTALrkNNwNM6i4xzYLz/5
+14GkN9ncHY87eSOk6r53ptER6mQMhCe9qPRjSHnpWTTyj6IXTaYe+dDQw657B80w
+cSHL7Ed25zUCAwEAAaMTMBEwDwYDVR0RBAgwBocEwKgKATANBgkqhkiG9w0BAQUF
+AAOCAQEAgrwrtOWZT3fbi1AafpGaAiOBWSJqYqRhtQy0AfiZBxv1U0XaYqmZmpnq
+DVAqr0NkljowD28NBrxIFO5gBNum2ZOPDl2/5vjFn+IirUCJ9u9wS7zYkTCW2lQR
+xE7Ic3mfWv7wUbKDfjlWqP1IDHUxwkrBTAl+HnwOPiaKKk1ttwcrgS8AHlqASe03
+mlwnvJ+Stk54IneRaegL0L93sNAy63RZqnPCTxGz7eHcFwX8Jdr4sbxTxQqV6pIc
+WPjHQcWfpkFzAF5wyOq0kveVfx0g5xPhOVDd+U+q7WastbXICpCoHp9FxISmZVik
+sAyifp8agkYdzaSh55fFmKXlFnRsQw==
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/raw/install b/core/tests/coretests/res/raw/install
new file mode 100644
index 0000000..06981f4
--- /dev/null
+++ b/core/tests/coretests/res/raw/install
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert1 b/core/tests/coretests/res/raw/install_app1_cert1
new file mode 100644
index 0000000..f880c0b
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert1
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert1_cert2 b/core/tests/coretests/res/raw/install_app1_cert1_cert2
new file mode 100644
index 0000000..ed89fbb
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert2 b/core/tests/coretests/res/raw/install_app1_cert2
new file mode 100644
index 0000000..5551c7e
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert3 b/core/tests/coretests/res/raw/install_app1_cert3
new file mode 100644
index 0000000..0d1a4dc
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert3
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_cert3_cert4 b/core/tests/coretests/res/raw/install_app1_cert3_cert4
new file mode 100644
index 0000000..29ff3b6
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_cert3_cert4
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app1_unsigned b/core/tests/coretests/res/raw/install_app1_unsigned
new file mode 100644
index 0000000..01b39e2
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app1_unsigned
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_cert1 b/core/tests/coretests/res/raw/install_app2_cert1
new file mode 100644
index 0000000..12bfc6f
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_cert1
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_cert1_cert2 b/core/tests/coretests/res/raw/install_app2_cert1_cert2
new file mode 100644
index 0000000..39095ba
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_cert1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_cert2 b/core/tests/coretests/res/raw/install_app2_cert2
new file mode 100644
index 0000000..f6d965b
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_cert3 b/core/tests/coretests/res/raw/install_app2_cert3
new file mode 100644
index 0000000..3d8b6f1
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_cert3
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_app2_unsigned b/core/tests/coretests/res/raw/install_app2_unsigned
new file mode 100644
index 0000000..b69d9fe
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_app2_unsigned
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_decl_perm b/core/tests/coretests/res/raw/install_decl_perm
new file mode 100644
index 0000000..af05d81
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_decl_perm
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_loc_auto b/core/tests/coretests/res/raw/install_loc_auto
new file mode 100644
index 0000000..63bf35c
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_loc_auto
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_loc_internal b/core/tests/coretests/res/raw/install_loc_internal
new file mode 100644
index 0000000..5178803
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_loc_internal
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_loc_sdcard b/core/tests/coretests/res/raw/install_loc_sdcard
new file mode 100644
index 0000000..013a414
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_loc_sdcard
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_loc_unspecified b/core/tests/coretests/res/raw/install_loc_unspecified
new file mode 100644
index 0000000..06981f4
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_loc_unspecified
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared1_cert1 b/core/tests/coretests/res/raw/install_shared1_cert1
new file mode 100644
index 0000000..714f9ff
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared1_cert1
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared1_cert1_cert2 b/core/tests/coretests/res/raw/install_shared1_cert1_cert2
new file mode 100644
index 0000000..83725e0
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared1_cert1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared1_cert2 b/core/tests/coretests/res/raw/install_shared1_cert2
new file mode 100644
index 0000000..6a3157e
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared1_unsigned b/core/tests/coretests/res/raw/install_shared1_unsigned
new file mode 100644
index 0000000..2a2e5f5
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared1_unsigned
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared2_cert1 b/core/tests/coretests/res/raw/install_shared2_cert1
new file mode 100644
index 0000000..7006edc
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared2_cert1
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared2_cert1_cert2 b/core/tests/coretests/res/raw/install_shared2_cert1_cert2
new file mode 100644
index 0000000..b7b084c
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared2_cert1_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared2_cert2 b/core/tests/coretests/res/raw/install_shared2_cert2
new file mode 100644
index 0000000..0f04388
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared2_cert2
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_shared2_unsigned b/core/tests/coretests/res/raw/install_shared2_unsigned
new file mode 100644
index 0000000..2794282
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_shared2_unsigned
Binary files differ
diff --git a/core/tests/coretests/res/raw/install_use_perm_good b/core/tests/coretests/res/raw/install_use_perm_good
new file mode 100644
index 0000000..a7eb32f
--- /dev/null
+++ b/core/tests/coretests/res/raw/install_use_perm_good
Binary files differ
diff --git a/core/tests/coretests/res/raw/medium.xml b/core/tests/coretests/res/raw/medium.xml
new file mode 100644
index 0000000..5757a24
--- /dev/null
+++ b/core/tests/coretests/res/raw/medium.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout id="@+id/content" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
+ <TextView id="@+id/text" android:text="S" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView id="@+id/text" android:text="M" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView id="@+id/text" android:text="T" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView id="@+id/text" android:text="W" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView id="@+id/text" android:text="H" android:layout_width="match_parent" android:layout_height="wrap_content" />
+ <TextView id="@+id/text" android:text="F" android:layout_width="match_parent" android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/core/tests/coretests/res/raw/small.xml b/core/tests/coretests/res/raw/small.xml
new file mode 100644
index 0000000..ee859b9
--- /dev/null
+++ b/core/tests/coretests/res/raw/small.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<view class="com.android.tests.InflateTest$ViewOne" id="@+id/viewOne" android:layout_width="match_parent" android:layout_height="match_parent"/>
diff --git a/core/tests/coretests/res/raw/subject_alt_only.crt b/core/tests/coretests/res/raw/subject_alt_only.crt
new file mode 100644
index 0000000..d5808fb
--- /dev/null
+++ b/core/tests/coretests/res/raw/subject_alt_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICvTCCAaWgAwIBAgIJALbA0TZk2YmNMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIwNTg1NFoYDzIwNjQxMDE1MjA1ODU0WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEg6acVC9V4
+xNGoLNVLPbqBc8IvMvcsc88dF6MW3d9VagX3aeWU8c79tI/KOV/1AOakH7WYxw/w
+yD8aOX7+9BK1Hu0qKKKbSM+ycqaMthXd6xytrNDsIx5WiGUz8zTko0Gk3orIR7p7
+rPcNzB/zwtESkscqPv85aEn7S/yClNkzLfEzm3CtaYOc0tfhBMyzi/ipXzGMxUmx
+PvOLr3v/Oz5pZEQw7Kxlm4+tAtn7bJlHziQ1UW4WPIy+T3hySBEpODFiqZi7Ok3X
+Zjxdii62fgo5B2Ee7q5Amo0mUIwcQTDjJ2CLAqzYnSh3tpiPJGjEIjmRyCoMQ1bx
+7D+y7nSPIq8CAwEAAaMeMBwwGgYDVR0RBBMwEYIPd3d3LmV4YW1wbGUuY29tMA0G
+CSqGSIb3DQEBBQUAA4IBAQBsGEh+nHc0l9FJTzWqvG3qs7i6XoJZdtThCDx4HjKJ
+8GMrJtreNN4JvIxn7KC+alVbnILjzCRO+c3rsnpxKBi5cp2imjuw5Kf/x2Seimb9
+UvZbaJvBVOzy4Q1IGef9bLy3wZzy2/WfBFyvPTAkgkRaX7LN2jnYOYVhNoNFrwqe
+EWxkA6fzrpyseUEFeGFFjGxRSRCDcQ25Eq6d9rkC1x21zNtt4QwZBO0wHrTy155M
+JPRynf9244Pn0Sr/wsnmdsTRFIFYynrc51hQ7DkwbUxpcaewkZzilru/SwZ3+pPT
+9JSqm5hJ1pg5WDlPkW7c/1VA0/141N52Q8MIU+2ZpuOj
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/raw/subject_only.crt b/core/tests/coretests/res/raw/subject_only.crt
new file mode 100644
index 0000000..11b34e7
--- /dev/null
+++ b/core/tests/coretests/res/raw/subject_only.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbmgAwIBAgIJANCQbJPPw31SMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1ODE4
+WhgPMjA2NDEwMTUyMDU4MThaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsdUJk
+4KxADA3vlDHxNbyC27Ozw4yiSVzPTHUct471YmdDRW3orO2P5a5hRnUGV70gjH9X
+MU4oeOdWYAgXB9pxfLyr6621k1+uNrmaZtzp0ECH9twcwxNJJFDZsN7o9vt7V6Ej
+NN9weeqDr/aeQXo07a12vyVfR6jWO8jHB0e4aemwZNoYjNvM69fivQTse2ZoRVfj
+eSHhjRTX6I8ry4a31Hwt+fT1QiWWNN6o7+WOtpJAhX3eg4smhSD1svi2kOT8tdUe
+NS4hWlmXmumU9G4tI8PBurcLNTm7PB2lUlbn/IV18WavqKE/Uy/1WgAx+a1EJNdp
+i07AG1PsqaONKkf1AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJrNsuL7fZZNC8gL
+BdePJ7DYW2e7mXANU3bCBe2BZqmXKQxKwibZnEsqA+yMLqcSd8uxISlyHY2tw9wT
+4wB9KPIttfNLbwn/rk+MbOTHpvyF60d9WhJJVUkPBl8D4VuPSl+VnlA54kU9dtZN
++ZYdxYbNtSsI/Flz9SCoOV79W9GhN+uYJhv6RwyIMIHeMpZpyX1xSUVx5dZlmerQ
+WAUvghDH3fFRt2ZdnA4OXoKkTAaM3Pv7PUMsnah8bux6MQi0AuLMWFWOI1H34koH
+rs2oQLwOLnuifH52ey9+tJguabo+brlYYigAuWWFEzJfBzikDkIwnE/L7wlrypIk
+taXDWI4=
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/raw/subject_with_alt_names.crt b/core/tests/coretests/res/raw/subject_with_alt_names.crt
new file mode 100644
index 0000000..6963c7e
--- /dev/null
+++ b/core/tests/coretests/res/raw/subject_with_alt_names.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIJALv14qjcuhw9MA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjA1OTM4
+WhgPMjA2NDEwMTUyMDU5MzhaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiTVgU
+kBO9KNYZZLmiPR0eBrk8u61CLnm35BGKW8EFpDaINLbbIFIQvqOMekURON/N+xFY
+D8roo7aFZVuHWAUqFcOJ4e6NmviK5qocLihtzAexsw4f4AzZxM3A8kcLlWLyAt7e
+EVLxhcMHogY7GaF6q+33Z8p+zp6x3tj07mwyPrriCLse2PeRsRunZl/fp/VvRlr6
+YbC7CbRrhnIv5nqohs8BsbBiiFpxQftsMQmiXhY2LUzqY2RXUIOw24fHjoQkHTL2
+4z5nUM3b6ueQe+CBnobUS6fzK/36Nct4dRpev9i/ORdRLuIDKJ+QR16G1V/BJYBR
+dAK+3iXvg6z8vP1XAgMBAAGjMTAvMC0GA1UdEQQmMCSCEHd3dzIuZXhhbXBsZS5j
+b22CEHd3dzMuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEBAJQNf38uXm3h
+0vsF+Yd6/HqM48Su7tWnTDAfTXnQZZkzjzITq3JXzquMXICktAVN2cLnT9zPfRAE
+8V8A3BNO5zXiR5W3o/mJP5HQ3/WxpzBGM2N+YmDCJyBoQrIVaAZaXAZUaBBvn5A+
+kEVfGWquwIFuvA67xegbJOCRLD4eUzRdNsn5+NFiakWO1tkFqEzqyQ0PNPviRjgu
+z9NxdPvd1JQOhydkucsPKJzlEBbGyL5QL/Jkot3Qy+FOeuNzgQUfAGtQgzRrsZDK
+hrTVypLSoRXuTB2aWilu4p6aNh84xTdyqo2avtNr2MiQMZIcdamBq8LdBIAShFXI
+h5G2eVGXH/Y=
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/raw/subject_with_wild_alt_name.crt b/core/tests/coretests/res/raw/subject_with_wild_alt_name.crt
new file mode 100644
index 0000000..19b1174
--- /dev/null
+++ b/core/tests/coretests/res/raw/subject_with_wild_alt_name.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8DCCAdigAwIBAgIJAL/oWJ64VAdXMA0GCSqGSIb3DQEBBQUAMCcxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wIBcNMTAwMTEyMjEwMDAx
+WhgPMjA2NDEwMTUyMTAwMDFaMCcxCzAJBgNVBAYTAkpQMRgwFgYDVQQDEw93d3cu
+ZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbx1QB
+92iea7VybLYICA4MX4LWipYrRsgXUXQrcIQ3YLTQ9rH0VwScrHL4O4JDxgXCQnR+
+4VOzD42q1KXHJAqzqGUYCNPyvZEzkGCnQ4FBIUEmxZd5SNEefJVH3Z6GizYJomTh
+p78yDcoqymD9umxRC2cWFu8GscfFGMVyhsqLlOofu7UWOs22mkXPo43jDx+VOAoV
+n48YP3P57a2Eo0gcd4zVL00y62VegqBO/1LW38aTS7teiCBFc1TkNYa5I40yN9lP
+rB9ICHYQWyzf/7OxU9iauEK2w6DmSsQoLs9JzEhgeNZddkcc77ciSUCo2Hx0VpOJ
+BFyf2rbryJeAk+FDAgMBAAGjHTAbMBkGA1UdEQQSMBCCDiouZXhhbXBsZTIuY29t
+MA0GCSqGSIb3DQEBBQUAA4IBAQA2a14pRL+4laJ8sscQlucaDB/oSdb0cwhk4IkE
+kKl/ZKr6rKwPZ81sJRgzvI4imLbUAKt4AJHdpI9cIQUq1gw9bzil7LKwmFtFSPmC
+MYb1iadaYrvp7RE4yXrWCcSbU0hup9JQLHTrHLlqLtRuU48NHMvWYThBcS9Q/hQp
+nJ/JxYy3am99MHALWLAfuRxQXhE4C5utDmBwI2KD6A8SA30s+CnuegmkYScuSqBu
+Y3R0HZvKzNIU3pwAm69HCJoG+/9MZEIDJb0WJc5UygxDT45XE9zQMQe4dBOTaNXT
++ntgaB62kE10HzrzpqXAgoAWxWK4RzFcUpBWw9qYq9xOCewJ
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/raw/text.txt b/core/tests/coretests/res/raw/text.txt
new file mode 100644
index 0000000..3d8c519
--- /dev/null
+++ b/core/tests/coretests/res/raw/text.txt
@@ -0,0 +1 @@
+OneTwoThreeFourFiveSixSevenEightNineTen \ No newline at end of file
diff --git a/core/tests/coretests/res/raw/v21_backslash.vcf b/core/tests/coretests/res/raw/v21_backslash.vcf
new file mode 100644
index 0000000..bd3002b
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_backslash.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:2.1
+N:;A\;B\\;C\\\;;D;\:E;\\\\;
+FN:A;B\C\;D:E\\
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_complicated.vcf b/core/tests/coretests/res/raw/v21_complicated.vcf
new file mode 100644
index 0000000..de34e16
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_complicated.vcf
@@ -0,0 +1,106 @@
+BEGIN:VCARD
+VERSION:2.1
+N:Gump;Forrest;Hoge;Pos;Tao
+FN:Joe Due
+ORG:Gump Shrimp Co.;Sales Dept.\;Manager;Fish keeper
+ROLE:Fish Cake Keeper!
+X-CLASS:PUBLIC
+TITLE:Shrimp Man
+TEL;WORK;VOICE:(111) 555-1212
+TEL;HOME;VOICE:(404) 555-1212
+TEL;CELL:0311111111
+TEL;VIDEO:0322222222
+TEL;VOICE:0333333333
+ADR;WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America
+LABEL;WORK;ENCODING=QUOTED-PRINTABLE:100 Waters Edge=0D=0ABaytown, LA 30314=0D=0AUnited States of America
+ADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America
+LABEL;HOME;ENCODING=QUOTED-PRINTABLE:42 Plantation St.=0D=0A=
+Baytown, LA 30314=0D=0A=
+United States of America
+EMAIL;PREF;INTERNET:forrestgump@walladalla.com
+EMAIL;CELL:cell@example.com
+NOTE:The following note is the example from RFC 2045.
+NOTE;ENCODING=QUOTED-PRINTABLE:Now's the time =
+for all folk to come=
+ to the aid of their country.
+
+PHOTO;ENCODING=BASE64;TYPE=JPEG:
+ /9j/4QoPRXhpZgAATU0AKgAAAAgADQEOAAIAAAAPAAAAqgEPAAIAAAAHAAAAugEQAAIAAAAG
+ AAAAwgESAAMAAAABAAEAAAEaAAUAAAABAAAAyAEbAAUAAAABAAAA0AEoAAMAAAABAAIAAAEx
+ AAIAAAAOAAAA2AEyAAIAAAAUAAAA5gITAAMAAAABAAEAAIKYAAIAAAAOAAAA+odpAAQAAAAB
+ AAABhMSlAAcAAAB8AAABCAAABB4yMDA4MTAyOTEzNTUzMQAARG9Db01vAABEOTA1aQAAAABI
+ AAAAAQAAAEgAAAABRDkwNWkgVmVyMS4wMAAyMDA4OjEwOjI5IDEzOjU1OjQ3ACAgICAgICAg
+ ICAgICAAUHJpbnRJTQAwMzAwAAAABgABABQAFAACAQAAAAADAAAANAEABQAAAAEBAQAAAAEQ
+ gAAAAAAAEQkAACcQAAAPCwAAJxAAAAWXAAAnEAAACLAAACcQAAAcAQAAJxAAAAJeAAAnEAAA
+ AIsAACcQAAADywAAJxAAABvlAAAnEAAogpoABQAAAAEAAANqgp0ABQAAAAEAAANyiCIAAwAA
+ AAEAAgAAkAAABwAAAAQwMjIwkAMAAgAAABQAAAN6kAQAAgAAABQAAAOOkQEABwAAAAQBAgMA
+ kQIABQAAAAEAAAOikgEACgAAAAEAAAOqkgIABQAAAAEAAAOykgQACgAAAAEAAAO6kgUABQAA
+ AAEAAAPCkgcAAwAAAAEAAgAAkggAAwAAAAEAAAAAkgkAAwAAAAEAAAAAkgoABQAAAAEAAAPK
+ knwABwAAAAEAAAAAkoYABwAAABYAAAPSoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIAAwAA
+ AAEAYAAAoAMAAwAAAAEASAAAoAUABAAAAAEAAAQAog4ABQAAAAEAAAPoog8ABQAAAAEAAAPw
+ ohAAAwAAAAEAAgAAohcAAwAAAAEAAgAAowAABwAAAAEDAAAAowEABwAAAAEBAAAApAEAAwAA
+ AAEAAAAApAIAAwAAAAEAAAAApAMAAwAAAAEAAAAApAQABQAAAAEAAAP4pAUAAwAAAAEAHQAA
+ pAYAAwAAAAEAAAAApAcAAwAAAAEAAAAApAgAAwAAAAEAAAAApAkAAwAAAAEAAAAApAoAAwAA
+ AAEAAAAApAwAAwAAAAEAAgAAAAAAAAAAAFMAACcQAAABXgAAAGQyMDA4OjEwOjI5IDEzOjU1
+ OjMxADIwMDg6MTA6MjkgMTM6NTU6NDcAAAApiAAAGwAAAAKyAAAAZAAAAV4AAABkAAAAAAAA
+ AGQAAAAlAAAACgAADpIAAAPoAAAAAAAAAAAyMDA4MTAyOTEzNTUzMQAAICoAAAAKAAAq4gAA
+ AAoAAAAAAAAAAQACAAEAAgAAAARSOTgAAAIABwAAAAQwMTAwAAAAAAAGAQMAAwAAAAEABgAA
+ ARoABQAAAAEAAARsARsABQAAAAEAAAR0ASgAAwAAAAEAAgAAAgEABAAAAAEAAAR8AgIABAAA
+ AAEAAAWLAAAAAAAAAEgAAAABAAAASAAAAAH/2P/bAIQAIBYYHBgUIBwaHCQiICYwUDQwLCww
+ YkZKOlB0Znp4cmZwboCQuJyAiK6KbnCg2qKuvsTO0M58muLy4MjwuMrOxgEiJCQwKjBeNDRe
+ xoRwhMbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbG
+ /8AAEQgAeACgAwEhAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAA
+ AgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkK
+ FhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWG
+ h4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl
+ 5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQH
+ BQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBka
+ JicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
+ lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz
+ 9PX29/j5+v/aAAwDAQACEQMRAD8AFFSqKkZIoqRVpgSKKeBTEOApwFADsUYpgIRSEUANIppF
+ ICNhUTCgCMio2FICJhULCgC0oqVaAJFFSqKBkgFOApiHCnCgB2KMUCENJQA0imEUDGMKiYUA
+ RtUbUgIWqJhQBZSpFoAlWpVoGPFPFMQ7tSK2ODQA4yKO9HmKe9FxAzDHFIOlAAaYaAGNUTUD
+ ImqNqQETVE1AE6VKKAJFNSqaAHg08GmANIFFQM5Y5qJMBuT60ZNQIcrkVYSQMKuLGKaaasQx
+ qiagZE1RtSAjaomoAkQ1KpoAlU1IpoAkU07OBTArO+5qkV12Y71lfUBmaKkCRSuznrTFba2a
+ oCwGyM0E1qIjY1GxoGRNUZNICNqiagByGplNAEimpFNMB4YDvSucpxSYEIU04KazsAu1qArU
+ WELtPpTSposBNETt5pxNaoCNjUbGgCNjUZoGRtUTUgFU1KpoAkBqQHigCFnO7rUqOdlZp6gA
+ c+tODn1pXAXzD60eYfWncQvmNSGQ07gOMhCVEJGz1ptgS5yKYxqwGE1GxoAiamGkMapqVTQB
+ Kpp+eKAICfmqWM/Kaz6gANOBqQFzRmmAuaTNACsfkqMHmm9wJs8U0mtRDGNRsaAI2phpDI1N
+ SqaAJFNSA8UCISfmqSM/Kaz6jAHmnA1ICg0uaAFzSZpgKx+SmDrTe4E2eKaTWoiMmmMaAIzT
+ DSGRKakU0ASKaeDTERseakjPyms+oxAacDUgOBpc0gFzSZpgOY/KKYv3qrqIlpprQBjGoyaA
+ GGmmkMgU1IppgPBqQGgQu0Gn4wvFKwEQpwNZDHZpc0ALmigRKBleaQKBWtgA001QDGqM0gGm
+ mGkMrqakBoAepp4NMRIDTwaAE2A008GokgHxjd1pzKFpW0uAg5NSBBTirgOpDWgDTTTQAw0w
+ 0gGGmmgZWBp4pASKaeDTEOBp4NADwajbrUyBEkXWnSUdAGr1qeiAMSkNWAhphoAaaYaQDDTT
+ SGVRTwaYDxTwaBDwaeDQA4GlK5oauIeo20pGaLaAKqgU6hKwBSGmAhphoAaaYaQxhpppDKgN
+ PFMB4p4oEPFOBpgPBp4NAhwpwoAWloAKSgBDTTQMYaYaQDTTTSGA/9n/2wCEAAoHBwgHBgoI
+ CAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9
+ PjsBCgsLDg0OHBAQHDsoIig7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7
+ Ozs7Ozs7Ozs7Ozs7O//AABEIAEgAYAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAA
+ AQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNC
+ scEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hp
+ anN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS
+ 09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI
+ CQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVi
+ ctEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4
+ eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY
+ 2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AJ7SLgcVr20I4rNFGvbQAAHFaEUX
+ SrQi5HCMdKk8oY6VSJYx4hjpVaWMelAFC4iGDxWPdR8mkxmRdxjBrEvI+tZjN20xtHNbNqAc
+ UIDXg6Cr0WKtCY8XKQOyzOB3FKNWsyceZ+lS6sY6NkjvPSdwImBHUmmy4q076oCjOODWPdgc
+ 0MpGPdAYNYl4o5rNjNKzkyorZtXxihAa1vIDip7m9Frb7/4jwKcnyxbEzN3ieJppZsyZ4U1H
+ urzZau4mWVlNrGk0UuWPVa1YroXEIkHfrXZh5W90RWncAHmsi6bJNdQ0ZNw3BrGuiMGs2Mks
+ puBzWzbzdOaEBeOpR2oUtkk9hTru7iuo4m8wgemKyqTi04sBsfkEf68j8KlUQZz9o/SuZRj3
+ JYriAji4/Sp7W6htbV2aXcu70ramoxle4gN7HcIXjbis+4k5NdaaauhmVcv1rHuW61DGiG1m
+ 6c1s20/TmgAv5vmj57VKk3+ixnPc1xVV70h9CVJuOtSrL71hFgxzScUkkn+iY/2q1i9xDrGT
+ 9y31pJ5Otd1L+GhMy7mTrWXO2SapjRn28vTmta3nxjmgGOvJd2w1Kkv+ipz/ABGuOoveYdCe
+ ObjrU6y5rlsA8ycUksn+ij/eNaw6iJLNsW59zTJn6816FP4EJmbO+Saz5m602UjIgk4HNadv
+ LwKaBl+MpIMOMipp490SCJeF7CoqQvF2JuRqWQ4YEGrSiQJuKnHrXByMpki73GFBNXIoh9n2
+ SrnnOK6MPTbd3sSwIVF2qMCqkzHmuy1lYRnTHrVGWpZaMKB+BWlbycYoQM0IZDxzV+GU8c1a
+ IYy5Y+dnHatAsfsAHfArmS1mPoh1gT8x9qtk1rQX7tCe5DIapzGtGBQm71SlqGWjnIH6Vowt
+ zmhAy/E3vV6F6tEMuxlWIyAfrVxCCAO1VZEEyYA4AApxNGwyJ+lVJRUsaKMw61SlFQzRAP/Z
+
+X-ATTRIBUTE:Some String
+BDAY:19800101
+GEO:35.6563854,139.6994233
+URL:http://www.example.com/
+REV:20080424T195243Z
+END:VCARD \ No newline at end of file
diff --git a/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf b/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf
new file mode 100644
index 0000000..f910710
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_invalid_comment_line.vcf
@@ -0,0 +1,10 @@
+BEGIN:vCard
+VERSION:2.1
+UID:357
+N:;Conference Call
+FN:Conference Call
+# This line must be ignored.
+NOTE;ENCODING=QUOTED-PRINTABLE:This is an (sharp ->=
+#<- sharp) example. This message must NOT be ignored.
+# This line must be ignored too.
+END:vCard
diff --git a/core/tests/coretests/res/raw/v21_japanese_1.vcf b/core/tests/coretests/res/raw/v21_japanese_1.vcf
new file mode 100644
index 0000000..d05e2ff
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_japanese_1.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ;;;;
+TEL;PREF;VOICE:0300000000
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_japanese_2.vcf b/core/tests/coretests/res/raw/v21_japanese_2.vcf
new file mode 100644
index 0000000..fa54acb
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_japanese_2.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD
+VERSION:2.1
+FN;CHARSET=SHIFT_JIS:ˆÀ“¡ ƒƒCƒh 1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡;ƒƒCƒh1;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³;Û²ÄÞ1;;;
+ADR;HOME;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:;=93=8C=8B=9E=93=73=
+=8F=61=92=4A=8B=E6=8D=F7=8B=75=92=AC26-1=83=5A=83=8B=83=8A=83=41=83=93=
+=83=5E=83=8F=81=5B6=8A=4B;;;;150-8512;
+NOTE;CHARSET=SHIFT_JIS;ENCODING=QUOTED-PRINTABLE:=83=81=83=82
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_multiple_entry.vcf b/core/tests/coretests/res/raw/v21_multiple_entry.vcf
new file mode 100644
index 0000000..ebbb19a
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_multiple_entry.vcf
@@ -0,0 +1,33 @@
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh3;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ3;;;;
+TEL;X-NEC-SECRET:9
+TEL;X-NEC-HOTEL:10
+TEL;X-NEC-SCHOOL:11
+TEL;HOME;FAX:12
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh4;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ4;;;;
+TEL;MODEM:13
+TEL;PAGER:14
+TEL;X-NEC-FAMILY:15
+TEL;X-NEC-GIRL:16
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+N;CHARSET=SHIFT_JIS:ˆÀ“¡ƒƒCƒh5;;;;
+SOUND;X-IRMC-N;CHARSET=SHIFT_JIS:±ÝÄÞ³Û²ÄÞ5;;;;
+TEL;X-NEC-BOY:17
+TEL;X-NEC-FRIEND:18
+TEL;X-NEC-PHS:19
+TEL;X-NEC-RESTAURANT:20
+END:VCARD
+
+
diff --git a/core/tests/coretests/res/raw/v21_org_before_title.vcf b/core/tests/coretests/res/raw/v21_org_before_title.vcf
new file mode 100644
index 0000000..8ff1190
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_org_before_title.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Normal Guy
+ORG:Company;Organization;Devision;Room;Sheet No.
+TITLE:Excellent Janitor
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_pref_handling.vcf b/core/tests/coretests/res/raw/v21_pref_handling.vcf
new file mode 100644
index 0000000..5105310
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_pref_handling.vcf
@@ -0,0 +1,15 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Smith
+TEL;HOME:1
+TEL;WORK;PREF:2
+TEL;ISDN:3
+EMAIL;PREF;HOME:test@example.com
+EMAIL;CELL;PREF:test2@examination.com
+ORG:Company
+TITLE:Engineer
+ORG:Mystery
+TITLE:Blogger
+ORG:Poetry
+TITLE:Poet
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_1.vcf b/core/tests/coretests/res/raw/v21_simple_1.vcf
new file mode 100644
index 0000000..6aabb4c
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_simple_1.vcf
@@ -0,0 +1,3 @@
+BEGIN:VCARD
+N:Ando;Roid;
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_2.vcf b/core/tests/coretests/res/raw/v21_simple_2.vcf
new file mode 100644
index 0000000..f0d5ab5
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_simple_2.vcf
@@ -0,0 +1,3 @@
+BEGIN:VCARD
+FN:Ando Roid
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_simple_3.vcf b/core/tests/coretests/res/raw/v21_simple_3.vcf
new file mode 100644
index 0000000..beddabb
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_simple_3.vcf
@@ -0,0 +1,4 @@
+BEGIN:VCARD
+N:Ando;Roid;
+FN:Ando Roid
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_title_before_org.vcf b/core/tests/coretests/res/raw/v21_title_before_org.vcf
new file mode 100644
index 0000000..9fdc738
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_title_before_org.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:Nice Guy
+TITLE:Cool Title
+ORG:Marverous;Perfect;Great;Good;Bad;Poor
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_winmo_65.vcf b/core/tests/coretests/res/raw/v21_winmo_65.vcf
new file mode 100644
index 0000000..f380d0d
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_winmo_65.vcf
@@ -0,0 +1,10 @@
+BEGIN:VCARD
+VERSION:2.1
+N:Example;;;;
+FN:Example
+ANNIVERSARY;VALUE=DATE:20091010
+AGENT:Invalid line which must be handled correctly.
+X-CLASS:PUBLIC
+X-REDUCTION:
+X-NO:
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_comma_separated.vcf b/core/tests/coretests/res/raw/v30_comma_separated.vcf
new file mode 100644
index 0000000..98a7f20
--- /dev/null
+++ b/core/tests/coretests/res/raw/v30_comma_separated.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_simple.vcf b/core/tests/coretests/res/raw/v30_simple.vcf
new file mode 100644
index 0000000..418661f
--- /dev/null
+++ b/core/tests/coretests/res/raw/v30_simple.vcf
@@ -0,0 +1,13 @@
+BEGIN:VCARD
+VERSION:3.0
+FN:And Roid
+N:And;Roid;;;
+ORG:Open;Handset; Alliance
+SORT-STRING:android
+TEL;TYPE=PREF;TYPE=VOICE:0300000000
+CLASS:PUBLIC
+X-GNO:0
+X-GN:group0
+X-REDUCTION:0
+REV:20081031T065854Z
+END:VCARD
diff --git a/core/tests/coretests/res/raw/wild_alt_name_only.crt b/core/tests/coretests/res/raw/wild_alt_name_only.crt
new file mode 100644
index 0000000..fafdebf
--- /dev/null
+++ b/core/tests/coretests/res/raw/wild_alt_name_only.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuzCCAaOgAwIBAgIJAP82tgcvmAGxMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
+BAYTAkpQMCAXDTEwMDExMjIxMDAyN1oYDzIwNjQxMDE1MjEwMDI3WjANMQswCQYD
+VQQGEwJKUDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs528EQbcB1
+x4BwxthQBZrgDJzoO7KPV3dhGYoeP8EnRjapZm+T/sj9P/O4HvfxjnB+fsjYSdmE
+WWUtnFrP7wtG9DUC748Ea2PMV8WFhOG58dqBNIko5XzkHB7SxkNZD5S/0KQYMGLr
+rchDsDlmsEf2Qb6qiqpNEU70aSkExZJcH+B9nWdeBpsVFu7wtezwSWEc2NUa2bhW
+gcXQ/aafwHZ4o2PyGwy0sgS/UifqO9tEllC2tPleSNJOmYsVudv5Bz4Q0GG38BSz
+Pc0IcOoln0ZWpXbGr03V2vlXWCwzaFAl3I1T3O7YVqDiaSWoP+d0tHZzmw8aJLXd
+B+KaUUGxRPsCAwEAAaMcMBowGAYDVR0RBBEwD4INKi5leGFtcGxlLmNvbTANBgkq
+hkiG9w0BAQUFAAOCAQEAJbVan4QgJ0cvpJnK9UWIVJNC+UbP87RC5go2fQiTnmGv
+prOrIuMqz1+vGcpIheLTLctJRHPoadXq0+UbQEIaU3pQbY6C4nNdfl+hcvmJeqrt
+kOCcvmIamO68iNvTSeszuHuu4O38PefrW2Xd0nn7bjFZrzBzHFhTudmnqNliP3ue
+KKQpqkUt5lCytnH8V/u/UCWdvVx5LnUa2XFGVLi3ongBIojW5fvF+yxn9ADqxdrI
+va++ow5r1VxQXFJc0ZPzsDo+6TlktoDHaRQJGMqQomqHWT4i7F5UZgf6BHGfEUPU
+qep+GsF3QRHSBtpObWkVDZNFvky3a1iZ2q25+hFIqQ==
+-----END CERTIFICATE-----
diff --git a/core/tests/coretests/res/values/arrays.xml b/core/tests/coretests/res/values/arrays.xml
new file mode 100644
index 0000000..f76da85
--- /dev/null
+++ b/core/tests/coretests/res/values/arrays.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string-array name="reminder_minutes_labels">
+ <item>5 minutes</item>
+ <item>10 minutes</item>
+ <item>15 minutes</item>
+ <item>20 minutes</item>
+ <item>25 minutes</item>
+ <item>30 minutes</item>
+ <item>45 minutes</item>
+ <item>1 hour</item>
+ <item>2 hours</item>
+ <item>3 hours</item>
+ <item>12 hours</item>
+ <item>24 hours</item>
+ <item>2 days</item>
+ <item>1 week</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values-mcc262-nb/strings.xml b/core/tests/coretests/res/values/attrs.xml
index a90e7cf..bf59e74 100644
--- a/core/res/res/values-mcc262-nb/strings.xml
+++ b/core/tests/coretests/res/values/attrs.xml
@@ -1,19 +1,21 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="273407696421660814">"de_de"</string>
+
+<resources>
+ <declare-styleable name="SelectableRowView">
+ <attr name="numRows" format="integer" />
+ </declare-styleable>
</resources>
diff --git a/core/tests/coretests/res/values/colors.xml b/core/tests/coretests/res/values/colors.xml
new file mode 100644
index 0000000..f881660
--- /dev/null
+++ b/core/tests/coretests/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/samples/SampleCode/res/values/colors.xml
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+ <drawable name="red">#ffff0000</drawable>
+ <drawable name="blue">#ff0000ff</drawable>
+ <drawable name="green">#ff00ff00</drawable>
+ <drawable name="yellow">#ffffff00</drawable>
+</resources>
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml
new file mode 100644
index 0000000..807386a
--- /dev/null
+++ b/core/tests/coretests/res/values/strings.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">FrameworkTestApplication</string>
+
+ <!-- FocusAfterRemoval -->
+ <string name="left_top">parent GONE</string>
+ <string name="left_bottom">parent INVISIBLE</string>
+ <string name="right_top">me GONE</string>
+ <string name="right_bottom">me INVISIBLE</string>
+
+ <!-- Strings for the 12 key dialer -->
+ <string name="keypad_one">1</string>
+ <string name="keypad_two">"2\nabc"</string>
+ <string name="keypad_three">"3\ndef"</string>
+ <string name="keypad_four">"4\nghi"</string>
+ <string name="keypad_five">"5\njkl"</string>
+ <string name="keypad_six">"6\nmno"</string>
+ <string name="keypad_seven">"7\npqrs"</string>
+ <string name="keypad_eight">"8\ntuv"</string>
+ <string name="keypad_nine">"9\nwxyz"</string>
+ <string name="keypad_zero">0</string>
+ <string name="keypad_star">*</string>
+ <string name="keypad_pound">"#"</string>
+
+
+ <!-- HorizontalGravity -->
+ <string name="table_layout_open">Open\u2026</string>
+ <string name="table_layout_open_shortcut">Ctrl-O</string>
+ <string name="table_layout_save_as">Save As\u2026</string>
+ <string name="table_layout_save_as_shortcut">Ctrl-Shift-S</string>
+ <string name="table_layout_export">Export\u2026</string>
+ <string name="table_layout_export_shortcut">Ctrl-E</string>
+
+ <!-- CellSpan -->
+ <string name="table_layout_a">A</string>
+ <string name="table_layout_b">BB</string>
+ <string name="table_layout_c">CCCC</string>
+ <string name="table_layout_d">D</string>
+ <string name="table_layout_e">E</string>
+ <string name="table_layout_f">F</string>
+ <string name="table_layout_g">G</string>
+ <string name="table_layout_h">H</string>
+
+ <!-- scroll_to_rectangle -->
+ <string name="scroll_top_button">Click to make "hi!" on screen</string>
+ <string name="scroll_top_button2">Click to make blob on screen</string>
+ <string name="scroll_to_me">hi!</string>
+ <string name="scroll_bottom_button2">Click to make blob on screen</string>
+ <string name="scroll_bottom_button">Click to make "hi!" on screen</string>
+
+ <!-- Visibility -->
+ <string name="visibility_1_view_1">View A</string>
+ <string name="visibility_1_view_2">View B</string>
+ <string name="visibility_1_view_3">View C</string>
+ <string name="visibility_1_vis">Visible</string>
+ <string name="visibility_1_invis">Invisible</string>
+ <string name="visibility_1_gone">Gone</string>
+
+ <string name="submit">submit</string>
+ <string name="cancel">cancel</string>
+ <string name="enter_title">enter title</string>
+ <string name="enter_url">enter url</string>
+
+ <!-- List with button above -->
+ <string name="button_above_list_label">Button above list</string>
+
+ <!-- List take focus from side -->
+ <string name="side_button_label">X</string>
+
+ <!-- Linear Layout show/hide & sizing -->
+ <string name="linear_listheight_fixed">Fixed Size</string>
+ <string name="linear_listheight_fill">Fill Screen</string>
+ <string name="linear_listheight_hide">Hide</string>
+
+ <!-- Baseline -->
+ <string name="show">Show</string>
+ <string name="time">00:00:00</string>
+
+ <!-- Drawable -->
+ <string name="minimal_text">a</string>
+ <string name="change_backgrounds">Change backgrounds</string>
+
+ <!-- List with empty view -->
+ <string name="empty_list">Empty list</string>
+ <string name="menu_add">Add</string>
+ <string name="menu_remove">Remove</string>
+
+ <!-- Misc. -->
+ <string name="add_row_button">Add Row</string>
+ <string name="disabled_button">Disabled</string>
+ <string name="translucent_background">Example of how you can make an
+ activity have a translucent background, compositing over
+ whatever is behind it.</string>
+ <string name="text">Lorem ipsum datum carat casus belli honorare</string>
+
+ <string name="hide">Hide</string>
+ <string name="go">Go</string>
+
+ <string name="include_label">Included views below:</string>
+ <string name="include_button">I was included!</string>
+
+ <string name="view">View</string>
+
+ <string name="layout_five_text_text">S</string>
+ <string name="layout_four_text_text">S</string>
+ <string name="layout_six_text_text">S</string>
+
+ <string name="menu_test">test</string>
+ <string name="metadata_text">text</string>
+
+ <string name="permlab_testGranted">Test Granted</string>
+ <string name="permdesc_testGranted">Used for running unit tests, for
+ testing operations where we have the permission.</string>
+ <string name="permlab_testDenied">Test Denied</string>
+ <string name="permdesc_testDenied">Used for running unit tests, for
+ testing operations where we do not have the permission.</string>
+
+ <string name="searchable_label">SearchManager Test</string>
+ <string name="searchable_hint">A search hint</string>
+</resources>
diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml
new file mode 100644
index 0000000..7a90197
--- /dev/null
+++ b/core/tests/coretests/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+ <style name="Theme" parent="android:Theme">
+ <item name="android:windowAnimationStyle">@style/Animation</item>
+ </style>
+
+ <style name="Animation">
+ <item name="android:activityOpenEnterAnimation">@null</item>
+ <item name="android:activityOpenExitAnimation">@null</item>
+ <item name="android:activityCloseEnterAnimation">@null</item>
+ <item name="android:activityCloseExitAnimation">@null</item>
+ <item name="android:taskOpenEnterAnimation">@null</item>
+ <item name="android:taskOpenExitAnimation">@null</item>
+ <item name="android:taskCloseEnterAnimation">@null</item>
+ <item name="android:taskCloseExitAnimation">@null</item>
+ <item name="android:taskToFrontEnterAnimation">@null</item>
+ <item name="android:taskToFrontExitAnimation">@null</item>
+ <item name="android:taskToBackEnterAnimation">@null</item>
+ <item name="android:taskToBackExitAnimation">@null</item>
+ </style>
+</resources>
diff --git a/core/res/res/values-mcc232-nb/strings.xml b/core/tests/coretests/res/xml/metadata.xml
index b028927..e352f27 100644
--- a/core/res/res/values-mcc232-nb/strings.xml
+++ b/core/tests/coretests/res/xml/metadata.xml
@@ -1,19 +1,23 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="166900303893651370">"de_at"</string>
-</resources>
+
+<thedata xmlns:android="http://schemas.android.com/apk/res/android"
+ rawText="some raw text"
+ rawColor="#ffffff00"
+ android:color="#f00"
+ android:text="@string/metadata_text"
+
+/>
diff --git a/core/res/res/drawable/pickerbox.xml b/core/tests/coretests/res/xml/metadata_app.xml
index 9cb2436..c37e6ba 100644
--- a/core/res/res/drawable/pickerbox.xml
+++ b/core/tests/coretests/res/xml/metadata_app.xml
@@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,8 +14,11 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/pickerbox_selected" />
- <item android:drawable="@drawable/pickerbox_unselected" />
-</selector>
+<thedata xmlns:android="http://schemas.android.com/apk/res/android"
+ rawText="some raw text"
+ rawColor="#ffffff00"
+ android:color="#f00"
+ android:text="@string/metadata_text"
+ appInfo="true"
+/>
diff --git a/core/tests/coretests/res/xml/searchable.xml b/core/tests/coretests/res/xml/searchable.xml
new file mode 100644
index 0000000..9d293b5
--- /dev/null
+++ b/core/tests/coretests/res/xml/searchable.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ android:label="@string/searchable_label"
+ android:hint="@string/searchable_hint"
+ android:searchSuggestAuthority="com.android.unit_tests.SuggestionProvider"
+ >
+
+ <actionkey android:keycode="KEYCODE_CALL"
+ android:suggestActionMsgColumn="suggest_action_msg_call" />
+
+</searchable> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java
new file mode 100644
index 0000000..2a51eea
--- /dev/null
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityTestService.java
@@ -0,0 +1,167 @@
+/*
+ * 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.accessibilityservice;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Notification;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This class text the accessibility framework end to end.
+ * <p>
+ * Note: Since accessibility is provided by {@link AccessibilityService}s we create one,
+ * and it generates an event and an interruption dispatching them through the
+ * {@link AccessibilityManager}. We verify the received result. To trigger the test
+ * go to Settings->Accessibility and select the enable accessibility check and then
+ * select the check for this service (same name as the class).
+ */
+public class AccessibilityTestService extends AccessibilityService {
+
+ private static final String LOG_TAG = "AccessibilityTestService";
+
+ private static final String CLASS_NAME = "foo.bar.baz.Test";
+ private static final String PACKAGE_NAME = "foo.bar.baz";
+ private static final String TEXT = "Some stuff";
+ private static final String BEFORE_TEXT = "Some other stuff";
+
+ private static final String CONTENT_DESCRIPTION = "Content description";
+
+ private static final int ITEM_COUNT = 10;
+ private static final int CURRENT_ITEM_INDEX = 1;
+ private static final int INTERRUPT_INVOCATION_TYPE = 0x00000200;
+
+ private static final int FROM_INDEX = 1;
+ private static final int ADDED_COUNT = 2;
+ private static final int REMOVED_COUNT = 1;
+
+ private static final int NOTIFICATION_TIMEOUT_MILLIS = 80;
+
+ private int mReceivedResult;
+
+ private Timer mTimer = new Timer();
+
+ @Override
+ public void onServiceConnected() {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
+ info.notificationTimeout = NOTIFICATION_TIMEOUT_MILLIS;
+ info.flags &= AccessibilityServiceInfo.DEFAULT;
+ setServiceInfo(info);
+
+ // we need to wait until the system picks our configuration
+ // otherwise it will not notify us
+ mTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ testAccessibilityEventDispatching();
+ testInterrupt();
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error in testing Accessibility feature", e);
+ }
+ }
+ }, 1000);
+ }
+
+ /**
+ * Check here if the event we received is actually the one we sent.
+ */
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ assert(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED == event.getEventType());
+ assert(event != null);
+ assert(event.getEventTime() > 0);
+ assert(CLASS_NAME.equals(event.getClassName()));
+ assert(PACKAGE_NAME.equals(event.getPackageName()));
+ assert(1 == event.getText().size());
+ assert(TEXT.equals(event.getText().get(0)));
+ assert(BEFORE_TEXT.equals(event.getBeforeText()));
+ assert(event.isChecked());
+ assert(CONTENT_DESCRIPTION.equals(event.getContentDescription()));
+ assert(ITEM_COUNT == event.getItemCount());
+ assert(CURRENT_ITEM_INDEX == event.getCurrentItemIndex());
+ assert(event.isEnabled());
+ assert(event.isPassword());
+ assert(FROM_INDEX == event.getFromIndex());
+ assert(ADDED_COUNT == event.getAddedCount());
+ assert(REMOVED_COUNT == event.getRemovedCount());
+ assert(event.getParcelableData() != null);
+ assert(1 == ((Notification) event.getParcelableData()).icon);
+
+ // set the type of the receved request
+ mReceivedResult = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
+ }
+
+ /**
+ * Set a flag that we received the interrupt request.
+ */
+ @Override
+ public void onInterrupt() {
+
+ // set the type of the receved request
+ mReceivedResult = INTERRUPT_INVOCATION_TYPE;
+ }
+
+ /**
+ * If an {@link AccessibilityEvent} is sent and received correctly.
+ */
+ public void testAccessibilityEventDispatching() throws Exception {
+ AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+
+ assert(event != null);
+ event.setClassName(CLASS_NAME);
+ event.setPackageName(PACKAGE_NAME);
+ event.getText().add(TEXT);
+ event.setBeforeText(BEFORE_TEXT);
+ event.setChecked(true);
+ event.setContentDescription(CONTENT_DESCRIPTION);
+ event.setItemCount(ITEM_COUNT);
+ event.setCurrentItemIndex(CURRENT_ITEM_INDEX);
+ event.setEnabled(true);
+ event.setPassword(true);
+ event.setFromIndex(FROM_INDEX);
+ event.setAddedCount(ADDED_COUNT);
+ event.setRemovedCount(REMOVED_COUNT);
+ event.setParcelableData(new Notification(1, "Foo", 1234));
+
+ AccessibilityManager.getInstance(this).sendAccessibilityEvent(event);
+
+ assert(mReceivedResult == event.getEventType());
+
+ Log.i(LOG_TAG, "AccessibilityTestService#testAccessibilityEventDispatching: Success");
+ }
+
+ /**
+ * If accessibility feedback interruption is triggered and received correctly.
+ */
+ public void testInterrupt() throws Exception {
+ AccessibilityManager.getInstance(this).interrupt();
+
+ assert(INTERRUPT_INVOCATION_TYPE == mReceivedResult);
+
+ Log.i(LOG_TAG, "AccessibilityTestService#testInterrupt: Success");
+ }
+}
+
diff --git a/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java b/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java
new file mode 100644
index 0000000..394b9f2
--- /dev/null
+++ b/core/tests/coretests/src/android/accounts/AccountManagerServiceTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accounts;
+
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.IsolatedContext;
+import android.test.mock.MockContext;
+import android.test.mock.MockContentResolver;
+import android.content.*;
+import android.accounts.Account;
+import android.accounts.AccountManagerService;
+import android.os.Bundle;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class AccountManagerServiceTest extends AndroidTestCase {
+ @Override
+ protected void setUp() throws Exception {
+ final String filenamePrefix = "test.";
+ MockContentResolver resolver = new MockContentResolver();
+ RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(
+ new MockContext(), // The context that most methods are delegated to
+ getContext(), // The context that file methods are delegated to
+ filenamePrefix);
+ Context context = new IsolatedContext(resolver, targetContextWrapper);
+ setContext(context);
+ }
+
+ public class AccountSorter implements Comparator<Account> {
+ public int compare(Account object1, Account object2) {
+ if (object1 == object2) return 0;
+ if (object1 == null) return 1;
+ if (object2 == null) return -1;
+ int result = object1.type.compareTo(object2.type);
+ if (result != 0) return result;
+ return object1.name.compareTo(object2.name);
+ }
+ }
+
+ public void testCheckAddAccount() throws Exception {
+ AccountManagerService ams = new AccountManagerService(getContext());
+ Account a11 = new Account("account1", "type1");
+ Account a21 = new Account("account2", "type1");
+ Account a31 = new Account("account3", "type1");
+ Account a12 = new Account("account1", "type2");
+ Account a22 = new Account("account2", "type2");
+ Account a32 = new Account("account3", "type2");
+ ams.addAccount(a11, "p11", null);
+ ams.addAccount(a12, "p12", null);
+ ams.addAccount(a21, "p21", null);
+ ams.addAccount(a22, "p22", null);
+ ams.addAccount(a31, "p31", null);
+ ams.addAccount(a32, "p32", null);
+
+ Account[] accounts = ams.getAccounts(null);
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(6, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a21, accounts[1]);
+ assertEquals(a31, accounts[2]);
+ assertEquals(a12, accounts[3]);
+ assertEquals(a22, accounts[4]);
+ assertEquals(a32, accounts[5]);
+
+ accounts = ams.getAccountsByType("type1" );
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(3, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a21, accounts[1]);
+ assertEquals(a31, accounts[2]);
+
+ ams.removeAccount(null, a21);
+
+ accounts = ams.getAccountsByType("type1" );
+ Arrays.sort(accounts, new AccountSorter());
+ assertEquals(2, accounts.length);
+ assertEquals(a11, accounts[0]);
+ assertEquals(a31, accounts[1]);
+ }
+
+ public void testPasswords() throws Exception {
+ AccountManagerService ams = new AccountManagerService(getContext());
+ Account a11 = new Account("account1", "type1");
+ Account a12 = new Account("account1", "type2");
+ ams.addAccount(a11, "p11", null);
+ ams.addAccount(a12, "p12", null);
+
+ assertEquals("p11", ams.getPassword(a11));
+ assertEquals("p12", ams.getPassword(a12));
+
+ ams.setPassword(a11, "p11b");
+
+ assertEquals("p11b", ams.getPassword(a11));
+ assertEquals("p12", ams.getPassword(a12));
+ }
+
+ public void testUserdata() throws Exception {
+ AccountManagerService ams = new AccountManagerService(getContext());
+ Account a11 = new Account("account1", "type1");
+ Bundle u11 = new Bundle();
+ u11.putString("a", "a_a11");
+ u11.putString("b", "b_a11");
+ u11.putString("c", "c_a11");
+ Account a12 = new Account("account1", "type2");
+ Bundle u12 = new Bundle();
+ u12.putString("a", "a_a12");
+ u12.putString("b", "b_a12");
+ u12.putString("c", "c_a12");
+ ams.addAccount(a11, "p11", u11);
+ ams.addAccount(a12, "p12", u12);
+
+ assertEquals("a_a11", ams.getUserData(a11, "a"));
+ assertEquals("b_a11", ams.getUserData(a11, "b"));
+ assertEquals("c_a11", ams.getUserData(a11, "c"));
+ assertEquals("a_a12", ams.getUserData(a12, "a"));
+ assertEquals("b_a12", ams.getUserData(a12, "b"));
+ assertEquals("c_a12", ams.getUserData(a12, "c"));
+
+ ams.setUserData(a11, "b", "b_a11b");
+
+ assertEquals("a_a11", ams.getUserData(a11, "a"));
+ assertEquals("b_a11b", ams.getUserData(a11, "b"));
+ assertEquals("c_a11", ams.getUserData(a11, "c"));
+ assertEquals("a_a12", ams.getUserData(a12, "a"));
+ assertEquals("b_a12", ams.getUserData(a12, "b"));
+ assertEquals("c_a12", ams.getUserData(a12, "c"));
+ }
+
+ public void testAuthtokens() throws Exception {
+ AccountManagerService ams = new AccountManagerService(getContext());
+ Account a11 = new Account("account1", "type1");
+ Account a12 = new Account("account1", "type2");
+ ams.addAccount(a11, "p11", null);
+ ams.addAccount(a12, "p12", null);
+
+ ams.setAuthToken(a11, "att1", "a11_att1");
+ ams.setAuthToken(a11, "att2", "a11_att2");
+ ams.setAuthToken(a11, "att3", "a11_att3");
+ ams.setAuthToken(a12, "att1", "a12_att1");
+ ams.setAuthToken(a12, "att2", "a12_att2");
+ ams.setAuthToken(a12, "att3", "a12_att3");
+
+ assertEquals("a11_att1", ams.peekAuthToken(a11, "att1"));
+ assertEquals("a11_att2", ams.peekAuthToken(a11, "att2"));
+ assertEquals("a11_att3", ams.peekAuthToken(a11, "att3"));
+ assertEquals("a12_att1", ams.peekAuthToken(a12, "att1"));
+ assertEquals("a12_att2", ams.peekAuthToken(a12, "att2"));
+ assertEquals("a12_att3", ams.peekAuthToken(a12, "att3"));
+
+ ams.setAuthToken(a11, "att3", "a11_att3b");
+ ams.invalidateAuthToken(a12.type, "a12_att2");
+
+ assertEquals("a11_att1", ams.peekAuthToken(a11, "att1"));
+ assertEquals("a11_att2", ams.peekAuthToken(a11, "att2"));
+ assertEquals("a11_att3b", ams.peekAuthToken(a11, "att3"));
+ assertEquals("a12_att1", ams.peekAuthToken(a12, "att1"));
+ assertNull(ams.peekAuthToken(a12, "att2"));
+ assertEquals("a12_att3", ams.peekAuthToken(a12, "att3"));
+
+ assertNull(ams.readAuthTokenFromDatabase(a12, "att2"));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/SearchManagerTest.java b/core/tests/coretests/src/android/app/SearchManagerTest.java
new file mode 100644
index 0000000..08b7f60
--- /dev/null
+++ b/core/tests/coretests/src/android/app/SearchManagerTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.app;
+
+import android.app.activity.LocalActivity;
+
+import android.app.Activity;
+import android.app.ISearchManager;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * To launch this test from the command line:
+ *
+ * adb shell am instrument -w \
+ * -e class com.android.unit_tests.SearchManagerTest \
+ * com.android.unit_tests/android.test.InstrumentationTestRunner
+ */
+public class SearchManagerTest extends ActivityInstrumentationTestCase2<LocalActivity> {
+
+ private ComponentName SEARCHABLE_ACTIVITY =
+ new ComponentName("com.android.frameworks.coretests",
+ "android.app.activity.SearchableActivity");
+
+ /*
+ * Bug list of test ideas.
+ *
+ * testSearchManagerInterfaceAvailable()
+ * Exercise the interface obtained
+ *
+ * testSearchManagerAvailable()
+ * Exercise the interface obtained
+ *
+ * testSearchManagerInvocations()
+ * FIX - make it work again
+ * stress test with a very long string
+ *
+ * SearchManager tests
+ * confirm proper identification of "default" activity based on policy, not hardcoded contacts
+ *
+ * SearchBar tests
+ * Maybe have to do with framework / unittest runner - need instrumented activity?
+ * How can we unit test the suggestions content providers?
+ * Should I write unit tests for any of them?
+ * Test scenarios:
+ * type-BACK (cancel)
+ * type-GO (send)
+ * type-navigate-click (suggestion)
+ * type-action
+ * type-navigate-action (suggestion)
+ */
+
+ /**
+ * Local copy of activity context
+ */
+ Context mContext;
+
+ public SearchManagerTest() {
+ super("com.android.frameworks.coretests", LocalActivity.class);
+ }
+
+ /**
+ * Setup any common data for the upcoming tests.
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ Activity testActivity = getActivity();
+ mContext = testActivity;
+ }
+
+ private ISearchManager getSearchManagerService() {
+ return ISearchManager.Stub.asInterface(
+ ServiceManager.getService(Context.SEARCH_SERVICE));
+ }
+
+ /**
+ * The goal of this test is to confirm that we can obtain
+ * a search manager interface.
+ */
+ @MediumTest
+ public void testSearchManagerInterfaceAvailable() {
+ assertNotNull(getSearchManagerService());
+ }
+
+ /**
+ * The goal of this test is to confirm that we can obtain
+ * a search manager at any time, and that for any given context,
+ * it is a singleton.
+ */
+ @LargeTest
+ public void testSearchManagerAvailable() {
+ SearchManager searchManager1 = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager1);
+ SearchManager searchManager2 = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager2);
+ assertSame(searchManager1, searchManager2 );
+ }
+
+ /**
+ * Tests that startSearch() can be called multiple times without stopSearch()
+ * in between.
+ */
+ public void testStartSearchIdempotent() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+ }
+
+ /**
+ * Tests that stopSearch() can be called when the search UI is not visible and can be
+ * called multiple times without startSearch() in between.
+ */
+ public void testStopSearchIdempotent() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+ searchManager.stopSearch();
+
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+ searchManager.stopSearch();
+ }
+
+ /**
+ * The goal of this test is to confirm that we can start and then
+ * stop a simple search.
+ */
+ public void testSearchManagerInvocations() throws Exception {
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
+ assertNotNull(searchManager);
+
+ // These tests should simply run to completion w/o exceptions
+ searchManager.startSearch(null, false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+
+ searchManager.startSearch("", false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+
+ searchManager.startSearch("test search string", false, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+
+ searchManager.startSearch("test search string", true, SEARCHABLE_ACTIVITY, null, false);
+ searchManager.stopSearch();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/SearchablesTest.java b/core/tests/coretests/src/android/app/SearchablesTest.java
new file mode 100644
index 0000000..6cb31d4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/SearchablesTest.java
@@ -0,0 +1,449 @@
+/*
+ * 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.SearchManager;
+import android.app.SearchableInfo;
+import android.app.SearchableInfo.ActionKeyInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.RemoteException;
+import android.server.search.Searchables;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.mock.MockContext;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * To launch this test from the command line:
+ *
+ * adb shell am instrument -w \
+ * -e class com.android.unit_tests.SearchablesTest \
+ * com.android.unit_tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class SearchablesTest extends AndroidTestCase {
+
+ /*
+ * SearchableInfo tests
+ * Mock the context so I can provide very specific input data
+ * Confirm OK with "zero" searchables
+ * Confirm "good" metadata read properly
+ * Confirm "bad" metadata skipped properly
+ * Confirm ordering of searchables
+ * Confirm "good" actionkeys
+ * confirm "bad" actionkeys are rejected
+ * confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+ * findActionKey works
+ * getIcon works
+ */
+
+ /**
+ * Test that non-searchable activities return no searchable info (this would typically
+ * trigger the use of the default searchable e.g. contacts)
+ */
+ public void testNonSearchable() {
+ // test basic array & hashmap
+ Searchables searchables = new Searchables(mContext);
+ searchables.buildSearchableList();
+
+ // confirm that we return null for non-searchy activities
+ ComponentName nonActivity = new ComponentName(
+ "com.android.frameworks.coretests",
+ "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
+ SearchableInfo si = searchables.getSearchableInfo(nonActivity);
+ assertNull(si);
+ }
+
+ /**
+ * This is an attempt to run the searchable info list with a mocked context. Here are some
+ * things I'd like to test.
+ *
+ * Confirm OK with "zero" searchables
+ * Confirm "good" metadata read properly
+ * Confirm "bad" metadata skipped properly
+ * Confirm ordering of searchables
+ * Confirm "good" actionkeys
+ * confirm "bad" actionkeys are rejected
+ * confirm XML ordering enforced (will fail today - bug in SearchableInfo)
+ * findActionKey works
+ * getIcon works
+
+ */
+ public void testSearchablesListReal() {
+ MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
+ MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+
+ // build item list with real-world source data
+ mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
+ Searchables searchables = new Searchables(mockContext);
+ searchables.buildSearchableList();
+ // tests with "real" searchables (deprecate, this should be a unit test)
+ ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
+ int count = searchablesList.size();
+ assertTrue(count >= 1); // this isn't really a unit test
+ checkSearchables(searchablesList);
+ ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
+ checkSearchables(global);
+ }
+
+ /**
+ * This round of tests confirms good operations with "zero" searchables found
+ */
+ public void testSearchablesListEmpty() {
+ MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
+ MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+
+ mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
+ Searchables searchables = new Searchables(mockContext);
+ searchables.buildSearchableList();
+ ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
+ assertNotNull(searchablesList);
+ MoreAsserts.assertEmpty(searchablesList);
+ ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
+ MoreAsserts.assertEmpty(global);
+ }
+
+ /**
+ * Generic health checker for an array of searchables.
+ *
+ * This is designed to pass for any semi-legal searchable, without knowing much about
+ * the format of the underlying data. It's fairly easy for a non-compliant application
+ * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
+ *
+ * @param searchables The list of searchables to examine.
+ */
+ private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
+ assertNotNull(searchablesList);
+ int count = searchablesList.size();
+ for (int ii = 0; ii < count; ii++) {
+ SearchableInfo si = searchablesList.get(ii);
+ checkSearchable(si);
+ }
+ }
+
+ private void checkSearchable(SearchableInfo si) {
+ assertNotNull(si);
+ assertTrue(si.getLabelId() != 0); // This must be a useable string
+ assertNotEmpty(si.getSearchActivity().getClassName());
+ assertNotEmpty(si.getSearchActivity().getPackageName());
+ if (si.getSuggestAuthority() != null) {
+ // The suggestion fields are largely optional, so we'll just confirm basic health
+ assertNotEmpty(si.getSuggestAuthority());
+ assertNullOrNotEmpty(si.getSuggestPath());
+ assertNullOrNotEmpty(si.getSuggestSelection());
+ assertNullOrNotEmpty(si.getSuggestIntentAction());
+ assertNullOrNotEmpty(si.getSuggestIntentData());
+ }
+ /* Add a way to get the entire action key list, then explicitly test its elements */
+ /* For now, test the most common action key (CALL) */
+ ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
+ if (ai != null) {
+ assertEquals(ai.getKeyCode(), KeyEvent.KEYCODE_CALL);
+ // one of these three fields must be non-null & non-empty
+ boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0);
+ boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0);
+ boolean m3 = (ai.getSuggestActionMsgColumn() != null) &&
+ (ai.getSuggestActionMsgColumn().length() > 0);
+ assertTrue(m1 || m2 || m3);
+ }
+
+ /*
+ * Find ways to test these:
+ *
+ * private int mSearchMode
+ * private Drawable mIcon
+ */
+
+ /*
+ * Explicitly not tested here:
+ *
+ * Can be null, so not much to see:
+ * public String mSearchHint
+ * private String mZeroQueryBanner
+ *
+ * To be deprecated/removed, so don't bother:
+ * public boolean mFilterMode
+ * public boolean mQuickStart
+ * private boolean mIconResized
+ * private int mIconResizeWidth
+ * private int mIconResizeHeight
+ *
+ * All of these are "internal" working variables, not part of any contract
+ * private ActivityInfo mActivityInfo
+ * private Rect mTempRect
+ * private String mSuggestProviderPackage
+ * private String mCacheActivityContext
+ */
+ }
+
+ /**
+ * Combo assert for "string not null and not empty"
+ */
+ private void assertNotEmpty(final String s) {
+ assertNotNull(s);
+ MoreAsserts.assertNotEqual(s, "");
+ }
+
+ /**
+ * Combo assert for "string null or (not null and not empty)"
+ */
+ private void assertNullOrNotEmpty(final String s) {
+ if (s != null) {
+ MoreAsserts.assertNotEqual(s, "");
+ }
+ }
+
+ /**
+ * This is a mock for context. Used to perform a true unit test on SearchableInfo.
+ *
+ */
+ private class MyMockContext extends MockContext {
+
+ protected Context mRealContext;
+ protected PackageManager mPackageManager;
+
+ /**
+ * Constructor.
+ *
+ * @param realContext Please pass in a real context for some pass-throughs to function.
+ */
+ MyMockContext(Context realContext, PackageManager packageManager) {
+ mRealContext = realContext;
+ mPackageManager = packageManager;
+ }
+
+ /**
+ * Resources. Pass through for now.
+ */
+ @Override
+ public Resources getResources() {
+ return mRealContext.getResources();
+ }
+
+ /**
+ * Package manager. Pass through for now.
+ */
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ /**
+ * Package manager. Pass through for now.
+ */
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws PackageManager.NameNotFoundException {
+ return mRealContext.createPackageContext(packageName, flags);
+ }
+
+ /**
+ * Message broadcast. Pass through for now.
+ */
+ @Override
+ public void sendBroadcast(Intent intent) {
+ mRealContext.sendBroadcast(intent);
+ }
+ }
+
+/**
+ * This is a mock for package manager. Used to perform a true unit test on SearchableInfo.
+ *
+ */
+ private class MyMockPackageManager extends MockPackageManager {
+
+ public final static int SEARCHABLES_PASSTHROUGH = 0;
+ public final static int SEARCHABLES_MOCK_ZERO = 1;
+ public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
+ public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
+
+ protected PackageManager mRealPackageManager;
+ protected int mSearchablesMode;
+
+ public MyMockPackageManager(PackageManager realPM) {
+ mRealPackageManager = realPM;
+ mSearchablesMode = SEARCHABLES_PASSTHROUGH;
+ }
+
+ /**
+ * Set the mode for various tests.
+ */
+ public void setSearchablesMode(int newMode) {
+ switch (newMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ case SEARCHABLES_MOCK_ZERO:
+ mSearchablesMode = newMode;
+ break;
+
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Find activities that support a given intent.
+ *
+ * Retrieve all activities that can be performed for the given intent.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags. The most important is
+ * MATCH_DEFAULT_ONLY, to limit the resolution to only
+ * those activities that support the CATEGORY_DEFAULT.
+ *
+ * @return A List<ResolveInfo> containing one entry for each matching
+ * Activity. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * resolveActivity(). If there are no matching activities, an empty
+ * list is returned.
+ */
+ @Override
+ public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ assertNotNull(intent);
+ assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
+ || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.queryIntentActivities(intent, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ return null;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public ResolveInfo resolveActivity(Intent intent, int flags) {
+ assertNotNull(intent);
+ assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
+ || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.resolveActivity(intent, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ return null;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Retrieve an XML file from a package. This is a low-level API used to
+ * retrieve XML meta data.
+ *
+ * @param packageName The name of the package that this xml is coming from.
+ * Can not be null.
+ * @param resid The resource identifier of the desired xml. Can not be 0.
+ * @param appInfo Overall information about <var>packageName</var>. This
+ * may be null, in which case the application information will be retrieved
+ * for you if needed; if you already have this information around, it can
+ * be much more efficient to supply it here.
+ *
+ * @return Returns an XmlPullParser allowing you to parse out the XML
+ * data. Returns null if the xml resource could not be found for any
+ * reason.
+ */
+ @Override
+ public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
+ assertNotNull(packageName);
+ MoreAsserts.assertNotEqual(packageName, "");
+ MoreAsserts.assertNotEqual(resid, 0);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.getXml(packageName, resid, appInfo);
+ case SEARCHABLES_MOCK_ZERO:
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Find a single content provider by its base path name.
+ *
+ * @param name The name of the provider to find.
+ * @param flags Additional option flags. Currently should always be 0.
+ *
+ * @return ContentProviderInfo Information about the provider, if found,
+ * else null.
+ */
+ @Override
+ public ProviderInfo resolveContentProvider(String name, int flags) {
+ assertNotNull(name);
+ MoreAsserts.assertNotEqual(name, "");
+ assertEquals(flags, 0);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.resolveContentProvider(name, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Get the activity information for a particular activity.
+ *
+ * @param name The name of the activity to find.
+ * @param flags Additional option flags.
+ *
+ * @return ActivityInfo Information about the activity, if found, else null.
+ */
+ @Override
+ public ActivityInfo getActivityInfo(ComponentName name, int flags)
+ throws NameNotFoundException {
+ assertNotNull(name);
+ MoreAsserts.assertNotEqual(name, "");
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.getActivityInfo(name, flags);
+ case SEARCHABLES_MOCK_ZERO:
+ throw new NameNotFoundException();
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int checkPermission(String permName, String pkgName) {
+ assertNotNull(permName);
+ assertNotNull(pkgName);
+ switch (mSearchablesMode) {
+ case SEARCHABLES_PASSTHROUGH:
+ return mRealPackageManager.checkPermission(permName, pkgName);
+ case SEARCHABLES_MOCK_ZERO:
+ return PackageManager.PERMISSION_DENIED;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/SuggestionProvider.java b/core/tests/coretests/src/android/app/SuggestionProvider.java
new file mode 100644
index 0000000..9fb7dcf
--- /dev/null
+++ b/core/tests/coretests/src/android/app/SuggestionProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+
+/** Simple test provider that runs in the local process.
+ *
+ * Used by {@link SearchManagerTest}.
+ */
+public class SuggestionProvider extends ContentProvider {
+ private static final String TAG = "SuggestionProvider";
+
+ private static final int SEARCH_SUGGESTIONS = 1;
+
+ private static final UriMatcher sURLMatcher = new UriMatcher(
+ UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY,
+ SEARCH_SUGGESTIONS);
+ sURLMatcher.addURI("*", SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
+ SEARCH_SUGGESTIONS);
+ }
+
+ private static final String[] COLUMNS = new String[] {
+ "_id",
+ SearchManager.SUGGEST_COLUMN_TEXT_1,
+ SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+ SearchManager.SUGGEST_COLUMN_QUERY
+ };
+
+ public SuggestionProvider() {
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SEARCH_SUGGESTIONS:
+ String query = url.getLastPathSegment();
+ MatrixCursor cursor = new MatrixCursor(COLUMNS);
+ String[] suffixes = { "", "a", " foo", "XXXXXXXXXXXXXXXXX" };
+ for (String suffix : suffixes) {
+ addRow(cursor, query + suffix);
+ }
+ return cursor;
+ default:
+ throw new IllegalArgumentException("Unknown URL: " + url);
+ }
+ }
+
+ private void addRow(MatrixCursor cursor, String string) {
+ long id = cursor.getCount();
+ cursor.newRow().add(id).add(string).add(Intent.ACTION_SEARCH).add(string);
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SEARCH_SUGGESTIONS:
+ return SearchManager.SUGGEST_MIME_TYPE;
+ default:
+ throw new IllegalArgumentException("Unknown URL: " + url);
+ }
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("update not supported");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("insert not supported");
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("delete not supported");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/TranslucentFancyActivity.java b/core/tests/coretests/src/android/app/TranslucentFancyActivity.java
new file mode 100644
index 0000000..ec5ad7a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/TranslucentFancyActivity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+
+/**
+ * <h3>Fancy Translucent Activity</h3>
+ *
+ * <p>This demonstrates the how to write an activity that is translucent,
+ * allowing windows underneath to show through, with a fancy
+ * compositing effect.</p>
+ *
+ * <h4>Demo</h4>
+ * App/Activity/Translucent Fancy
+ *
+ * <h4>Source files</h4>
+ * <table class="LinkTable">
+ * <tr>
+ * <td >src/com/android/samples/app/TranslucentFancyActivity.java</td>
+ * <td >The Translucent Fancy Screen implementation</td>
+ * </tr>
+ * <tr>
+ * <td >/res/any/layout/translucent_background.xml</td>
+ * <td >Defines contents of the screen</td>
+ * </tr>
+ * </table>
+ */
+public class TranslucentFancyActivity extends Activity
+{
+ /**
+ * Initialization of the Activity after it is first created. Must at least
+ * call {@link android.app.Activity#setContentView setContentView()} to
+ * describe what is to be displayed in the screen.
+ */
+ @Override
+ protected void onCreate(Bundle icicle)
+ {
+ // Be sure to call the super class.
+ super.onCreate(icicle);
+
+ // Have the system blur any windows behind this one.
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,
+ WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+ // See assets/res/any/layout/translucent_background.xml for this
+ // view layout definition, which is being set here as
+ // the content of our screen.
+ setContentView(R.layout.translucent_background);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/AbortReceiver.java b/core/tests/coretests/src/android/app/activity/AbortReceiver.java
new file mode 100644
index 0000000..fef1775
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/AbortReceiver.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Log;
+
+public class AbortReceiver extends BroadcastReceiver
+{
+ public AbortReceiver()
+ {
+ }
+
+ public void onReceive(Context context, Intent intent)
+ {
+ //Log.i("AbortReceiver", "onReceiveIntent!");
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(LaunchpadActivity.RECEIVER_ABORT);
+ caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+
+ // abort the broadcast!!!
+ abortBroadcast();
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
new file mode 100644
index 0000000..61d73bc
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.activity;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.content.res.Configuration;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class ActivityManagerTest extends AndroidTestCase {
+
+ protected Context mContext;
+ protected ActivityManager mActivityManager;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getContext();
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ }
+
+ // TODO should write a test for getRecentTasks()
+ // TODO should write a test for getRunningTasks()
+ // TODO should write a test for getMemoryInfo()
+
+ // TODO: Find a way to re-enable this. It fails if any other app has failed during startup.
+ // This is probably an OK assumption given the desired system status when we run unit tests,
+ // but it's not necessarily the right assumption for a unit test.
+ @Suppress
+ public void disabledTestErrorTasksEmpty() throws Exception {
+
+ List<ActivityManager.ProcessErrorStateInfo> errList;
+
+ errList = mActivityManager.getProcessesInErrorState();
+
+ // test: confirm list is empty
+ assertNull(errList);
+ }
+
+ // TODO: Force an activity into an error state - then see if we can catch it here?
+ @SmallTest
+ public void testErrorTasksWithError() throws Exception {
+
+ List<ActivityManager.ProcessErrorStateInfo> errList;
+
+ // TODO force another process into an error condition. How?
+
+ // test: confirm error list length is at least 1 under varying query lengths
+// checkErrorListMax(1,-1);
+
+ errList = mActivityManager.getProcessesInErrorState();
+
+ // test: the list itself is healthy
+ checkErrorListSanity(errList);
+
+ // test: confirm our application shows up in the list
+ }
+
+ // TODO: Force an activity into an ANR state - then see if we can catch it here?
+ @SmallTest
+ public void testErrorTasksWithANR() throws Exception {
+
+ List<ActivityManager.ProcessErrorStateInfo> errList;
+
+ // TODO: force an application into an ANR state
+
+ errList = mActivityManager.getProcessesInErrorState();
+
+ // test: the list itself is healthy
+ checkErrorListSanity(errList);
+
+ // test: confirm our ANR'ing application shows up in the list
+ }
+
+ @SmallTest
+ public void testGetDeviceConfigurationInfo() throws Exception {
+ ConfigurationInfo config = mActivityManager.getDeviceConfigurationInfo();
+ assertNotNull(config);
+ // Validate values against configuration retrieved from resources
+ Configuration vconfig = mContext.getResources().getConfiguration();
+ assertNotNull(vconfig);
+ assertEquals(config.reqKeyboardType, vconfig.keyboard);
+ assertEquals(config.reqTouchScreen, vconfig.touchscreen);
+ assertEquals(config.reqNavigation, vconfig.navigation);
+ if (vconfig.navigation == Configuration.NAVIGATION_NONAV) {
+ assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV);
+ }
+ if (vconfig.keyboard != Configuration.KEYBOARD_UNDEFINED) {
+ assertNotNull(config.reqInputFeatures & ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD);
+ }
+ }
+
+ // If any entries in appear in the list, sanity check them against all running applications
+ private void checkErrorListSanity(List<ActivityManager.ProcessErrorStateInfo> errList) {
+ if (errList == null) return;
+
+ Iterator<ActivityManager.ProcessErrorStateInfo> iter = errList.iterator();
+ while (iter.hasNext()) {
+ ActivityManager.ProcessErrorStateInfo info = iter.next();
+ assertNotNull(info);
+ // sanity checks
+ assertTrue((info.condition == ActivityManager.ProcessErrorStateInfo.CRASHED) ||
+ (info.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING));
+ // TODO look at each of these and consider a stronger test
+ // TODO can we cross-check at the process name via some other API?
+ // TODO is there a better test for strings, e.g. "assertIsLegalString")
+ assertNotNull(info.processName);
+ // reasonableness test for info.pid ?
+ assertNotNull(info.longMsg);
+ assertNotNull(info.shortMsg);
+ // is there any reasonable test for the crashData? Probably not.
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTests.java b/core/tests/coretests/src/android/app/activity/ActivityTests.java
new file mode 100644
index 0000000..c57fe98
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ActivityTests.java
@@ -0,0 +1,40 @@
+/*
+ * 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.app.activity;
+
+import junit.framework.TestSuite;
+
+public class ActivityTests {
+ public static final boolean DEBUG_LIFECYCLE = false;
+
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite(ActivityTests.class.getName());
+
+ suite.addTestSuite(BroadcastTest.class);
+ suite.addTestSuite(IntentSenderTest.class);
+ suite.addTestSuite(ActivityManagerTest.class);
+ suite.addTestSuite(LaunchTest.class);
+ suite.addTestSuite(LifecycleTest.class);
+ suite.addTestSuite(ServiceTest.class);
+ suite.addTestSuite(MetaDataTest.class);
+ // Remove temporarily until bug 1171309 is fixed.
+ //suite.addTestSuite(SubActivityTest.class);
+ suite.addTestSuite(SetTimeZonePermissionsTest.class);
+
+ return suite;
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
new file mode 100644
index 0000000..232abe2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
@@ -0,0 +1,212 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+
+public class ActivityTestsBase extends AndroidTestCase
+ implements PerformanceTestCase, LaunchpadActivity.CallingTest {
+ public static final String PERMISSION_GRANTED =
+ "com.android.frameworks.coretests.permission.TEST_GRANTED";
+ public static final String PERMISSION_DENIED =
+ "com.android.frameworks.coretests.permission.TEST_DENIED";
+
+ protected Intent mIntent;
+
+ private PerformanceTestCase.Intermediates mIntermediates;
+ private String mExpecting;
+
+ // Synchronization of activity result.
+ private boolean mFinished;
+ private int mResultCode = 0;
+ private Intent mData;
+ private RuntimeException mResultStack = null;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mIntent = new Intent(mContext, LaunchpadActivity.class);
+ mIntermediates = null;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mIntermediates = null;
+ super.tearDown();
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ public void setInternalIterations(int count) {
+ }
+
+ public void startTiming(boolean realTime) {
+ if (mIntermediates != null) {
+ mIntermediates.startTiming(realTime);
+ }
+ }
+
+ public void addIntermediate(String name) {
+ if (mIntermediates != null) {
+ mIntermediates.addIntermediate(name);
+ }
+ }
+
+ public void addIntermediate(String name, long timeInNS) {
+ if (mIntermediates != null) {
+ mIntermediates.addIntermediate(name, timeInNS);
+ }
+ }
+
+ public void finishTiming(boolean realTime) {
+ if (mIntermediates != null) {
+ mIntermediates.finishTiming(realTime);
+ }
+ }
+
+ public void activityFinished(int resultCode, Intent data, RuntimeException where) {
+ finishWithResult(resultCode, data, where);
+ }
+
+ public Intent editIntent() {
+ return mIntent;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public int startPerformance(Intermediates intermediates) {
+ mIntermediates = intermediates;
+ return 1;
+ }
+
+ public void finishGood() {
+ finishWithResult(Activity.RESULT_OK, null);
+ }
+
+ public void finishBad(String error) {
+ finishWithResult(Activity.RESULT_CANCELED, (new Intent()).setAction(error));
+ }
+
+ public void finishWithResult(int resultCode, Intent data) {
+ RuntimeException where = new RuntimeException("Original error was here");
+ where.fillInStackTrace();
+ finishWithResult(resultCode, data, where);
+ }
+
+ public void finishWithResult(int resultCode, Intent data, RuntimeException where) {
+ synchronized (this) {
+ //System.out.println("*** Activity finished!!");
+ mResultCode = resultCode;
+ mData = data;
+ mResultStack = where;
+ mFinished = true;
+ notifyAll();
+ }
+ }
+
+ public int runLaunchpad(String action) {
+ LaunchpadActivity.setCallingTest(this);
+
+ synchronized (this) {
+ mIntent.setAction(action);
+ mFinished = false;
+ //System.out.println("*** Starting: " + mIntent);
+ mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(mIntent);
+ }
+
+ return waitForResultOrThrow(60 * 1000);
+ }
+
+ public int waitForResultOrThrow(int timeoutMs) {
+ return waitForResultOrThrow(timeoutMs, null);
+ }
+
+ public int waitForResultOrThrow(int timeoutMs, String expected) {
+ int res = waitForResult(timeoutMs, expected);
+
+ if (res == Activity.RESULT_CANCELED) {
+ if (mResultStack != null) {
+ throw new RuntimeException(
+ mData != null ? mData.toString() : "Unable to launch",
+ mResultStack);
+ } else {
+ throw new RuntimeException(
+ mData != null ? mData.toString() : "Unable to launch");
+ }
+ }
+ return res;
+ }
+
+ public int waitForResult(int timeoutMs, String expected) {
+ mExpecting = expected;
+
+ long endTime = System.currentTimeMillis() + timeoutMs;
+
+ boolean timeout = false;
+ synchronized (this) {
+ while (!mFinished) {
+ long delay = endTime - System.currentTimeMillis();
+ if (delay < 0) {
+ timeout = true;
+ break;
+ }
+
+ try {
+ wait(delay);
+ } catch (java.lang.InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+
+ mFinished = false;
+
+ if (timeout) {
+ mResultCode = Activity.RESULT_CANCELED;
+ onTimeout();
+ }
+ return mResultCode;
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ public Intent getResultData() {
+ return mData;
+ }
+
+ public RuntimeException getResultStack() {
+ return mResultStack;
+ }
+
+ public void onTimeout() {
+ String msg = mExpecting == null
+ ? "Timeout" : ("Timeout while expecting " + mExpecting);
+ finishWithResult(Activity.RESULT_CANCELED, (new Intent()).setAction(msg));
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
new file mode 100644
index 0000000..4b1f9fd
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -0,0 +1,536 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class BroadcastTest extends ActivityTestsBase {
+ public static final int BROADCAST_TIMEOUT = 5 * 1000;
+
+ public static final String BROADCAST_REGISTERED =
+ "com.android.frameworks.coretests.activity.BROADCAST_REGISTERED";
+ public static final String BROADCAST_LOCAL =
+ "com.android.frameworks.coretests.activity.BROADCAST_LOCAL";
+ public static final String BROADCAST_LOCAL_GRANTED =
+ "com.android.frameworks.coretests.activity.BROADCAST_LOCAL_GRANTED";
+ public static final String BROADCAST_LOCAL_DENIED =
+ "com.android.frameworks.coretests.activity.BROADCAST_LOCAL_DENIED";
+ public static final String BROADCAST_REMOTE =
+ "com.android.frameworks.coretests.activity.BROADCAST_REMOTE";
+ public static final String BROADCAST_REMOTE_GRANTED =
+ "com.android.frameworks.coretests.activity.BROADCAST_REMOTE_GRANTED";
+ public static final String BROADCAST_REMOTE_DENIED =
+ "com.android.frameworks.coretests.activity.BROADCAST_REMOTE_DENIED";
+ public static final String BROADCAST_ALL =
+ "com.android.frameworks.coretests.activity.BROADCAST_ALL";
+ public static final String BROADCAST_MULTI =
+ "com.android.frameworks.coretests.activity.BROADCAST_MULTI";
+ public static final String BROADCAST_ABORT =
+ "com.android.frameworks.coretests.activity.BROADCAST_ABORT";
+
+ public static final String BROADCAST_STICKY1 =
+ "com.android.frameworks.coretests.activity.BROADCAST_STICKY1";
+ public static final String BROADCAST_STICKY2 =
+ "com.android.frameworks.coretests.activity.BROADCAST_STICKY2";
+
+ public static final String BROADCAST_FAIL_REGISTER =
+ "com.android.frameworks.coretests.activity.BROADCAST_FAIL_REGISTER";
+ public static final String BROADCAST_FAIL_BIND =
+ "com.android.frameworks.coretests.activity.BROADCAST_FAIL_BIND";
+
+ public static final String RECEIVER_REG = "receiver-reg";
+ public static final String RECEIVER_LOCAL = "receiver-local";
+ public static final String RECEIVER_REMOTE = "receiver-remote";
+ public static final String RECEIVER_ABORT = "receiver-abort";
+ public static final String RECEIVER_RESULTS = "receiver-results";
+
+ public static final String DATA_1 = "one";
+ public static final String DATA_2 = "two";
+
+ public static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+ public static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+ private String[] mExpectedReceivers = null;
+ private int mNextReceiver;
+
+ private String[] mExpectedData = null;
+ private boolean[] mReceivedData = null;
+
+ boolean mReceiverRegistered = false;
+
+ public void setExpectedReceivers(String[] receivers) {
+ mExpectedReceivers = receivers;
+ mNextReceiver = 0;
+ }
+
+ public void setExpectedData(String[] data) {
+ mExpectedData = data;
+ mReceivedData = new boolean[data.length];
+ }
+
+ public void onTimeout() {
+ String msg = "Timeout";
+ if (mExpectedReceivers != null && mNextReceiver < mExpectedReceivers.length) {
+ msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver];
+ }
+ finishBad(msg);
+ }
+
+ public Intent makeBroadcastIntent(String action) {
+ Intent intent = new Intent(action, null);
+ intent.putExtra("caller", mCallTarget);
+ return intent;
+ }
+
+ public void finishWithResult(int resultCode, Intent data) {
+ unregisterMyReceiver();
+ super.finishWithResult(resultCode, data);
+ }
+
+ public final void gotReceive(String name, Intent intent) {
+ synchronized (this) {
+
+ //System.out.println("Got receive: " + name);
+ //System.out.println(mNextReceiver + " in " + mExpectedReceivers);
+ //new RuntimeException("stack").printStackTrace();
+
+ addIntermediate(name);
+
+ if (mExpectedData != null) {
+ int n = mExpectedData.length;
+ int i;
+ boolean prev = false;
+ for (i = 0; i < n; i++) {
+ if (mExpectedData[i].equals(intent.getStringExtra("test"))) {
+ if (mReceivedData[i]) {
+ prev = true;
+ continue;
+ }
+ mReceivedData[i] = true;
+ break;
+ }
+ }
+ if (i >= n) {
+ if (prev) {
+ finishBad("Receive got data too many times: "
+ + intent.getStringExtra("test"));
+ } else {
+ finishBad("Receive got unexpected data: "
+ + intent.getStringExtra("test"));
+ }
+ new RuntimeException("stack").printStackTrace();
+ return;
+ }
+ }
+
+ if (mNextReceiver >= mExpectedReceivers.length) {
+ finishBad("Got too many onReceiveIntent() calls!");
+// System.out.println("Too many intents received: now at "
+// + mNextReceiver + ", expect list: "
+// + Arrays.toString(mExpectedReceivers));
+ fail("Got too many onReceiveIntent() calls!");
+ } else if (!mExpectedReceivers[mNextReceiver].equals(name)) {
+ finishBad("Receive out of order: got " + name
+ + " but expected "
+ + mExpectedReceivers[mNextReceiver]);
+ fail("Receive out of order: got " + name
+ + " but expected "
+ + mExpectedReceivers[mNextReceiver]);
+ } else {
+ mNextReceiver++;
+ if (mNextReceiver == mExpectedReceivers.length) {
+ finishTest();
+ }
+ }
+ }
+ }
+
+ public void registerMyReceiver(IntentFilter filter, String permission) {
+ mReceiverRegistered = true;
+ //System.out.println("Registering: " + mReceiver);
+ getContext().registerReceiver(mReceiver, filter, permission, null);
+ }
+
+ public void unregisterMyReceiver() {
+ if (mReceiverRegistered) {
+ unregisterMyReceiverNoCheck();
+ }
+ }
+
+ public void unregisterMyReceiverNoCheck() {
+ mReceiverRegistered = false;
+ //System.out.println("Unregistering: " + mReceiver);
+ getContext().unregisterReceiver(mReceiver);
+ }
+
+ public void onRegisteredReceiver(Intent intent) {
+ gotReceive(RECEIVER_REG, intent);
+ }
+
+ private Binder mCallTarget = new Binder() {
+ public boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) {
+ data.setDataPosition(0);
+ data.enforceInterface(LaunchpadActivity.LAUNCH);
+ if (code == GOT_RECEIVE_TRANSACTION) {
+ String name = data.readString();
+ gotReceive(name, null);
+ return true;
+ } else if (code == ERROR_TRANSACTION) {
+ finishBad(data.readString());
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private void finishTest() {
+ if (mReceiverRegistered) {
+ addIntermediate("before-unregister");
+ unregisterMyReceiver();
+ }
+ finishTiming(true);
+ finishGood();
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ //System.out.println("Receive in: " + this + ": " + intent);
+ onRegisteredReceiver(intent);
+ }
+ };
+
+ // Mark flaky until http://b/issue?id=1191607 is resolved.
+ @FlakyTest(tolerance=2)
+ public void testRegistered() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_REGISTERED);
+ }
+
+ public void testLocal() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_LOCAL);
+ }
+
+ public void testRemote() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_REMOTE);
+ }
+
+ public void testAbort() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_ABORT);
+ }
+
+ @FlakyTest(tolerance=2)
+ public void testAll() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_ALL);
+ }
+
+ @FlakyTest(tolerance=2)
+ public void testMulti() throws Exception {
+ runLaunchpad(LaunchpadActivity.BROADCAST_MULTI);
+ }
+
+ private class TestBroadcastReceiver extends BroadcastReceiver {
+ public boolean mHaveResult = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (BroadcastTest.this) {
+ mHaveResult = true;
+ BroadcastTest.this.notifyAll();
+ }
+ }
+ }
+
+ public void testResult() throws Exception {
+ TestBroadcastReceiver broadcastReceiver = new TestBroadcastReceiver();
+
+ synchronized (this) {
+ Bundle map = new Bundle();
+ map.putString("foo", "you");
+ map.putString("remove", "me");
+ getContext().sendOrderedBroadcast(
+ new Intent("com.android.frameworks.coretests.activity.BROADCAST_RESULT"),
+ null, broadcastReceiver, null, 1, "foo", map);
+ while (!broadcastReceiver.mHaveResult) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ //System.out.println("Code: " + mResultCode + ", data: " + mResultData);
+ //System.out.println("Extras: " + mResultExtras);
+
+ assertEquals("Incorrect code: " + broadcastReceiver.getResultCode(),
+ 3, broadcastReceiver.getResultCode());
+
+ assertEquals("bar", broadcastReceiver.getResultData());
+
+ Bundle resultExtras = broadcastReceiver.getResultExtras(false);
+ assertEquals("them", resultExtras.getString("bar"));
+ assertEquals("you", resultExtras.getString("foo"));
+ assertNull(resultExtras.getString("remove"));
+ }
+ }
+
+ public void testSetSticky() throws Exception {
+ Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_1);
+ ActivityManagerNative.getDefault().unbroadcastIntent(null, intent);
+
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ addIntermediate("finished-broadcast");
+
+ IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1);
+ Intent sticky = getContext().registerReceiver(null, filter);
+ assertNotNull("Sticky not found", sticky);
+ assertEquals(LaunchpadActivity.DATA_1, sticky.getStringExtra("test"));
+ }
+
+ public void testClearSticky() throws Exception {
+ Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_1);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+
+ ActivityManagerNative.getDefault().unbroadcastIntent(
+ null, new Intent(LaunchpadActivity.BROADCAST_STICKY1, null));
+ addIntermediate("finished-unbroadcast");
+
+ IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1);
+ Intent sticky = getContext().registerReceiver(null, filter);
+ assertNull("Sticky not found", sticky);
+ }
+
+ public void testReplaceSticky() throws Exception {
+ Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_1);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_2);
+
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ addIntermediate("finished-broadcast");
+
+ IntentFilter filter = new IntentFilter(LaunchpadActivity.BROADCAST_STICKY1);
+ Intent sticky = getContext().registerReceiver(null, filter);
+ assertNotNull("Sticky not found", sticky);
+ assertEquals(LaunchpadActivity.DATA_2, sticky.getStringExtra("test"));
+ }
+
+ // Marking flaky until http://b/issue?id=1191337 is resolved
+ @FlakyTest(tolerance=2)
+ public void testReceiveSticky() throws Exception {
+ Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_1);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+
+ runLaunchpad(LaunchpadActivity.BROADCAST_STICKY1);
+ }
+
+ // Marking flaky until http://b/issue?id=1191337 is resolved
+ @FlakyTest(tolerance=2)
+ public void testReceive2Sticky() throws Exception {
+ Intent intent = new Intent(LaunchpadActivity.BROADCAST_STICKY1, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_1);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ intent = new Intent(LaunchpadActivity.BROADCAST_STICKY2, null);
+ intent.putExtra("test", LaunchpadActivity.DATA_2);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+
+ runLaunchpad(LaunchpadActivity.BROADCAST_STICKY2);
+ }
+
+ public void testRegisteredReceivePermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_REG});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED);
+ addIntermediate("after-register");
+ getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_REGISTERED));
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRegisteredReceivePermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED);
+ addIntermediate("after-register");
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_REGISTERED),
+ null, finish, null, Activity.RESULT_CANCELED, null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRegisteredBroadcastPermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_REG});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null);
+ addIntermediate("after-register");
+ getContext().sendBroadcast(
+ makeBroadcastIntent(BROADCAST_REGISTERED),
+ PERMISSION_GRANTED);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRegisteredBroadcastPermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null);
+ addIntermediate("after-register");
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_REGISTERED),
+ PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED,
+ null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testLocalReceivePermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_LOCAL_GRANTED));
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testLocalReceivePermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_LOCAL_DENIED),
+ null, finish, null, Activity.RESULT_CANCELED,
+ null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testLocalBroadcastPermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ getContext().sendBroadcast(
+ makeBroadcastIntent(BROADCAST_LOCAL),
+ PERMISSION_GRANTED);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testLocalBroadcastPermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_LOCAL),
+ PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED,
+ null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRemoteReceivePermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_REMOTE});
+ getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_REMOTE_GRANTED));
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRemoteReceivePermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_REMOTE_DENIED),
+ null, finish, null, Activity.RESULT_CANCELED,
+ null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRemoteBroadcastPermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_REMOTE});
+ getContext().sendBroadcast(
+ makeBroadcastIntent(BROADCAST_REMOTE),
+ PERMISSION_GRANTED);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testRemoteBroadcastPermissionDenied() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+
+ BroadcastReceiver finish = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ getContext().sendOrderedBroadcast(
+ makeBroadcastIntent(BROADCAST_REMOTE),
+ PERMISSION_DENIED, finish, null, Activity.RESULT_CANCELED,
+ null, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testReceiverCanNotRegister() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_FAIL_REGISTER));
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testReceiverCanNotBind() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ getContext().sendBroadcast(makeBroadcastIntent(BROADCAST_FAIL_BIND));
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ }
+
+ public void testLocalUnregisterTwice() throws Exception {
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), null);
+ unregisterMyReceiverNoCheck();
+ try {
+ unregisterMyReceiverNoCheck();
+ fail("No exception thrown on second unregister");
+ } catch (IllegalArgumentException e) {
+ Log.i("foo", "Unregister exception", e);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ClearTop.java b/core/tests/coretests/src/android/app/activity/ClearTop.java
new file mode 100644
index 0000000..a5ee2ce
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ClearTop.java
@@ -0,0 +1,51 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ClearTop extends Activity {
+ public static final String WAIT_CLEAR_TASK = "waitClearTask";
+
+ public ClearTop() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ //Log.i("foo", "Creating: " + this);
+ Intent intent = new Intent(getIntent()).setAction(LocalScreen.CLEAR_TASK)
+ .setClass(this, LocalScreen.class);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ //Log.i("foo", "New intent in " + this + ": " + intent);
+ if (LocalScreen.CLEAR_TASK.equals(intent.getAction())) {
+ setResult(RESULT_OK);
+ } else {
+ setResult(RESULT_CANCELED, new Intent().setAction(
+ "New intent received " + intent + ", expecting action "
+ + TestedScreen.CLEAR_TASK));
+ }
+ finish();
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
new file mode 100644
index 0000000..3c30915
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.test.suitebuilder.annotation.Suppress;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.Suppress;
+
+public class IntentSenderTest extends BroadcastTest {
+
+ public void testRegisteredReceivePermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_REG});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED);
+ addIntermediate("after-register");
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
+ makeBroadcastIntent(BROADCAST_REGISTERED), 0);
+ is.send();
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ is.cancel();
+ }
+
+ public void testRegisteredReceivePermissionDenied() throws Exception {
+ final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED);
+ addIntermediate("after-register");
+
+ PendingIntent.OnFinished finish = new PendingIntent.OnFinished() {
+ public void onSendFinished(PendingIntent pi, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+ is.send(Activity.RESULT_CANCELED, finish, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ is.cancel();
+ }
+
+ public void testLocalReceivePermissionGranted() throws Exception {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
+ makeBroadcastIntent(BROADCAST_LOCAL_GRANTED), 0);
+ is.send();
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ is.cancel();
+ }
+
+ public void testLocalReceivePermissionDenied() throws Exception {
+ final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED);
+
+ setExpectedReceivers(new String[]{RECEIVER_RESULTS});
+
+ PendingIntent.OnFinished finish = new PendingIntent.OnFinished() {
+ public void onSendFinished(PendingIntent pi, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras) {
+ gotReceive(RECEIVER_RESULTS, intent);
+ }
+ };
+
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+ is.send(Activity.RESULT_CANCELED, finish, null);
+ waitForResultOrThrow(BROADCAST_TIMEOUT);
+ is.cancel();
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/LaunchTest.java b/core/tests/coretests/src/android/app/activity/LaunchTest.java
new file mode 100644
index 0000000..5893fd0
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LaunchTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.app.activity;
+
+import android.content.ComponentName;
+import android.test.suitebuilder.annotation.LargeTest;
+
+public class LaunchTest extends ActivityTestsBase {
+
+ @LargeTest
+ public void testColdActivity() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), TestedActivity.class));
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ @LargeTest
+ public void testLocalActivity() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), LocalActivity.class));
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ @LargeTest
+ public void testColdScreen() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), TestedScreen.class));
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ @LargeTest
+ public void testLocalScreen() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), LocalScreen.class));
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ @LargeTest
+ public void testForwardResult() throws Exception {
+ runLaunchpad(LaunchpadActivity.FORWARD_RESULT);
+ }
+
+ // The following is disabled until we can catch and recover from
+ // application errors.
+ public void xxtestBadParcelable() throws Exception {
+ // All we really care about for this test is that the system
+ // doesn't crash.
+ runLaunchpad(LaunchpadActivity.BAD_PARCELABLE);
+ }
+
+ @LargeTest
+ public void testClearTopInCreate() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), ClearTop.class));
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ @LargeTest
+ public void testClearTopWhileResumed() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), ClearTop.class));
+ mIntent.putExtra(ClearTop.WAIT_CLEAR_TASK, true);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+}
+
+
diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
new file mode 100644
index 0000000..7662456
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
@@ -0,0 +1,588 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.PerformanceTestCase;
+import android.util.Log;
+
+class MyBadParcelable implements Parcelable {
+ public MyBadParcelable() {
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString("I am bad");
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<MyBadParcelable> CREATOR
+ = new Parcelable.Creator<MyBadParcelable>() {
+ public MyBadParcelable createFromParcel(Parcel in) {
+ return new MyBadParcelable(in);
+ }
+
+ public MyBadParcelable[] newArray(int size) {
+ return new MyBadParcelable[size];
+ }
+ };
+
+ public MyBadParcelable(Parcel in) {
+ String nm = in.readString();
+ }
+}
+
+public class LaunchpadActivity extends Activity {
+ public interface CallingTest extends PerformanceTestCase.Intermediates {
+ public void startTiming(boolean realTime);
+ public void addIntermediate(String name);
+ public void addIntermediate(String name, long timeInNS);
+ public void finishTiming(boolean realTime);
+ public void activityFinished(int resultCode, Intent data,
+ RuntimeException where);
+ }
+
+ // Also used as the Binder interface descriptor string in these tests
+ public static final String LAUNCH = "com.android.frameworks.coretests.activity.LAUNCH";
+
+ public static final String FORWARD_RESULT =
+ "com.android.frameworks.coretests.activity.FORWARD_RESULT";
+ public static final String RETURNED_RESULT =
+ "com.android.frameworks.coretests.activity.RETURNED_RESULT";
+
+ public static final String BAD_PARCELABLE =
+ "comcom.android.frameworks.coretests.activity.BAD_PARCELABLE";
+
+ public static final int LAUNCHED_RESULT = 1;
+ public static final int FORWARDED_RESULT = 2;
+
+ public static final String LIFECYCLE_BASIC =
+ "com.android.frameworks.coretests.activity.LIFECYCLE_BASIC";
+ public static final String LIFECYCLE_SCREEN =
+ "com.android.frameworks.coretests.activity.LIFECYCLE_SCREEN";
+ public static final String LIFECYCLE_DIALOG =
+ "com.android.frameworks.coretests.activity.LIFECYCLE_DIALOG";
+ public static final String LIFECYCLE_FINISH_CREATE =
+ "com.android.frameworks.coretests.activity.LIFECYCLE_FINISH_CREATE";
+ public static final String LIFECYCLE_FINISH_START =
+ "com.android.frameworks.coretests.activity.LIFECYCLE_FINISH_START";
+
+ public static final String BROADCAST_REGISTERED =
+ "com.android.frameworks.coretests.activity.BROADCAST_REGISTERED";
+ public static final String BROADCAST_LOCAL =
+ "com.android.frameworks.coretests.activity.BROADCAST_LOCAL";
+ public static final String BROADCAST_REMOTE =
+ "com.android.frameworks.coretests.activity.BROADCAST_REMOTE";
+ public static final String BROADCAST_ALL =
+ "com.android.frameworks.coretests.activity.BROADCAST_ALL";
+ public static final String BROADCAST_REPEAT =
+ "com.android.frameworks.coretests.activity.BROADCAST_REPEAT";
+ public static final String BROADCAST_MULTI =
+ "com.android.frameworks.coretests.activity.BROADCAST_MULTI";
+ public static final String BROADCAST_ABORT =
+ "com.android.frameworks.coretests.activity.BROADCAST_ABORT";
+
+ public static final String BROADCAST_STICKY1 =
+ "com.android.frameworks.coretests.activity.BROADCAST_STICKY1";
+ public static final String BROADCAST_STICKY2 =
+ "com.android.frameworks.coretests.activity.BROADCAST_STICKY2";
+
+ public static final String RECEIVER_REG = "receiver-reg";
+ public static final String RECEIVER_LOCAL = "receiver-local";
+ public static final String RECEIVER_REMOTE = "receiver-remote";
+ public static final String RECEIVER_ABORT = "receiver-abort";
+
+ public static final String DATA_1 = "one";
+ public static final String DATA_2 = "two";
+
+ public static final String ON_START = "onStart";
+ public static final String ON_RESTART = "onRestart";
+ public static final String ON_RESUME = "onResume";
+ public static final String ON_FREEZE = "onSaveInstanceState";
+ public static final String ON_PAUSE = "onPause";
+ public static final String ON_STOP = "onStop";
+ public static final String ON_DESTROY = "onDestroy";
+
+ public static final String DO_FINISH = "finish";
+ public static final String DO_LOCAL_SCREEN = "local-screen";
+ public static final String DO_LOCAL_DIALOG = "local-dialog";
+
+ private boolean mBadParcelable = false;
+
+ private boolean mStarted = false;
+ private long mStartTime;
+
+ private int mResultCode = RESULT_CANCELED;
+ private Intent mData = (new Intent()).setAction("No result received");
+ private RuntimeException mResultStack = null;
+
+ private String[] mExpectedLifecycle = null;
+ private int mNextLifecycle;
+
+ private String[] mExpectedReceivers = null;
+ private int mNextReceiver;
+
+ private String[] mExpectedData = null;
+ private boolean[] mReceivedData = null;
+
+ boolean mReceiverRegistered = false;
+
+ private static CallingTest sCallingTest = null;
+
+ public static void setCallingTest(CallingTest ct) {
+ sCallingTest = ct;
+ }
+
+ public LaunchpadActivity() {
+ mStartTime = System.currentTimeMillis();
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ String action = getIntent().getAction();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "CREATE lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ if (LIFECYCLE_BASIC.equals(action)) {
+ setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
+ DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
+ } else if (LIFECYCLE_SCREEN.equals(action)) {
+ setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
+ DO_LOCAL_SCREEN, ON_FREEZE, ON_PAUSE, ON_STOP,
+ ON_RESTART, ON_START, ON_RESUME,
+ DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
+ } else if (LIFECYCLE_DIALOG.equals(action)) {
+ setExpectedLifecycle(new String[]{ON_START, ON_RESUME,
+ DO_LOCAL_DIALOG, ON_FREEZE, ON_PAUSE, ON_RESUME,
+ DO_FINISH, ON_PAUSE, ON_STOP, ON_DESTROY});
+ } else if (LIFECYCLE_FINISH_CREATE.equals(action)) {
+ // This one behaves a little differently when running in a group.
+ if (getParent() == null) {
+ setExpectedLifecycle(new String[]{ON_DESTROY});
+ } else {
+ setExpectedLifecycle(new String[]{ON_START, ON_STOP, ON_DESTROY});
+ }
+ finish();
+ } else if (LIFECYCLE_FINISH_START.equals(action)) {
+ setExpectedLifecycle(new String[]{ON_START, DO_FINISH,
+ ON_STOP, ON_DESTROY});
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "START lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ checkLifecycle(ON_START);
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onStart();
+ checkLifecycle(ON_RESTART);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "RESUME lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ checkLifecycle(ON_RESUME);
+
+ if (!mStarted) {
+ mStarted = true;
+
+ mHandler.postDelayed(mTimeout, 5 * 1000);
+
+ String action = getIntent().getAction();
+
+ sCallingTest.startTiming(true);
+
+ if (LAUNCH.equals(action)) {
+ Intent intent = getIntent();
+ intent.setFlags(0);
+ intent.setComponent((ComponentName)
+ intent.getParcelableExtra("component"));
+ //System.out.println("*** Launchpad is starting: comp=" + intent.component);
+ startActivityForResult(intent, LAUNCHED_RESULT);
+ } else if (FORWARD_RESULT.equals(action)) {
+ Intent intent = getIntent();
+ intent.setFlags(0);
+ intent.setClass(this, LocalScreen.class);
+ startActivityForResult(intent, FORWARDED_RESULT);
+ } else if (BAD_PARCELABLE.equals(action)) {
+ mBadParcelable = true;
+ Intent intent = getIntent();
+ intent.setFlags(0);
+ intent.setClass(this, LocalScreen.class);
+ startActivityForResult(intent, LAUNCHED_RESULT);
+ } else if (BROADCAST_REGISTERED.equals(action)) {
+ setExpectedReceivers(new String[]{RECEIVER_REG});
+ registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED));
+ sCallingTest.addIntermediate("after-register");
+ sendBroadcast(makeBroadcastIntent(BROADCAST_REGISTERED));
+ } else if (BROADCAST_LOCAL.equals(action)) {
+ setExpectedReceivers(new String[]{RECEIVER_LOCAL});
+ sendBroadcast(makeBroadcastIntent(BROADCAST_LOCAL));
+ } else if (BROADCAST_REMOTE.equals(action)) {
+ setExpectedReceivers(new String[]{RECEIVER_REMOTE});
+ sendBroadcast(makeBroadcastIntent(BROADCAST_REMOTE));
+ } else if (BROADCAST_ALL.equals(action)) {
+ setExpectedReceivers(new String[]{
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL});
+ registerMyReceiver(new IntentFilter(BROADCAST_ALL));
+ sCallingTest.addIntermediate("after-register");
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ } else if (BROADCAST_MULTI.equals(action)) {
+ setExpectedReceivers(new String[]{
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_LOCAL, RECEIVER_REMOTE,
+ RECEIVER_LOCAL, RECEIVER_REMOTE,
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_REG, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_LOCAL,
+ RECEIVER_REMOTE, RECEIVER_LOCAL});
+ registerMyReceiver(new IntentFilter(BROADCAST_ALL));
+ sCallingTest.addIntermediate("after-register");
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_LOCAL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REMOTE), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ALL), null);
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_REPEAT), null);
+ } else if (BROADCAST_ABORT.equals(action)) {
+ setExpectedReceivers(new String[]{
+ RECEIVER_REMOTE, RECEIVER_ABORT});
+ registerMyReceiver(new IntentFilter(BROADCAST_ABORT));
+ sCallingTest.addIntermediate("after-register");
+ sendOrderedBroadcast(makeBroadcastIntent(BROADCAST_ABORT), null);
+ } else if (BROADCAST_STICKY1.equals(action)) {
+ setExpectedReceivers(new String[]{RECEIVER_REG});
+ setExpectedData(new String[]{DATA_1});
+ registerMyReceiver(new IntentFilter(BROADCAST_STICKY1));
+ sCallingTest.addIntermediate("after-register");
+ } else if (BROADCAST_STICKY2.equals(action)) {
+ setExpectedReceivers(new String[]{RECEIVER_REG, RECEIVER_REG});
+ setExpectedData(new String[]{DATA_1, DATA_2});
+ IntentFilter filter = new IntentFilter(BROADCAST_STICKY1);
+ filter.addAction(BROADCAST_STICKY2);
+ registerMyReceiver(filter);
+ sCallingTest.addIntermediate("after-register");
+ }
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle icicle) {
+ super.onSaveInstanceState(icicle);
+ checkLifecycle(ON_FREEZE);
+ if (mBadParcelable) {
+ icicle.putParcelable("baddy", new MyBadParcelable());
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "PAUSE lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ checkLifecycle(ON_PAUSE);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "STOP lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ checkLifecycle(ON_STOP);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ switch (requestCode) {
+ case LAUNCHED_RESULT:
+ sCallingTest.finishTiming(true);
+ finishWithResult(resultCode, data);
+ break;
+ case FORWARDED_RESULT:
+ sCallingTest.finishTiming(true);
+ if (RETURNED_RESULT.equals(data.getAction())) {
+ finishWithResult(resultCode, data);
+ } else {
+ finishWithResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Bad data returned: " + data));
+ }
+ break;
+ default:
+ sCallingTest.finishTiming(true);
+ finishWithResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Unexpected request code: " + requestCode));
+ break;
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "DESTROY lauchpad "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ checkLifecycle(ON_DESTROY);
+ sCallingTest.activityFinished(mResultCode, mData, mResultStack);
+ }
+
+ private void setExpectedLifecycle(String[] lifecycle) {
+ mExpectedLifecycle = lifecycle;
+ mNextLifecycle = 0;
+ }
+
+ private void checkLifecycle(String where) {
+ if (mExpectedLifecycle == null) return;
+
+ if (mNextLifecycle >= mExpectedLifecycle.length) {
+ finishBad("Activity lifecycle incorrect: received " + where
+ + " but don't expect any more calls");
+ mExpectedLifecycle = null;
+ return;
+ }
+ if (!mExpectedLifecycle[mNextLifecycle].equals(where)) {
+ finishBad("Activity lifecycle incorrect: received " + where
+ + " but expected " + mExpectedLifecycle[mNextLifecycle]
+ + " at " + mNextLifecycle);
+ mExpectedLifecycle = null;
+ return;
+ }
+
+ mNextLifecycle++;
+
+ if (mNextLifecycle >= mExpectedLifecycle.length) {
+ setTestResult(RESULT_OK, null);
+ return;
+ }
+
+ String next = mExpectedLifecycle[mNextLifecycle];
+ if (where.equals(ON_DESTROY)) {
+ finishBad("Activity lifecycle incorrect: received " + where
+ + " but expected more actions (next is " + next + ")");
+ mExpectedLifecycle = null;
+ return;
+ } else if (next.equals(DO_FINISH)) {
+ mNextLifecycle++;
+ if (mNextLifecycle >= mExpectedLifecycle.length) {
+ setTestResult(RESULT_OK, null);
+ }
+ if (!isFinishing()) {
+ finish();
+ }
+ } else if (next.equals(DO_LOCAL_SCREEN)) {
+ mNextLifecycle++;
+ Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH);
+ intent.setClass(this, LocalScreen.class);
+ startActivity(intent);
+ } else if (next.equals(DO_LOCAL_DIALOG)) {
+ mNextLifecycle++;
+ Intent intent = new Intent(TestedScreen.WAIT_BEFORE_FINISH);
+ intent.setClass(this, LocalDialog.class);
+ startActivity(intent);
+ }
+ }
+
+ private void setExpectedReceivers(String[] receivers) {
+ mExpectedReceivers = receivers;
+ mNextReceiver = 0;
+ }
+
+ private void setExpectedData(String[] data) {
+ mExpectedData = data;
+ mReceivedData = new boolean[data.length];
+ }
+
+ private Intent makeBroadcastIntent(String action) {
+ Intent intent = new Intent(action, null);
+ intent.putExtra("caller", mCallTarget);
+ return intent;
+ }
+
+ private void finishGood() {
+ finishWithResult(RESULT_OK, null);
+ }
+
+ private void finishBad(String error) {
+ finishWithResult(RESULT_CANCELED, (new Intent()).setAction(error));
+ }
+
+ private void finishWithResult(int resultCode, Intent data) {
+ setTestResult(resultCode, data);
+ finish();
+ }
+
+ private void setTestResult(int resultCode, Intent data) {
+ mHandler.removeCallbacks(mTimeout);
+ unregisterMyReceiver();
+ mResultCode = resultCode;
+ mData = data;
+ mResultStack = new RuntimeException("Original error was here");
+ mResultStack.fillInStackTrace();
+ }
+
+ private void registerMyReceiver(IntentFilter filter) {
+ mReceiverRegistered = true;
+ //System.out.println("Registering: " + mReceiver);
+ registerReceiver(mReceiver, filter);
+ }
+
+ private void unregisterMyReceiver() {
+ if (mReceiverRegistered) {
+ mReceiverRegistered = false;
+ //System.out.println("Unregistering: " + mReceiver);
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ }
+ };
+
+ static final int GOT_RECEIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
+ static final int ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+ private Binder mCallTarget = new Binder() {
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
+ data.setDataPosition(0);
+ data.enforceInterface(LaunchpadActivity.LAUNCH);
+ if (code == GOT_RECEIVE_TRANSACTION) {
+ String name = data.readString();
+ gotReceive(name, null);
+ return true;
+ } else if (code == ERROR_TRANSACTION) {
+ finishBad(data.readString());
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private final void gotReceive(String name, Intent intent) {
+ synchronized (this) {
+
+ //System.out.println("Got receive: " + name);
+ //System.out.println(mNextReceiver + " in " + mExpectedReceivers);
+ //new RuntimeException("stack").printStackTrace();
+
+ sCallingTest.addIntermediate(mNextReceiver + "-" + name);
+
+ if (mExpectedData != null) {
+ int n = mExpectedData.length;
+ int i;
+ boolean prev = false;
+ for (i = 0; i < n; i++) {
+ if (mExpectedData[i].equals(intent.getStringExtra("test"))) {
+ if (mReceivedData[i]) {
+ prev = true;
+ continue;
+ }
+ mReceivedData[i] = true;
+ break;
+ }
+ }
+ if (i >= n) {
+ if (prev) {
+ finishBad("Receive got data too many times: "
+ + intent.getStringExtra("test"));
+ } else {
+ finishBad("Receive got unexpected data: "
+ + intent.getStringExtra("test"));
+ }
+ return;
+ }
+ }
+
+ if (mNextReceiver >= mExpectedReceivers.length) {
+ finishBad("Got too many onReceiveIntent() calls!");
+// System.out.println("Too many intents received: now at "
+// + mNextReceiver + ", expect list: "
+// + Arrays.toString(mExpectedReceivers));
+ } else if (!mExpectedReceivers[mNextReceiver].equals(name)) {
+ finishBad("Receive out of order: got " + name + " but expected "
+ + mExpectedReceivers[mNextReceiver] + " at "
+ + mNextReceiver);
+ } else {
+ mNextReceiver++;
+ if (mNextReceiver == mExpectedReceivers.length) {
+ mHandler.post(mUnregister);
+ }
+ }
+
+ }
+ }
+
+ private Runnable mUnregister = new Runnable() {
+ public void run() {
+ if (mReceiverRegistered) {
+ sCallingTest.addIntermediate("before-unregister");
+ unregisterMyReceiver();
+ }
+ sCallingTest.finishTiming(true);
+ finishGood();
+ }
+ };
+
+ private Runnable mTimeout = new Runnable() {
+ public void run() {
+ Log.i("foo", "**** TIMEOUT");
+ String msg = "Timeout";
+ if (mExpectedReceivers != null
+ && mNextReceiver < mExpectedReceivers.length) {
+ msg = msg + " waiting for " + mExpectedReceivers[mNextReceiver];
+ }
+ finishBad(msg);
+ }
+ };
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ //System.out.println("Receive in: " + this + ": " + intent);
+ gotReceive(RECEIVER_REG, intent);
+ }
+ };
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java
new file mode 100644
index 0000000..79b860f
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LaunchpadTabActivity.java
@@ -0,0 +1,43 @@
+/*
+ * 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.app.activity;
+
+import android.app.TabActivity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.TabHost;
+
+public class LaunchpadTabActivity extends TabActivity {
+ public LaunchpadTabActivity() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ Intent tabIntent = new Intent(getIntent());
+ tabIntent.setComponent((ComponentName)tabIntent.getParcelableExtra("tab"));
+
+ TabHost th = getTabHost();
+ TabHost.TabSpec ts = th.newTabSpec("1");
+ ts.setIndicator("One");
+ ts.setContent(tabIntent);
+ th.addTab(ts);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LifecycleTest.java b/core/tests/coretests/src/android/app/activity/LifecycleTest.java
new file mode 100644
index 0000000..768a9a4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LifecycleTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.app.activity;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+public class LifecycleTest extends ActivityTestsBase {
+ private Intent mTopIntent;
+ private Intent mTabIntent;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTopIntent = mIntent;
+ mTabIntent = new Intent(mContext, LaunchpadTabActivity.class);
+ mTabIntent.putExtra("tab", new ComponentName(mContext,
+ LaunchpadActivity.class));
+ }
+
+ @LargeTest
+ public void testBasic() throws Exception {
+ mIntent = mTopIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_BASIC);
+ }
+
+ //Suppressing until 1285425 is fixed.
+ @Suppress
+ public void testTabBasic() throws Exception {
+ mIntent = mTabIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_BASIC);
+ }
+
+ //Marking flaky until bug 1164344 is fixed.
+ // @FlakyTest(tolerance=2)
+ // @LargeTest
+ public void testScreen() throws Exception {
+ mIntent = mTopIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_SCREEN);
+ }
+
+ //Marking flaky until bug 1164344 is fixed.
+ //@FlakyTest(tolerance=2)
+ //Suppressing until 1285425 is fixed.
+ @Suppress
+ public void testTabScreen() throws Exception {
+ mIntent = mTabIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_SCREEN);
+ }
+
+ //flaky test, removing from large suite until 1866891 is fixed
+ //@LargeTest
+ public void testDialog() throws Exception {
+ mIntent = mTopIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_DIALOG);
+ }
+
+ //Suppressing until 1285425 is fixed.
+ @Suppress
+ public void testTabDialog() throws Exception {
+ mIntent = mTabIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_DIALOG);
+ }
+
+ @MediumTest
+ public void testFinishCreate() throws Exception {
+ mIntent = mTopIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_CREATE);
+ }
+
+ //Suppressing until 1285425 is fixed.
+ @Suppress
+ public void testTabFinishCreate() throws Exception {
+ mIntent = mTabIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_CREATE);
+ }
+
+ @MediumTest
+ public void testFinishStart() throws Exception {
+ mIntent = mTopIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_START);
+ }
+
+ //Suppressing until 1285425 is fixed.
+ @Suppress
+ public void testTabFinishStart() throws Exception {
+ mIntent = mTabIntent;
+ runLaunchpad(LaunchpadActivity.LIFECYCLE_FINISH_START);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/LocalActivity.java b/core/tests/coretests/src/android/app/activity/LocalActivity.java
new file mode 100644
index 0000000..01f1fb6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.app.activity;
+
+import java.util.Map;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+
+public class LocalActivity extends TestedActivity
+{
+ public LocalActivity()
+ {
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java b/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java
new file mode 100644
index 0000000..2120a1d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalDeniedReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.app.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+class LocalDeniedReceiver extends BroadcastReceiver {
+ public LocalDeniedReceiver() {
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(BroadcastTest.RECEIVER_LOCAL);
+ caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/java/android/pim/DateException.java b/core/tests/coretests/src/android/app/activity/LocalDeniedService.java
index 90bfe7f..3bdac22 100644
--- a/core/java/android/pim/DateException.java
+++ b/core/tests/coretests/src/android/app/activity/LocalDeniedService.java
@@ -14,13 +14,9 @@
* limitations under the License.
*/
-package android.pim;
+package android.app.activity;
-public class DateException extends Exception
+public class LocalDeniedService extends LocalService
{
- public DateException(String message)
- {
- super(message);
- }
}
diff --git a/core/java/android/os/Base64Utils.java b/core/tests/coretests/src/android/app/activity/LocalDialog.java
index 684a469..c92fa43 100644
--- a/core/java/android/os/Base64Utils.java
+++ b/core/tests/coretests/src/android/app/activity/LocalDialog.java
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package android.os;
+package android.app.activity;
-/**
- * {@hide}
- */
-public class Base64Utils
+import java.util.Map;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+
+public class LocalDialog extends TestedScreen
{
- // TODO add encode api here if possible
-
- public static byte [] decodeBase64(String data) {
- return decodeBase64Native(data);
+ public LocalDialog()
+ {
}
- private static native byte[] decodeBase64Native(String data);
}
diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java b/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java
new file mode 100644
index 0000000..c9e6ab4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalGrantedReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.app.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+public class LocalGrantedReceiver extends BroadcastReceiver {
+ public LocalGrantedReceiver() {
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(BroadcastTest.RECEIVER_LOCAL);
+ caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LocalGrantedService.java b/core/tests/coretests/src/android/app/activity/LocalGrantedService.java
new file mode 100644
index 0000000..7ab0fb4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalGrantedService.java
@@ -0,0 +1,22 @@
+/*
+ * 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.app.activity;
+
+public class LocalGrantedService extends LocalService
+{
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LocalProvider.java b/core/tests/coretests/src/android/app/activity/LocalProvider.java
new file mode 100644
index 0000000..085e622
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalProvider.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.activity;
+
+import android.content.UriMatcher;
+import android.content.*;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.util.Config;
+import android.util.Log;
+
+/** Simple test provider that runs in the local process. */
+public class LocalProvider extends ContentProvider {
+ private static final String TAG = "LocalProvider";
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private static final int DATA = 1;
+ private static final int DATA_ID = 2;
+ private static final UriMatcher sURLMatcher = new UriMatcher(
+ UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("*", "data", DATA);
+ sURLMatcher.addURI("*", "data/#", DATA_ID);
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "local.db";
+ private static final int DATABASE_VERSION = 1;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE data (" +
+ "_id INTEGER PRIMARY KEY," +
+ "text TEXT, " +
+ "integer INTEGER);");
+
+ // insert alarms
+ db.execSQL("INSERT INTO data (text, integer) VALUES ('first data', 100);");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ Log.w(TAG, "Upgrading test database from version " +
+ oldVersion + " to " + currentVersion +
+ ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS data");
+ onCreate(db);
+ }
+ }
+
+
+ public LocalProvider() {
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ // Generate the body of the query
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA:
+ qb.setTables("data");
+ break;
+ case DATA_ID:
+ qb.setTables("data");
+ qb.appendWhere("_id=");
+ qb.appendWhere(url.getPathSegments().get(1));
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
+ null, null, sort);
+
+ if (ret == null) {
+ if (Config.LOGD) Log.d(TAG, "Alarms.query: failed");
+ } else {
+ ret.setNotificationUri(getContext().getContentResolver(), url);
+ }
+
+ return ret;
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA:
+ return "vnd.android.cursor.dir/vnd.google.unit_tests.local";
+ case DATA_ID:
+ return "vnd.android.cursor.item/vnd.google.unit_tests.local";
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ int count;
+ long rowId = 0;
+ int match = sURLMatcher.match(url);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case DATA_ID: {
+ String segment = url.getPathSegments().get(1);
+ rowId = Long.parseLong(segment);
+ count = db.update("data", values, "_id=" + rowId, null);
+ break;
+ }
+ default: {
+ throw new UnsupportedOperationException(
+ "Cannot update URL: " + url);
+ }
+ }
+ if (Config.LOGD) Log.d(TAG, "*** notifyChange() rowId: " + rowId);
+ getContext().getContentResolver().notifyChange(url, null);
+ return count;
+ }
+
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("delete not supported");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/LocalReceiver.java b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
new file mode 100644
index 0000000..bfd543f
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalReceiver.java
@@ -0,0 +1,77 @@
+/*
+ * 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.app.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ReceiverCallNotAllowedException;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+public class LocalReceiver extends BroadcastReceiver {
+ public LocalReceiver() {
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ String resultString = LaunchpadActivity.RECEIVER_LOCAL;
+ if (BroadcastTest.BROADCAST_FAIL_REGISTER.equals(intent.getAction())) {
+ resultString = "Successfully registered, but expected it to fail";
+ try {
+ context.registerReceiver(this, new IntentFilter("foo.bar"));
+ context.unregisterReceiver(this);
+ } catch (ReceiverCallNotAllowedException e) {
+ //resultString = "This is the correct behavior but not yet implemented";
+ resultString = LaunchpadActivity.RECEIVER_LOCAL;
+ }
+ } else if (BroadcastTest.BROADCAST_FAIL_BIND.equals(intent.getAction())) {
+ resultString = "Successfully bound to service, but expected it to fail";
+ try {
+ ServiceConnection sc = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+ context.bindService(new Intent(context, LocalService.class), sc, 0);
+ context.unbindService(sc);
+ } catch (ReceiverCallNotAllowedException e) {
+ //resultString = "This is the correct behavior but not yet implemented";
+ resultString = LaunchpadActivity.RECEIVER_LOCAL;
+ }
+ } else if (LaunchpadActivity.BROADCAST_REPEAT.equals(intent.getAction())) {
+ Intent newIntent = new Intent(intent);
+ newIntent.setAction(LaunchpadActivity.BROADCAST_LOCAL);
+ context.sendOrderedBroadcast(newIntent, null);
+ }
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(resultString);
+ caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LocalScreen.java b/core/tests/coretests/src/android/app/activity/LocalScreen.java
new file mode 100644
index 0000000..f7c8c33
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalScreen.java
@@ -0,0 +1,33 @@
+/*
+ * 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.app.activity;
+
+import java.util.Map;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+
+public class LocalScreen extends TestedScreen
+{
+ public LocalScreen()
+ {
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/LocalService.java b/core/tests/coretests/src/android/app/activity/LocalService.java
new file mode 100644
index 0000000..c31ca4b
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/LocalService.java
@@ -0,0 +1,122 @@
+/*
+ * 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.app.activity;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Log;
+
+public class LocalService extends Service {
+ private final IBinder mBinder = new Binder() {
+
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException {
+ if (code == ServiceTest.SET_REPORTER_CODE) {
+ data.enforceInterface(ServiceTest.SERVICE_LOCAL);
+ mReportObject = data.readStrongBinder();
+ return true;
+ } else {
+ return super.onTransact(code, data, reply, flags);
+ }
+ }
+
+ };
+
+ private IBinder mReportObject;
+ private int mStartCount = 1;
+
+ public LocalService() {
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ //Log.i("LocalService", "onStart: " + intent);
+ if (intent.getExtras() != null) {
+ mReportObject = intent.getExtras().getIBinder(ServiceTest.REPORT_OBJ_NAME);
+ if (mReportObject != null) {
+ try {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
+ data.writeInt(mStartCount);
+ mStartCount++;
+ mReportObject.transact(
+ ServiceTest.STARTED_CODE, data, null, 0);
+ data.recycle();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.i("LocalService", "onDestroy: mReportObject=" + mReportObject);
+ if (mReportObject != null) {
+ try {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
+ mReportObject.transact(
+ ServiceTest.DESTROYED_CODE, data, null, 0);
+ data.recycle();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.i("LocalService", "onBind: " + intent);
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.i("LocalService", "onUnbind: " + intent);
+ if (mReportObject != null) {
+ try {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
+ mReportObject.transact(
+ ServiceTest.UNBIND_CODE, data, null, 0);
+ data.recycle();
+ } catch (RemoteException e) {
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ Log.i("LocalService", "onUnbind: " + intent);
+ if (mReportObject != null) {
+ try {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(ServiceTest.SERVICE_LOCAL);
+ mReportObject.transact(
+ ServiceTest.REBIND_CODE, data, null, 0);
+ data.recycle();
+ } catch (RemoteException e) {
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/MetaDataTest.java b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
new file mode 100644
index 0000000..214bc91
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/MetaDataTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.app.activity;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.frameworks.coretests.R;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Tests for meta-data associated with application components.
+ */
+public class MetaDataTest extends AndroidTestCase {
+
+ private void checkMetaData(ComponentName cn, PackageItemInfo ci)
+ throws IOException, XmlPullParserException {
+ assertNotNull("Unable to find component " + cn, ci);
+
+ Bundle md = ci.metaData;
+ assertNotNull("No meta data found", md);
+
+ assertEquals("foo", md.getString("com.android.frameworks.coretests.string"));
+ assertTrue(md.getBoolean("com.android.frameworks.coretests.boolean"));
+ assertEquals(100, md.getInt("com.android.frameworks.coretests.integer"));
+ assertEquals(0xff000000, md.getInt("com.android.frameworks.coretests.color"));
+
+ assertEquals((double) 1001,
+ Math.floor(md.getFloat("com.android.frameworks.coretests.float") * 10 + .5));
+
+ assertEquals(R.xml.metadata, md.getInt("com.android.frameworks.coretests.reference"));
+
+ XmlResourceParser xml = ci.loadXmlMetaData(mContext.getPackageManager(),
+ "com.android.frameworks.coretests.reference");
+ assertNotNull(xml);
+
+ int type;
+ while ((type = xml.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ assertEquals(XmlPullParser.START_TAG, type);
+ assertEquals("thedata", xml.getName());
+
+ // method 1: direct access
+ final String rawAttr = xml.getAttributeValue(null, "rawText");
+ assertEquals("some raw text", rawAttr);
+
+ // method 2: direct access of typed value
+ final int rawColorIntAttr = xml.getAttributeIntValue(null, "rawColor", 0);
+ assertEquals(0xffffff00, rawColorIntAttr);
+ final String rawColorStrAttr = xml.getAttributeValue(null, "rawColor");
+ assertEquals("#ffffff00", rawColorStrAttr);
+
+ // method 2: direct access of resource attribute
+ final String nameSpace = "http://schemas.android.com/apk/res/android";
+ final int colorIntAttr = xml.getAttributeIntValue(nameSpace, "color", 0);
+ assertEquals(0xffff0000, colorIntAttr);
+ final String colorStrAttr = xml.getAttributeValue(nameSpace, "color");
+ assertEquals("#ffff0000", colorStrAttr);
+
+ // method 3: styled access (borrowing an attr from view system here)
+ TypedArray a = mContext.obtainStyledAttributes(xml,
+ android.R.styleable.TextView);
+ String styledAttr = a.getString(android.R.styleable.TextView_text);
+ assertEquals("text", styledAttr);
+ a.recycle();
+
+ xml.close();
+ }
+
+ @SmallTest
+ public void testActivityWithData() throws Exception {
+ ComponentName cn = new ComponentName(mContext, LocalActivity.class);
+ ActivityInfo ai = mContext.getPackageManager().getActivityInfo(
+ cn, PackageManager.GET_META_DATA);
+
+ checkMetaData(cn, ai);
+
+ ai = mContext.getPackageManager().getActivityInfo(cn, 0);
+
+ assertNull("Meta data returned when not requested", ai.metaData);
+ }
+
+ @SmallTest
+ public void testReceiverWithData() throws Exception {
+ ComponentName cn = new ComponentName(mContext, LocalReceiver.class);
+ ActivityInfo ai = mContext.getPackageManager().getReceiverInfo(
+ cn, PackageManager.GET_META_DATA);
+
+ checkMetaData(cn, ai);
+
+ ai = mContext.getPackageManager().getReceiverInfo(cn, 0);
+
+ assertNull("Meta data returned when not requested", ai.metaData);
+ }
+
+ @SmallTest
+ public void testServiceWithData() throws Exception {
+ ComponentName cn = new ComponentName(mContext, LocalService.class);
+ ServiceInfo si = mContext.getPackageManager().getServiceInfo(
+ cn, PackageManager.GET_META_DATA);
+
+ checkMetaData(cn, si);
+
+ si = mContext.getPackageManager().getServiceInfo(cn, 0);
+
+ assertNull("Meta data returned when not requested", si.metaData);
+ }
+
+ @MediumTest
+ public void testProviderWithData() throws Exception {
+ ComponentName cn = new ComponentName(mContext, LocalProvider.class);
+ ProviderInfo pi = mContext.getPackageManager().resolveContentProvider(
+ "com.android.frameworks.coretests.LocalProvider",
+ PackageManager.GET_META_DATA);
+ checkMetaData(cn, pi);
+
+ pi = mContext.getPackageManager().resolveContentProvider(
+ "com.android.frameworks.coretests.LocalProvider", 0);
+
+ assertNull("Meta data returned when not requested", pi.metaData);
+ }
+
+ @SmallTest
+ public void testPermissionWithData() throws Exception {
+ ComponentName cn = new ComponentName("foo",
+ "com.android.frameworks.coretests.permission.TEST_GRANTED");
+ PermissionInfo pi = mContext.getPackageManager().getPermissionInfo(
+ cn.getClassName(), PackageManager.GET_META_DATA);
+ checkMetaData(cn, pi);
+
+ pi = mContext.getPackageManager().getPermissionInfo(
+ cn.getClassName(), 0);
+
+ assertNull("Meta data returned when not requested", pi.metaData);
+ }
+}
+
+
diff --git a/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java
new file mode 100644
index 0000000..7c89346
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RemoteDeniedReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.app.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+class RemoteDeniedReceiver extends BroadcastReceiver {
+ public RemoteDeniedReceiver() {
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(BroadcastTest.RECEIVER_REMOTE);
+ caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java
new file mode 100644
index 0000000..0eca8f7
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RemoteGrantedReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.app.activity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+public class RemoteGrantedReceiver extends BroadcastReceiver {
+ public RemoteGrantedReceiver() {
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(BroadcastTest.RECEIVER_REMOTE);
+ caller.transact(BroadcastTest.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/RemoteReceiver.java b/core/tests/coretests/src/android/app/activity/RemoteReceiver.java
new file mode 100644
index 0000000..9608fc4
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RemoteReceiver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.app.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+
+public class RemoteReceiver extends BroadcastReceiver
+{
+ public RemoteReceiver()
+ {
+ }
+
+ public void onReceive(Context context, Intent intent)
+ {
+ if (LaunchpadActivity.BROADCAST_REPEAT.equals(intent.getAction())) {
+ Intent newIntent = new Intent(intent);
+ newIntent.setAction(LaunchpadActivity.BROADCAST_REMOTE);
+ context.sendOrderedBroadcast(newIntent, null);
+ }
+ try {
+ IBinder caller = intent.getIBinderExtra("caller");
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(LaunchpadActivity.LAUNCH);
+ data.writeString(LaunchpadActivity.RECEIVER_REMOTE);
+ caller.transact(LaunchpadActivity.GOT_RECEIVE_TRANSACTION, data, null, 0);
+ data.recycle();
+ } catch (RemoteException ex) {
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java b/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java
new file mode 100644
index 0000000..e969d10
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/RemoteSubActivityScreen.java
@@ -0,0 +1,59 @@
+/* //device/apps/AndroidTests/src/com.android.unit_tests/activity/TestedScreen.java
+**
+** Copyright 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.app.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+
+public class RemoteSubActivityScreen extends SubActivityScreen {
+ Handler mHandler = new Handler();
+ boolean mFirst = false;
+
+ public RemoteSubActivityScreen() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ // We are running in a remote process, so want to have the sub-activity
+ // sending the result back in the original process.
+ Intent intent = getIntent();
+ intent.setClass(this, SubActivityScreen.class);
+
+ super.onCreate(icicle);
+
+ boolean kill = intent.getBooleanExtra("kill", false);
+ //Log.i("foo", "RemoteSubActivityScreen pid=" + Process.myPid()
+ // + " kill=" + kill);
+
+ if (kill) {
+ // After finishing initialization, kill the process! But only if
+ // this is the first time...
+ if (icicle == null) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ handleBeforeStopping();
+ Process.killProcess(Process.myPid());
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/ResultReceiver.java b/core/tests/coretests/src/android/app/activity/ResultReceiver.java
new file mode 100644
index 0000000..f7daf2c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ResultReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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.app.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Bundle;
+
+import java.util.Map;
+
+public class ResultReceiver extends BroadcastReceiver
+{
+ public ResultReceiver()
+ {
+ }
+
+ public void onReceive(Context context, Intent intent)
+ {
+ setResultCode(3);
+ setResultData("bar");
+ Bundle map = getResultExtras(false);
+ map.remove("remove");
+ map.putString("bar", "them");
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/SearchableActivity.java b/core/tests/coretests/src/android/app/activity/SearchableActivity.java
new file mode 100644
index 0000000..e238572
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/SearchableActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.activity;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class SearchableActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ finish();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/app/activity/ServiceTest.java b/core/tests/coretests/src/android/app/activity/ServiceTest.java
new file mode 100644
index 0000000..d3ae415
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/ServiceTest.java
@@ -0,0 +1,469 @@
+/*
+ * 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.app.activity;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+// These test binders purport to support an interface whose canonical
+// interface name is ServiceTest.SERVICE_LOCAL
+// Temporarily suppress, this test is causing unit test suite run to fail
+// TODO: remove this suppress
+@Suppress
+public class ServiceTest extends ActivityTestsBase {
+
+ public static final String SERVICE_LOCAL =
+ "com.android.frameworks.coretests.activity.SERVICE_LOCAL";
+ public static final String SERVICE_LOCAL_GRANTED =
+ "com.android.frameworks.coretests.activity.SERVICE_LOCAL_GRANTED";
+ public static final String SERVICE_LOCAL_DENIED =
+ "com.android.frameworks.coretests.activity.SERVICE_LOCAL_DENIED";
+
+ public static final String REPORT_OBJ_NAME = "report";
+
+ public static final int STARTED_CODE = 1;
+ public static final int DESTROYED_CODE = 2;
+ public static final int SET_REPORTER_CODE = 3;
+ public static final int UNBIND_CODE = 4;
+ public static final int REBIND_CODE = 5;
+
+ public static final int STATE_START_1 = 0;
+ public static final int STATE_START_2 = 1;
+ public static final int STATE_UNBIND = 2;
+ public static final int STATE_DESTROY = 3;
+ public static final int STATE_REBIND = 4;
+ public static final int STATE_UNBIND_ONLY = 5;
+ public int mStartState;
+
+ public IBinder mStartReceiver = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException {
+ //Log.i("ServiceTest", "Received code " + code + " in state " + mStartState);
+ if (code == STARTED_CODE) {
+ data.enforceInterface(SERVICE_LOCAL);
+ int count = data.readInt();
+ if (mStartState == STATE_START_1) {
+ if (count == 1) {
+ finishGood();
+ } else {
+ finishBad("onStart() again on an object when it should have been the first time");
+ }
+ } else if (mStartState == STATE_START_2) {
+ if (count == 2) {
+ finishGood();
+ } else {
+ finishBad("onStart() the first time on an object when it should have been the second time");
+ }
+ } else {
+ finishBad("onStart() was called when not expected (state="+mStartState+")");
+ }
+ return true;
+ } else if (code == DESTROYED_CODE) {
+ data.enforceInterface(SERVICE_LOCAL);
+ if (mStartState == STATE_DESTROY) {
+ finishGood();
+ } else {
+ finishBad("onDestroy() was called when not expected (state="+mStartState+")");
+ }
+ return true;
+ } else if (code == UNBIND_CODE) {
+ data.enforceInterface(SERVICE_LOCAL);
+ if (mStartState == STATE_UNBIND) {
+ mStartState = STATE_DESTROY;
+ } else if (mStartState == STATE_UNBIND_ONLY) {
+ finishGood();
+ } else {
+ finishBad("onUnbind() was called when not expected (state="+mStartState+")");
+ }
+ return true;
+ } else if (code == REBIND_CODE) {
+ data.enforceInterface(SERVICE_LOCAL);
+ if (mStartState == STATE_REBIND) {
+ finishGood();
+ } else {
+ finishBad("onRebind() was called when not expected (state="+mStartState+")");
+ }
+ return true;
+ } else {
+ return super.onTransact(code, data, reply, flags);
+ }
+ }
+ };
+
+ public class EmptyConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ }
+
+ public class TestConnection implements ServiceConnection {
+ private final boolean mExpectDisconnect;
+ private final boolean mSetReporter;
+ private boolean mMonitor;
+ private int mCount;
+
+ public TestConnection(boolean expectDisconnect, boolean setReporter) {
+ mExpectDisconnect = expectDisconnect;
+ mSetReporter = setReporter;
+ mMonitor = !setReporter;
+ }
+
+ void setMonitor(boolean v) {
+ mMonitor = v;
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (mSetReporter) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(SERVICE_LOCAL);
+ data.writeStrongBinder(mStartReceiver);
+ try {
+ service.transact(SET_REPORTER_CODE, data, null, 0);
+ } catch (RemoteException e) {
+ finishBad("DeadObjectException when sending reporting object");
+ }
+ data.recycle();
+ }
+
+ if (mMonitor) {
+ mCount++;
+ if (mStartState == STATE_START_1) {
+ if (mCount == 1) {
+ finishGood();
+ } else {
+ finishBad("onServiceConnected() again on an object when it should have been the first time");
+ }
+ } else if (mStartState == STATE_START_2) {
+ if (mCount == 2) {
+ finishGood();
+ } else {
+ finishBad("onServiceConnected() the first time on an object when it should have been the second time");
+ }
+ } else {
+ finishBad("onServiceConnected() called unexpectedly");
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ if (mMonitor) {
+ if (mStartState == STATE_DESTROY) {
+ if (mExpectDisconnect) {
+ finishGood();
+ } else {
+ finishBad("onServiceDisconnected() when it shouldn't have been");
+ }
+ } else {
+ finishBad("onServiceDisconnected() called unexpectedly");
+ }
+ }
+ }
+ }
+
+ void startExpectResult(Intent service) {
+ startExpectResult(service, new Bundle());
+ }
+
+ void startExpectResult(Intent service, Bundle bundle) {
+ bundle.putIBinder(REPORT_OBJ_NAME, mStartReceiver);
+ boolean success = false;
+ try {
+ //Log.i("foo", "STATE_START_1");
+ mStartState = STATE_START_1;
+ getContext().startService(new Intent(service).putExtras(bundle));
+ waitForResultOrThrow(5 * 1000, "service to start first time");
+ //Log.i("foo", "STATE_START_2");
+ mStartState = STATE_START_2;
+ getContext().startService(new Intent(service).putExtras(bundle));
+ waitForResultOrThrow(5 * 1000, "service to start second time");
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ getContext().stopService(service);
+ } catch (Exception e) {
+ // eat
+ }
+ }
+ }
+ //Log.i("foo", "STATE_DESTROY");
+ mStartState = STATE_DESTROY;
+ getContext().stopService(service);
+ waitForResultOrThrow(5 * 1000, "service to be destroyed");
+ }
+
+ void startExpectNoPermission(Intent service) {
+ try {
+ getContext().startService(service);
+ fail("Expected security exception when starting " + service);
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ void bindExpectResult(Intent service) {
+ TestConnection conn = new TestConnection(true, false);
+ TestConnection conn2 = new TestConnection(false, false);
+ boolean success = false;
+ try {
+ // Expect to see the TestConnection connected.
+ mStartState = STATE_START_1;
+ getContext().bindService(service, conn, 0);
+ getContext().startService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to receive service");
+
+ // Expect to see the second TestConnection connected.
+ getContext().bindService(service, conn2, 0);
+ waitForResultOrThrow(5 * 1000, "new connection to receive service");
+
+ getContext().unbindService(conn2);
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ getContext().stopService(service);
+ getContext().unbindService(conn);
+ getContext().unbindService(conn2);
+ } catch (Exception e) {
+ // eat
+ }
+ }
+ }
+
+ // Expect to see the TestConnection disconnected.
+ mStartState = STATE_DESTROY;
+ getContext().stopService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to lose service");
+
+ getContext().unbindService(conn);
+
+ conn = new TestConnection(true, true);
+ success = false;
+ try {
+ // Expect to see the TestConnection connected.
+ conn.setMonitor(true);
+ mStartState = STATE_START_1;
+ getContext().bindService(service, conn, 0);
+ getContext().startService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to receive service");
+
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ getContext().stopService(service);
+ getContext().unbindService(conn);
+ } catch (Exception e) {
+ // eat
+ }
+ }
+ }
+
+ // Expect to see the service unbind and then destroyed.
+ conn.setMonitor(false);
+ mStartState = STATE_UNBIND;
+ getContext().stopService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to lose service");
+
+ getContext().unbindService(conn);
+
+ conn = new TestConnection(true, true);
+ success = false;
+ try {
+ // Expect to see the TestConnection connected.
+ conn.setMonitor(true);
+ mStartState = STATE_START_1;
+ getContext().bindService(service, conn, 0);
+ getContext().startService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to receive service");
+
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ getContext().stopService(service);
+ getContext().unbindService(conn);
+ } catch (Exception e) {
+ // eat
+ }
+ }
+ }
+
+ // Expect to see the service unbind but not destroyed.
+ conn.setMonitor(false);
+ mStartState = STATE_UNBIND_ONLY;
+ getContext().unbindService(conn);
+ waitForResultOrThrow(5 * 1000, "existing connection to unbind service");
+
+ // Expect to see the service rebound.
+ mStartState = STATE_REBIND;
+ getContext().bindService(service, conn, 0);
+ waitForResultOrThrow(5 * 1000, "existing connection to rebind service");
+
+ // Expect to see the service unbind and then destroyed.
+ mStartState = STATE_UNBIND;
+ getContext().stopService(service);
+ waitForResultOrThrow(5 * 1000, "existing connection to lose service");
+
+ getContext().unbindService(conn);
+ }
+
+ void bindAutoExpectResult(Intent service) {
+ TestConnection conn = new TestConnection(false, true);
+ boolean success = false;
+ try {
+ conn.setMonitor(true);
+ mStartState = STATE_START_1;
+ getContext().bindService(
+ service, conn, Context.BIND_AUTO_CREATE);
+ waitForResultOrThrow(5 * 1000, "connection to start and receive service");
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ getContext().unbindService(conn);
+ } catch (Exception e) {
+ // eat
+ }
+ }
+ }
+ mStartState = STATE_UNBIND;
+ getContext().unbindService(conn);
+ waitForResultOrThrow(5 * 1000, "disconnecting from service");
+ }
+
+ void bindExpectNoPermission(Intent service) {
+ TestConnection conn = new TestConnection(false, false);
+ try {
+ getContext().bindService(service, conn, Context.BIND_AUTO_CREATE);
+ fail("Expected security exception when binding " + service);
+ } catch (SecurityException e) {
+ // expected
+ } finally {
+ getContext().unbindService(conn);
+ }
+ }
+
+
+ @MediumTest
+ public void testLocalStartClass() throws Exception {
+ startExpectResult(new Intent(getContext(), LocalService.class));
+ }
+
+ @MediumTest
+ public void testLocalStartAction() throws Exception {
+ startExpectResult(new Intent(SERVICE_LOCAL));
+ }
+
+ @MediumTest
+ public void testLocalBindClass() throws Exception {
+ bindExpectResult(new Intent(getContext(), LocalService.class));
+ }
+
+ @MediumTest
+ public void testLocalBindAction() throws Exception {
+ bindExpectResult(new Intent(SERVICE_LOCAL));
+ }
+
+ @MediumTest
+ public void testLocalBindAutoClass() throws Exception {
+ bindAutoExpectResult(new Intent(getContext(), LocalService.class));
+ }
+
+ @MediumTest
+ public void testLocalBindAutoAction() throws Exception {
+ bindAutoExpectResult(new Intent(SERVICE_LOCAL));
+ }
+
+ @MediumTest
+ public void testLocalStartClassPermissionGranted() throws Exception {
+ startExpectResult(new Intent(getContext(), LocalGrantedService.class));
+ }
+
+ @MediumTest
+ public void testLocalStartActionPermissionGranted() throws Exception {
+ startExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
+ }
+
+ @MediumTest
+ public void testLocalBindClassPermissionGranted() throws Exception {
+ bindExpectResult(new Intent(getContext(), LocalGrantedService.class));
+ }
+
+ @MediumTest
+ public void testLocalBindActionPermissionGranted() throws Exception {
+ bindExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
+ }
+
+ @MediumTest
+ public void testLocalBindAutoClassPermissionGranted() throws Exception {
+ bindAutoExpectResult(new Intent(getContext(), LocalGrantedService.class));
+ }
+
+ @MediumTest
+ public void testLocalBindAutoActionPermissionGranted() throws Exception {
+ bindAutoExpectResult(new Intent(SERVICE_LOCAL_GRANTED));
+ }
+
+ @MediumTest
+ public void testLocalStartClassPermissionDenied() throws Exception {
+ startExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
+ }
+
+ @MediumTest
+ public void testLocalStartActionPermissionDenied() throws Exception {
+ startExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
+ }
+
+ @MediumTest
+ public void testLocalBindClassPermissionDenied() throws Exception {
+ bindExpectNoPermission(new Intent(getContext(), LocalDeniedService.class));
+ }
+
+ @MediumTest
+ public void testLocalBindActionPermissionDenied() throws Exception {
+ bindExpectNoPermission(new Intent(SERVICE_LOCAL_DENIED));
+ }
+
+ @MediumTest
+ public void testLocalUnbindTwice() throws Exception {
+ EmptyConnection conn = new EmptyConnection();
+ getContext().bindService(
+ new Intent(SERVICE_LOCAL_GRANTED), conn, 0);
+ getContext().unbindService(conn);
+ try {
+ getContext().unbindService(conn);
+ fail("No exception thrown on second unbind");
+ } catch (IllegalArgumentException e) {
+ //Log.i("foo", "Unbind exception", e);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java b/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java
new file mode 100644
index 0000000..41b9547
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/SetTimeZonePermissionsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.activity;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.TimeZone;
+
+public class SetTimeZonePermissionsTest extends AndroidTestCase {
+
+ private String[] mZones;
+ private String mCurrentZone;
+ private AlarmManager mAlarm;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mZones = TimeZone.getAvailableIDs();
+ mCurrentZone = TimeZone.getDefault().getID();
+ mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ }
+
+ /**
+ * Verify that non-system processes cannot set the time zone.
+ */
+ @LargeTest
+ public void testSetTimeZonePermissions() {
+ /**
+ * Attempt to set several predefined time zones, verifying that the system
+ * system default time zone has not actually changed from its prior state
+ * after each attempt.
+ */
+ int max = (mZones.length > 10) ? mZones.length : 10;
+ assertTrue("No system-defined time zones - test invalid", max > 0);
+
+ for (int i = 0; i < max; i++) {
+ String tz = mZones[i];
+ try {
+ mAlarm.setTimeZone(tz);
+ } catch (SecurityException se) {
+ // Expected failure; no need to handle specially since we're
+ // about to assert that the test invariant holds: no change
+ // to the system time zone.
+ }
+
+ String newZone = TimeZone.getDefault().getID();
+ assertEquals("AlarmManager.setTimeZone() succeeded despite lack of permission",
+ mCurrentZone,
+ newZone);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/SubActivityScreen.java b/core/tests/coretests/src/android/app/activity/SubActivityScreen.java
new file mode 100644
index 0000000..919c591
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/SubActivityScreen.java
@@ -0,0 +1,168 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class SubActivityScreen extends Activity {
+ static final int NO_RESULT_MODE = 0;
+ static final int RESULT_MODE = 1;
+ static final int PENDING_RESULT_MODE = 2;
+ static final int FINISH_SUB_MODE = 3;
+
+ static final int CHILD_OFFSET = 1000;
+
+ int mMode;
+
+ public SubActivityScreen() {
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mMode = getIntent().getIntExtra("mode", mMode);
+ //Log.i("foo", "SubActivityScreen pid=" + Process.myPid()
+ // + " mode=" + mMode);
+
+ // Move on to the next thing that will generate a result... but only
+ // if we are being launched for the first time.
+ if (icicle == null) {
+ if (mMode == PENDING_RESULT_MODE) {
+ PendingIntent apr = createPendingResult(1, null,
+ Intent.FILL_IN_ACTION);
+ Intent res = new Intent();
+ res.putExtra("tkey", "tval");
+ res.setAction("test");
+ try {
+ apr.send(this, RESULT_OK, res);
+ } catch (PendingIntent.CanceledException e) {
+ }
+ } else if (mMode < CHILD_OFFSET) {
+ Intent intent = new Intent();
+ intent.setClass(this, SubActivityScreen.class);
+ intent.putExtra("mode", CHILD_OFFSET+mMode);
+ //System.out.println("*** Starting from onStart: " + intent);
+ startActivityForResult(intent, 1);
+ return;
+ }
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ //Log.i("foo", "SubActivityScreen pid=" + Process.myPid() + " onResume");
+
+ if (mMode >= CHILD_OFFSET) {
+ // Wait a little bit, to give our parent time to kill itself
+ // if that is something it is into.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction("Interrupted!"));
+ finish();
+ return;
+ }
+ //System.out.println("Resuming sub-activity: mode=" + mMode);
+ switch (mMode-CHILD_OFFSET) {
+ case NO_RESULT_MODE:
+ finish();
+ break;
+ case RESULT_MODE:
+ Intent res = new Intent();
+ res.putExtra("tkey", "tval");
+ res.setAction("test");
+ setResult(RESULT_OK, res);
+ finish();
+ break;
+ case FINISH_SUB_MODE:
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ //Log.i("foo", "SubActivityScreen pid=" + Process.myPid()
+ // + " onActivityResult: req=" + requestCode
+ // + " res=" + resultCode);
+
+ // Assume success.
+ setResult(RESULT_OK);
+
+ if (requestCode == 1) {
+ switch (mMode) {
+ case NO_RESULT_MODE:
+ case FINISH_SUB_MODE:
+ if (resultCode != RESULT_CANCELED) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Incorrect result code returned: " + resultCode));
+ }
+ break;
+ case RESULT_MODE:
+ case PENDING_RESULT_MODE:
+ if (resultCode != RESULT_OK) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Incorrect result code returned: " + resultCode));
+ } else if (data == null) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "null data returned"));
+ } else if (!("test".equals(data.getAction()))) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Incorrect action returned: " + data));
+ } else if (!("tval".equals(data.getStringExtra("tkey")))) {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Incorrect extras returned: " + data.getExtras()));
+ }
+ break;
+ }
+ } else {
+ setResult(RESULT_CANCELED, (new Intent()).setAction(
+ "Incorrect request code returned: " + requestCode));
+ }
+
+ finish();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ handleBeforeStopping();
+ }
+
+ public void handleBeforeStopping() {
+ if (mMode == FINISH_SUB_MODE) {
+ finishActivity(1);
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/SubActivityTest.java b/core/tests/coretests/src/android/app/activity/SubActivityTest.java
new file mode 100644
index 0000000..35dde8a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/SubActivityTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.app.activity;
+
+import android.test.suitebuilder.annotation.Suppress;
+import android.content.ComponentName;
+
+@Suppress
+public class SubActivityTest extends ActivityTestsBase {
+
+ public void testPendingResult() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.PENDING_RESULT_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testNoResult() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testResult() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testFinishSub() throws Exception {
+ mIntent.putExtra("component",
+ new ComponentName(getContext(), RemoteSubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteNoResult() throws Exception {
+ mIntent.putExtra("component",
+ new ComponentName(getContext(), RemoteSubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteResult() throws Exception {
+ mIntent.putExtra("component",
+ new ComponentName(getContext(), RemoteSubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteFinishSub() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteRestartNoResult() throws Exception {
+ mIntent.putExtra("component",
+ new ComponentName(getContext(), RemoteSubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.NO_RESULT_MODE);
+ mIntent.putExtra("kill", true);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteRestartResult() throws Exception {
+ mIntent.putExtra("component",
+ new ComponentName(getContext(), RemoteSubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.RESULT_MODE);
+ mIntent.putExtra("kill", true);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+
+ public void testRemoteRestartFinishSub() throws Exception {
+ mIntent.putExtra("component", new ComponentName(getContext(), SubActivityScreen.class));
+ mIntent.putExtra("mode", SubActivityScreen.FINISH_SUB_MODE);
+ mIntent.putExtra("kill", true);
+ runLaunchpad(LaunchpadActivity.LAUNCH);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/activity/TestedActivity.java b/core/tests/coretests/src/android/app/activity/TestedActivity.java
new file mode 100644
index 0000000..3a1c15f
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/TestedActivity.java
@@ -0,0 +1,77 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.Bundle;
+
+public class TestedActivity extends Activity
+{
+ public TestedActivity()
+ {
+ }
+
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+ }
+
+ protected void onRestoreInstanceState(Bundle state)
+ {
+ super.onRestoreInstanceState(state);
+ }
+
+ protected void onResume()
+ {
+ super.onResume();
+ Looper.myLooper().myQueue().addIdleHandler(new Idler());
+ }
+
+ protected void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+ }
+
+ protected void onStop()
+ {
+ super.onStop();
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ setResult(RESULT_OK);
+ finish();
+ }
+ };
+
+ private class Idler implements MessageQueue.IdleHandler
+ {
+ public final boolean queueIdle()
+ {
+ //Message m = Message.obtain();
+ //mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000);
+ setResult(RESULT_OK);
+ finish();
+ return false;
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/app/activity/TestedScreen.java b/core/tests/coretests/src/android/app/activity/TestedScreen.java
new file mode 100644
index 0000000..1682d1a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/activity/TestedScreen.java
@@ -0,0 +1,128 @@
+/*
+ * 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.app.activity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestedScreen extends Activity
+{
+ public static final String WAIT_BEFORE_FINISH = "TestedScreen.WAIT_BEFORE_FINISH";
+ public static final String DELIVER_RESULT = "TestedScreen.DELIVER_RESULT";
+ public static final String CLEAR_TASK = "TestedScreen.CLEAR_TASK";
+
+ public TestedScreen() {
+ }
+
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "CREATE tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ if (LaunchpadActivity.FORWARD_RESULT.equals(getIntent().getAction())) {
+ Intent intent = new Intent(getIntent());
+ intent.setAction(DELIVER_RESULT);
+ intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ finish();
+ } else if (DELIVER_RESULT.equals(getIntent().getAction())) {
+ setResult(RESULT_OK, (new Intent()).setAction(
+ LaunchpadActivity.RETURNED_RESULT));
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ finish();
+ } else if (CLEAR_TASK.equals(getIntent().getAction())) {
+ if (!getIntent().getBooleanExtra(ClearTop.WAIT_CLEAR_TASK, false)) {
+ launchClearTask();
+ }
+ }
+ }
+
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+ }
+
+ protected void onResume() {
+ super.onResume();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "RESUME tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ if (CLEAR_TASK.equals(getIntent().getAction())) {
+ if (getIntent().getBooleanExtra(ClearTop.WAIT_CLEAR_TASK, false)) {
+ Looper.myLooper().myQueue().addIdleHandler(new Idler());
+ }
+ } else {
+ Looper.myLooper().myQueue().addIdleHandler(new Idler());
+ }
+ }
+
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ }
+
+ protected void onStop() {
+ super.onStop();
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "STOP tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ }
+
+ private void launchClearTask() {
+ Intent intent = new Intent(getIntent()).
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).
+ setClass(this, ClearTop.class);
+ startActivity(intent);
+ }
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if (CLEAR_TASK.equals(getIntent().getAction())) {
+ launchClearTask();
+ } else {
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ };
+
+ private class Idler implements MessageQueue.IdleHandler {
+ public final boolean queueIdle() {
+ if (WAIT_BEFORE_FINISH.equals(getIntent().getAction())) {
+ Message m = Message.obtain();
+ mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000);
+ } else if (CLEAR_TASK.equals(getIntent().getAction())) {
+ Message m = Message.obtain();
+ mHandler.sendMessageAtTime(m, SystemClock.uptimeMillis()+1000);
+ } else {
+ if (ActivityTests.DEBUG_LIFECYCLE) Log.v("test", "Finishing tested "
+ + Integer.toHexString(System.identityHashCode(this)) + ": " + getIntent());
+ setResult(RESULT_OK);
+ finish();
+ }
+ return false;
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/bluetooth/AtParserTest.java b/core/tests/coretests/src/android/bluetooth/AtParserTest.java
new file mode 100644
index 0000000..c5aa52b
--- /dev/null
+++ b/core/tests/coretests/src/android/bluetooth/AtParserTest.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.AtCommandHandler;
+import android.bluetooth.AtCommandResult;
+import android.bluetooth.AtParser;
+
+import java.util.*;
+import junit.framework.*;
+
+public class AtParserTest extends TestCase {
+
+ /* An AtCommandHandler instrumented for testing purposes
+ */
+ private class HandlerTest extends AtCommandHandler {
+ boolean mBasicCalled, mActionCalled, mReadCalled, mTestCalled,
+ mSetCalled;
+ int mBasicReturn, mActionReturn, mReadReturn, mTestReturn, mSetReturn;
+ Object[] mSetArgs;
+ String mBasicArgs;
+
+ HandlerTest() {
+ this(AtCommandResult.ERROR, AtCommandResult.ERROR,
+ AtCommandResult.ERROR, AtCommandResult.ERROR,
+ AtCommandResult.ERROR);
+ }
+
+ HandlerTest(int a, int b, int c, int d, int e) {
+ mBasicReturn = a;
+ mActionReturn = b;
+ mReadReturn = c;
+ mSetReturn = d;
+ mTestReturn = e;
+ reset();
+ }
+ public void reset() {
+ mBasicCalled = false;
+ mActionCalled = false;
+ mReadCalled = false;
+ mSetCalled = false;
+ mTestCalled = false;
+ mSetArgs = null;
+ mBasicArgs = null;
+ }
+ public boolean wasCalled() { // helper
+ return mBasicCalled || mActionCalled || mReadCalled ||
+ mTestCalled || mSetCalled;
+ }
+ @Override
+ public AtCommandResult handleBasicCommand(String args) {
+ mBasicCalled = true;
+ mBasicArgs = args;
+ return new AtCommandResult(mBasicReturn);
+ }
+ @Override
+ public AtCommandResult handleActionCommand() {
+ mActionCalled = true;
+ return new AtCommandResult(mActionReturn);
+ }
+ @Override
+ public AtCommandResult handleReadCommand() {
+ mReadCalled = true;
+ return new AtCommandResult(mReadReturn);
+ }
+ @Override
+ public AtCommandResult handleSetCommand(Object[] args) {
+ mSetCalled = true;
+ mSetArgs = args;
+ return new AtCommandResult(mSetReturn);
+ }
+ @Override
+ public AtCommandResult handleTestCommand() {
+ mTestCalled = true;
+ return new AtCommandResult(mTestReturn);
+ }
+ }
+
+ private AtParser mParser;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mParser = new AtParser();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+
+ /* Test that the right method is being called
+ */
+/* public void testBasic1() throws Exception {
+ HandlerTest D = new HandlerTest(0, 1, 1, 1, 1);
+ HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
+ mParser.register('D', D);
+ mParser.register('A', A);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process(" A T D = ? T 1 2 3 4 ").toStrings()));
+ assertTrue(D.mBasicCalled);
+ assertFalse(D.mActionCalled);
+ assertFalse(D.mTestCalled);
+ assertFalse(D.mSetCalled);
+ assertFalse(D.mReadCalled);
+ assertFalse(A.wasCalled());
+ assertEquals("=?T1234", D.mBasicArgs);
+ }
+*/
+ /* Test some crazy strings
+ *//*
+ public void testBasic2() throws Exception {
+ HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
+ mParser.register('A', A);
+
+ assertTrue(Arrays.equals(
+ new String[]{},
+ mParser.process(" ").toStrings()));
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process(" a T a t \"\" 1 2 3 a 4 ")
+ .toStrings()));
+ assertEquals("T\"\"123A4", A.mBasicArgs);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process(" a T a t \"foo BaR12Z\" 1 2 3 a 4 ")
+ .toStrings()));
+ assertEquals("T\"foo BaR12Z\"123A4", A.mBasicArgs);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("ATA\"").toStrings()));
+ assertEquals("\"\"", A.mBasicArgs);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("ATA\"a").toStrings()));
+ assertEquals("\"a\"", A.mBasicArgs);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("ATa\" ").toStrings()));
+ assertEquals("\" \"", A.mBasicArgs);
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("ATA \"one \" two \"t hr ee ")
+ .toStrings()));
+ assertEquals("\"one \"TWO\"t hr ee \"", A.mBasicArgs);
+ }*/
+
+ /* Simple extended commands
+ *//*
+ public void testExt1() throws Exception {
+ HandlerTest A = new HandlerTest(1, 0, 0, 0, 0);
+ mParser.register("+A", A);
+
+ assertTrue(Arrays.equals(
+ new String[]{"ERROR"},
+ mParser.process("AT+B").toStrings()));
+ assertFalse(A.wasCalled());
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+A").toStrings()));
+ assertTrue(A.mActionCalled);
+ A.mActionCalled = false;
+ assertFalse(A.wasCalled());
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+A=").toStrings()));
+ assertTrue(A.mSetCalled);
+ A.mSetCalled = false;
+ assertFalse(A.wasCalled());
+ assertEquals(1, A.mSetArgs.length);
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+A=?").toStrings()));
+ assertTrue(A.mTestCalled);
+ A.mTestCalled = false;
+ assertFalse(A.wasCalled());
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+A?").toStrings()));
+ assertTrue(A.mReadCalled);
+ A.mReadCalled = false;
+ assertFalse(A.wasCalled());
+ A.reset();
+ }
+*/
+
+
+ /* Test chained commands
+ *//*
+ public void testChain1() throws Exception {
+ HandlerTest A = new HandlerTest(0, 1, 1, 1, 1);
+ HandlerTest B = new HandlerTest(1, 0, 0, 0, 0);
+ HandlerTest C = new HandlerTest(1, 1, 1, 1, 1);
+ mParser.register('A', A);
+ mParser.register("+B", B);
+ mParser.register("+C", C);
+
+ assertTrue(Arrays.equals(
+ new String[]{"ERROR"},
+ mParser.process("AT+B;+C").toStrings()));
+ assertTrue(B.mActionCalled);
+ assertTrue(C.mActionCalled);
+ B.reset();
+ C.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"ERROR"},
+ mParser.process("AT+C;+B").toStrings()));
+ assertFalse(B.wasCalled());
+ assertTrue(C.mActionCalled);
+ B.reset();
+ C.reset();
+ }*/
+
+ /* Test Set command
+ *//*
+ public void testSet1() throws Exception {
+ HandlerTest A = new HandlerTest(1, 1, 1, 0, 1);
+ mParser.register("+AAAA", A);
+ Object[] expectedResult;
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=1").toStrings()));
+ expectedResult = new Object[]{(Integer)1};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=1,2,3").toStrings()));
+ expectedResult = new Object[]{(Integer)1, (Integer)2, (Integer)3};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=3,0,0,1").toStrings()));
+ expectedResult = new Object[]{(Integer)3, (Integer)0, (Integer)0,
+ (Integer)1};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=\"foo\",1,\"b,ar").toStrings()));
+ expectedResult = new Object[]{"\"foo\"", 1, "\"b,ar\""};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=").toStrings()));
+ expectedResult = new Object[]{""};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=,").toStrings()));
+ expectedResult = new Object[]{"", ""};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=,,,").toStrings()));
+ expectedResult = new Object[]{"", "", "", ""};
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("AT+AAAA=,1,,\"foo\",").toStrings()));
+ expectedResult = new Object[]{"", 1, "", "\"foo\"", ""};
+ assertEquals(5, A.mSetArgs.length);
+ assertTrue(Arrays.equals(expectedResult, A.mSetArgs));
+ A.reset();
+ }*/
+
+ /* Test repeat command "A/"
+ *//*
+ public void testRepeat() throws Exception {
+ HandlerTest A = new HandlerTest(0, 0, 0, 0, 0);
+ mParser.register('A', A);
+
+ // Try repeated command on fresh parser
+ assertTrue(Arrays.equals(
+ new String[]{},
+ mParser.process("A/").toStrings()));
+ assertFalse(A.wasCalled());
+ A.reset();
+
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("ATA").toStrings()));
+ assertTrue(A.mBasicCalled);
+ assertEquals("", A.mBasicArgs);
+ A.reset();
+
+ // Now repeat the command
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("A/").toStrings()));
+ assertTrue(A.mBasicCalled);
+ assertEquals("", A.mBasicArgs);
+ A.reset();
+
+ // Multiple repeats
+ assertTrue(Arrays.equals(
+ new String[]{"OK"},
+ mParser.process("A/").toStrings()));
+ assertTrue(A.mBasicCalled);
+ assertEquals("", A.mBasicArgs);
+ A.reset();
+
+ }*/
+}
diff --git a/core/tests/coretests/src/android/content/AssetTest.java b/core/tests/coretests/src/android/content/AssetTest.java
new file mode 100644
index 0000000..b66574c
--- /dev/null
+++ b/core/tests/coretests/src/android/content/AssetTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.res.AssetManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AssetTest extends AndroidTestCase {
+ private AssetManager mAssets;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mAssets = mContext.getAssets();
+ }
+
+ public static void verifyTextAsset(InputStream is) throws IOException {
+ String expectedString = "OneTwoThreeFourFiveSixSevenEightNineTen";
+ byte[] buffer = new byte[10];
+
+ int readCount;
+ int curIndex = 0;
+ while ((readCount = is.read(buffer, 0, buffer.length)) > 0) {
+ for (int i = 0; i < readCount; i++) {
+ assertEquals("At index " + curIndex
+ + " expected " + expectedString.charAt(curIndex)
+ + " but found " + ((char) buffer[i]),
+ buffer[i], expectedString.charAt(curIndex));
+ curIndex++;
+ }
+ }
+
+ readCount = is.read(buffer, 0, buffer.length);
+ assertEquals("Reading end of buffer: expected readCount=-1 but got " + readCount,
+ -1, readCount);
+
+ readCount = is.read(buffer, buffer.length, 0);
+ assertEquals("Reading end of buffer length 0: expected readCount=0 but got " + readCount,
+ 0, readCount);
+
+ is.close();
+ }
+
+ @SmallTest
+ public void testReadToEnd() throws Exception {
+ InputStream is = mAssets.open("text.txt");
+ verifyTextAsset(is);
+ }
+
+ // XXX failing
+ public void xxtestListDir() throws Exception {
+ String[] files = mAssets.list("");
+ assertEquals(1, files.length);
+ assertEquals("test.txt", files[0]);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java
new file mode 100644
index 0000000..c7d0b7a
--- /dev/null
+++ b/core/tests/coretests/src/android/content/BrickDeniedTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/** Test to make sure brick intents <b>don't</b> work without permission. */
+public class BrickDeniedTest extends AndroidTestCase {
+ @MediumTest
+ public void testBrick() {
+ // Try both the old and new brick intent names. Neither should work,
+ // since this test application doesn't have the required permission.
+ // If it does work, well, the test certainly won't pass.
+ getContext().sendBroadcast(new Intent("SHES_A_BRICK_HOUSE"));
+ getContext().sendBroadcast(new Intent("android.intent.action.BRICK"));
+ }
+}
diff --git a/core/tests/coretests/src/android/content/ContentProviderOperationTest.java b/core/tests/coretests/src/android/content/ContentProviderOperationTest.java
new file mode 100644
index 0000000..aea124b
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentProviderOperationTest.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Map;
+import java.util.Map.Entry;
+
+@SmallTest
+public class ContentProviderOperationTest extends TestCase {
+ private final static Uri sTestUri1 = Uri.parse("content://authority/blah");
+ private final static ContentValues sTestValues1;
+
+ private final static Class<ContentProviderOperation.Builder> CLASS_BUILDER =
+ ContentProviderOperation.Builder.class;
+ private final static Class<ContentProviderOperation> CLASS_OPERATION =
+ ContentProviderOperation.class;
+
+ static {
+ sTestValues1 = new ContentValues();
+ sTestValues1.put("a", 1);
+ sTestValues1.put("b", "two");
+ }
+
+ public void testInsert() throws OperationApplicationException {
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .withValues(sTestValues1)
+ .build();
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(sTestUri1.toString(), uri.toString());
+ assertEquals(sTestValues1.toString(), values.toString());
+ return uri.buildUpon().appendPath("19").build();
+ }
+ }, null, 0);
+ assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
+ }
+
+ public void testInsertNoValues() throws OperationApplicationException {
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .build();
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(sTestUri1.toString(), uri.toString());
+ assertNull(values);
+ return uri.buildUpon().appendPath("19").build();
+ }
+ }, null, 0);
+ assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
+ }
+
+ public void testInsertFailed() {
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .withValues(sTestValues1)
+ .build();
+ try {
+ op1.apply(new TestContentProvider() {
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(sTestUri1.toString(), uri.toString());
+ assertEquals(sTestValues1.toString(), values.toString());
+ return null;
+ }
+ }, null, 0);
+ fail("the apply should have thrown an OperationApplicationException");
+ } catch (OperationApplicationException e) {
+ // this is the expected case
+ }
+ }
+
+ public void testInsertWithBackRefs() throws OperationApplicationException {
+ ContentProviderResult[] previousResults = new ContentProviderResult[4];
+ previousResults[0] = new ContentProviderResult(100);
+ previousResults[1] = new ContentProviderResult(101);
+ previousResults[2] = new ContentProviderResult(102);
+ previousResults[3] = new ContentProviderResult(103);
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .withValues(sTestValues1)
+ .withValueBackReference("a1", 3)
+ .withValueBackReference("a2", 1)
+ .build();
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(sTestUri1.toString(), uri.toString());
+ ContentValues expected = new ContentValues(sTestValues1);
+ expected.put("a1", 103);
+ expected.put("a2", 101);
+ assertEquals(expected.toString(), values.toString());
+ return uri.buildUpon().appendPath("19").build();
+ }
+ }, previousResults, previousResults.length);
+ assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
+ }
+
+ public void testUpdate() throws OperationApplicationException {
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .withValues(sTestValues1)
+ .build();
+ ContentProviderResult[] backRefs = new ContentProviderResult[2];
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Uri insert(Uri uri, ContentValues values) {
+ assertEquals(sTestUri1.toString(), uri.toString());
+ assertEquals(sTestValues1.toString(), values.toString());
+ return uri.buildUpon().appendPath("19").build();
+ }
+ }, backRefs, 1);
+ assertEquals(sTestUri1.buildUpon().appendPath("19").toString(), result.uri.toString());
+ }
+
+ public void testAssert() {
+ // Build an operation to assert values match provider
+ ContentProviderOperation op1 = ContentProviderOperation.newAssertQuery(sTestUri1)
+ .withValues(sTestValues1).build();
+
+ try {
+ // Assert that values match from cursor
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // Return cursor over specific set of values
+ return getCursor(sTestValues1, 1);
+ }
+ }, null, 0);
+ } catch (OperationApplicationException e) {
+ fail("newAssert() failed");
+ }
+ }
+
+ public void testAssertNoValues() {
+ // Build an operation to assert values match provider
+ ContentProviderOperation op1 = ContentProviderOperation.newAssertQuery(sTestUri1)
+ .withExpectedCount(1).build();
+
+ try {
+ // Assert that values match from cursor
+ ContentProviderResult result = op1.apply(new TestContentProvider() {
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // Return cursor over specific set of values
+ return getCursor(sTestValues1, 1);
+ }
+ }, null, 0);
+ } catch (OperationApplicationException e) {
+ fail("newAssert() failed");
+ }
+
+ ContentProviderOperation op2 = ContentProviderOperation.newAssertQuery(sTestUri1)
+ .withExpectedCount(0).build();
+
+ try {
+ // Assert that values match from cursor
+ ContentProviderResult result = op2.apply(new TestContentProvider() {
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // Return cursor over specific set of values
+ return getCursor(sTestValues1, 0);
+ }
+ }, null, 0);
+ } catch (OperationApplicationException e) {
+ fail("newAssert() failed");
+ }
+
+ ContentProviderOperation op3 = ContentProviderOperation.newAssertQuery(sTestUri1)
+ .withExpectedCount(2).build();
+
+ try {
+ // Assert that values match from cursor
+ ContentProviderResult result = op3.apply(new TestContentProvider() {
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ // Return cursor over specific set of values
+ return getCursor(sTestValues1, 5);
+ }
+ }, null, 0);
+ fail("we expect the exception to be thrown");
+ } catch (OperationApplicationException e) {
+ }
+ }
+
+ /**
+ * Build a {@link Cursor} with a single row that contains all values
+ * provided through the given {@link ContentValues}.
+ */
+ private Cursor getCursor(ContentValues contentValues, int numRows) {
+ final Set<Entry<String, Object>> valueSet = contentValues.valueSet();
+ final String[] keys = new String[valueSet.size()];
+ final Object[] values = new Object[valueSet.size()];
+
+ int i = 0;
+ for (Entry<String, Object> entry : valueSet) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ i++;
+ }
+
+ final MatrixCursor cursor = new MatrixCursor(keys);
+ for (i = 0; i < numRows; i++) {
+ cursor.addRow(values);
+ }
+ return cursor;
+ }
+
+ public void testValueBackRefs() {
+ ContentValues values = new ContentValues();
+ values.put("a", "in1");
+ values.put("a2", "in2");
+ values.put("b", "in3");
+ values.put("c", "in4");
+
+ ContentProviderResult[] previousResults = new ContentProviderResult[4];
+ previousResults[0] = new ContentProviderResult(100);
+ previousResults[1] = new ContentProviderResult(101);
+ previousResults[2] = new ContentProviderResult(102);
+ previousResults[3] = new ContentProviderResult(103);
+
+ ContentValues expectedValues = new ContentValues(values);
+ expectedValues.put("a1", (long) 103);
+ expectedValues.put("a2", (long) 101);
+ expectedValues.put("a3", (long) 102);
+
+ ContentProviderOperation op1 = ContentProviderOperation.newInsert(sTestUri1)
+ .withValues(values)
+ .withValueBackReference("a1", 3)
+ .withValueBackReference("a2", 1)
+ .withValueBackReference("a3", 2)
+ .build();
+ ContentValues v2 = op1.resolveValueBackReferences(previousResults, previousResults.length);
+ assertEquals(expectedValues, v2);
+ }
+
+ public void testSelectionBackRefs() {
+ ContentProviderResult[] previousResults = new ContentProviderResult[4];
+ previousResults[0] = new ContentProviderResult(100);
+ previousResults[1] = new ContentProviderResult(101);
+ previousResults[2] = new ContentProviderResult(102);
+ previousResults[3] = new ContentProviderResult(103);
+
+ String[] selectionArgs = new String[]{"a", null, null, "b", null};
+
+ final ContentValues values = new ContentValues();
+ values.put("unused", "unused");
+
+ ContentProviderOperation op1 = ContentProviderOperation.newUpdate(sTestUri1)
+ .withSelectionBackReference(1, 3)
+ .withSelectionBackReference(2, 1)
+ .withSelectionBackReference(4, 2)
+ .withSelection("unused", selectionArgs)
+ .withValues(values)
+ .build();
+ String[] s2 = op1.resolveSelectionArgsBackReferences(
+ previousResults, previousResults.length);
+ assertEquals("a,103,101,b,102", TextUtils.join(",", s2));
+ }
+
+ public void testParcelingOperation() throws NoSuchFieldException, IllegalAccessException,
+ NoSuchMethodException, InvocationTargetException, InstantiationException {
+ Parcel parcel = Parcel.obtain();
+ ContentProviderOperation op1;
+ ContentProviderOperation op2;
+
+ HashMap<Integer, Integer> selArgsBackRef = new HashMap<Integer, Integer>();
+ selArgsBackRef.put(1, 2);
+ selArgsBackRef.put(3, 4);
+
+ ContentValues values = new ContentValues();
+ values.put("v1", "val1");
+ values.put("v2", "43");
+
+ ContentValues valuesBackRef = new ContentValues();
+ values.put("v3", "val3");
+ values.put("v4", "44");
+
+ try {
+ ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(
+ Uri.parse("content://goo/bar"));
+
+ builderSetExpectedCount(builder, 42);
+ builderSetSelection(builder, "selection");
+ builderSetSelectionArgs(builder, new String[]{"a", "b"});
+ builderSetSelectionArgsBackReferences(builder, selArgsBackRef);
+ builderSetValues(builder, values);
+ builderSetValuesBackReferences(builder, valuesBackRef);
+
+ op1 = newOperationFromBuilder(builder);
+ op1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
+
+ assertEquals(ContentProviderOperation.TYPE_INSERT, operationGetType(op2));
+ assertEquals("content://goo/bar", operationGetUri(op2).toString());
+ assertEquals(Integer.valueOf(42), operationGetExpectedCount(op2));
+ assertEquals("selection", operationGetSelection(op2));
+ assertEquals(2, operationGetSelectionArgs(op2).length);
+ assertEquals("a", operationGetSelectionArgs(op2)[0]);
+ assertEquals("b", operationGetSelectionArgs(op2)[1]);
+ assertEquals(values, operationGetValues(op2));
+ assertEquals(valuesBackRef, operationGetValuesBackReferences(op2));
+ assertEquals(2, operationGetSelectionArgsBackReferences(op2).size());
+ assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1));
+ assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3));
+ } finally {
+ parcel.recycle();
+ }
+
+ try {
+ ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(
+ Uri.parse("content://goo/bar"));
+
+ builderSetSelectionArgsBackReferences(builder, selArgsBackRef);
+
+ op1 = newOperationFromBuilder(builder);
+ op1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
+ assertEquals(ContentProviderOperation.TYPE_UPDATE, operationGetType(op2));
+ assertEquals("content://goo/bar", operationGetUri(op2).toString());
+ assertNull(operationGetExpectedCount(op2));
+ assertNull(operationGetSelection(op2));
+ assertNull(operationGetSelectionArgs(op2));
+ assertNull(operationGetValues(op2));
+ assertNull(operationGetValuesBackReferences(op2));
+ assertEquals(2, operationGetSelectionArgsBackReferences(op2).size());
+ assertEquals(Integer.valueOf(2), operationGetSelectionArgsBackReferences(op2).get(1));
+ assertEquals(Integer.valueOf(4), operationGetSelectionArgsBackReferences(op2).get(3));
+ } finally {
+ parcel.recycle();
+ }
+
+ try {
+ ContentProviderOperation.Builder builder = ContentProviderOperation.newDelete(
+ Uri.parse("content://goo/bar"));
+
+ op1 = newOperationFromBuilder(builder);
+ op1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ op2 = ContentProviderOperation.CREATOR.createFromParcel(parcel);
+ assertEquals(ContentProviderOperation.TYPE_DELETE, operationGetType(op2));
+ assertEquals("content://goo/bar", operationGetUri(op2).toString());
+ assertNull(operationGetExpectedCount(op2));
+ assertNull(operationGetSelection(op2));
+ assertNull(operationGetSelectionArgs(op2));
+ assertNull(operationGetValues(op2));
+ assertNull(operationGetValuesBackReferences(op2));
+ assertNull(operationGetSelectionArgsBackReferences(op2));
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ private static ContentProviderOperation newOperationFromBuilder(
+ ContentProviderOperation.Builder builder)
+ throws NoSuchMethodException, InstantiationException, IllegalAccessException,
+ InvocationTargetException {
+ final Constructor constructor = CLASS_OPERATION.getDeclaredConstructor(CLASS_BUILDER);
+ constructor.setAccessible(true);
+ return (ContentProviderOperation) constructor.newInstance(builder);
+ }
+
+ private void builderSetSelectionArgsBackReferences(
+ ContentProviderOperation.Builder builder, HashMap<Integer, Integer> selArgsBackRef)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mSelectionArgsBackReferences");
+ field.setAccessible(true);
+ field.set(builder, selArgsBackRef);
+ }
+
+ private void builderSetValuesBackReferences(
+ ContentProviderOperation.Builder builder, ContentValues valuesBackReferences)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mValuesBackReferences");
+ field.setAccessible(true);
+ field.set(builder, valuesBackReferences);
+ }
+
+ private void builderSetSelection(
+ ContentProviderOperation.Builder builder, String selection)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mSelection");
+ field.setAccessible(true);
+ field.set(builder, selection);
+ }
+
+ private void builderSetSelectionArgs(
+ ContentProviderOperation.Builder builder, String[] selArgs)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mSelectionArgs");
+ field.setAccessible(true);
+ field.set(builder, selArgs);
+ }
+
+ private void builderSetValues(
+ ContentProviderOperation.Builder builder, ContentValues values)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mValues");
+ field.setAccessible(true);
+ field.set(builder, values);
+ }
+
+ private void builderSetExpectedCount(
+ ContentProviderOperation.Builder builder, Integer expectedCount)
+ throws NoSuchFieldException, IllegalAccessException {
+ Field field;
+ field = CLASS_BUILDER.getDeclaredField("mExpectedCount");
+ field.setAccessible(true);
+ field.set(builder, expectedCount);
+ }
+
+ private int operationGetType(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mType");
+ field.setAccessible(true);
+ return field.getInt(operation);
+ }
+
+ private Uri operationGetUri(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mUri");
+ field.setAccessible(true);
+ return (Uri) field.get(operation);
+ }
+
+ private String operationGetSelection(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mSelection");
+ field.setAccessible(true);
+ return (String) field.get(operation);
+ }
+
+ private String[] operationGetSelectionArgs(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgs");
+ field.setAccessible(true);
+ return (String[]) field.get(operation);
+ }
+
+ private ContentValues operationGetValues(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mValues");
+ field.setAccessible(true);
+ return (ContentValues) field.get(operation);
+ }
+
+ private Integer operationGetExpectedCount(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mExpectedCount");
+ field.setAccessible(true);
+ return (Integer) field.get(operation);
+ }
+
+ private ContentValues operationGetValuesBackReferences(ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mValuesBackReferences");
+ field.setAccessible(true);
+ return (ContentValues) field.get(operation);
+ }
+
+ private Map<Integer, Integer> operationGetSelectionArgsBackReferences(
+ ContentProviderOperation operation)
+ throws NoSuchFieldException, IllegalAccessException {
+ final Field field = CLASS_OPERATION.getDeclaredField("mSelectionArgsBackReferences");
+ field.setAccessible(true);
+ return (Map<Integer, Integer>) field.get(operation);
+ }
+
+ public void testParcelingResult() {
+ Parcel parcel = Parcel.obtain();
+ ContentProviderResult result1;
+ ContentProviderResult result2;
+ try {
+ result1 = new ContentProviderResult(Uri.parse("content://goo/bar"));
+ result1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ result2 = ContentProviderResult.CREATOR.createFromParcel(parcel);
+ assertEquals("content://goo/bar", result2.uri.toString());
+ assertNull(result2.count);
+ } finally {
+ parcel.recycle();
+ }
+
+ parcel = Parcel.obtain();
+ try {
+ result1 = new ContentProviderResult(42);
+ result1.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ result2 = ContentProviderResult.CREATOR.createFromParcel(parcel);
+ assertEquals(Integer.valueOf(42), result2.count);
+ assertNull(result2.uri);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ static class TestContentProvider extends ContentProvider {
+ public boolean onCreate() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getType(Uri uri) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException();
+ }
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/ContentQueryMapTest.java b/core/tests/coretests/src/android/content/ContentQueryMapTest.java
new file mode 100644
index 0000000..d1b8c24
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ContentQueryMapTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.ContentQueryMap;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.util.Observable;
+import java.util.Observer;
+
+/** Test of {@link ContentQueryMap} */
+public class ContentQueryMapTest extends AndroidTestCase {
+ /** Helper class to run test code in a new thread with a Looper. */
+ private abstract class LooperThread extends Thread {
+ public Throwable mError = null;
+ public boolean mSuccess = false;
+
+ abstract void go();
+
+ public void run() {
+ try {
+ Looper.prepare();
+ go();
+ Looper.loop();
+ } catch (Throwable e) {
+ mError = e;
+ }
+ }
+ }
+
+ @MediumTest
+ public void testContentQueryMap() throws Throwable {
+ LooperThread thread = new LooperThread() {
+ void go() {
+ ContentResolver r = getContext().getContentResolver();
+ Settings.System.putString(r, "test", "Value");
+ Cursor cursor = r.query(
+ Settings.System.CONTENT_URI,
+ new String[] {
+ Settings.System.NAME,
+ Settings.System.VALUE,
+ }, null, null, null);
+
+ final ContentQueryMap cqm = new ContentQueryMap(
+ cursor, Settings.System.NAME, true, null);
+ // Get the current state of the CQM. This forces a requery and means that the
+ // call to getValues() below won't do a requery().
+ cqm.getRows();
+
+ // The cache won't notice changes until the loop runs.
+ Settings.System.putString(r, "test", "New Value");
+ ContentValues v = cqm.getValues("test");
+ String value = v.getAsString(Settings.System.VALUE);
+ assertEquals("Value", value);
+
+ // Use an Observer to find out when the cache does update.
+ cqm.addObserver(new Observer() {
+ public void update(Observable o, Object arg) {
+ // Should have the new values by now.
+ ContentValues v = cqm.getValues("test");
+ String value = v.getAsString(Settings.System.VALUE);
+ assertEquals("New Value", value);
+ Looper.myLooper().quit();
+ cqm.close();
+ mSuccess = true;
+ }
+ });
+
+ // Give up after a few seconds, if it doesn't.
+ new Handler().postDelayed(new Runnable() {
+ public void run() {
+ fail("Timed out");
+ }
+ }, 5000);
+ }
+ };
+
+ thread.start();
+ thread.join();
+ if (thread.mError != null) throw thread.mError;
+ assertTrue(thread.mSuccess);
+ }
+}
diff --git a/core/java/android/os/HandlerState.java b/core/tests/coretests/src/android/content/ContentTests.java
index 0708f7d..a1299e3 100644
--- a/core/java/android/os/HandlerState.java
+++ b/core/tests/coretests/src/android/content/ContentTests.java
@@ -14,20 +14,15 @@
* limitations under the License.
*/
-package android.os;
+package android.content;
-/**
- * {@hide}
- */
-public abstract class HandlerState {
- public HandlerState() {
- }
-
- public void enter(Message message) {
- }
+import junit.framework.TestSuite;
- public abstract void processMessage(Message message);
+public class ContentTests {
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite(ContentTests.class.getName());
- public void exit(Message message) {
+ suite.addTestSuite(AssetTest.class);
+ return suite;
}
}
diff --git a/core/tests/coretests/src/android/content/MemoryFileProvider.java b/core/tests/coretests/src/android/content/MemoryFileProvider.java
new file mode 100644
index 0000000..c4bc767
--- /dev/null
+++ b/core/tests/coretests/src/android/content/MemoryFileProvider.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.MemoryFile;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/** Simple test provider that runs in the local process. */
+public class MemoryFileProvider extends ContentProvider {
+ private static final String TAG = "MemoryFileProvider";
+
+ private static final String DATA_FILE = "data.bin";
+
+ // some random data
+ public static final byte[] TEST_BLOB = new byte[] {
+ -12, 127, 0, 3, 1, 2, 3, 4, 5, 6, 1, -128, -1, -54, -65, 35,
+ -53, -96, -74, -74, -55, -43, -69, 3, 52, -58,
+ -121, 127, 87, -73, 16, -13, -103, -65, -128, -36,
+ 107, 24, 118, -17, 97, 97, -88, 19, -94, -54,
+ 53, 43, 44, -27, -124, 28, -74, 26, 35, -36,
+ 16, -124, -31, -31, -128, -79, 108, 116, 43, -17 };
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private static final int DATA_ID_BLOB = 1;
+ private static final int HUGE = 2;
+ private static final int FILE = 3;
+
+ private static final UriMatcher sURLMatcher = new UriMatcher(
+ UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("*", "data/#/blob", DATA_ID_BLOB);
+ sURLMatcher.addURI("*", "huge", HUGE);
+ sURLMatcher.addURI("*", "file", FILE);
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String DATABASE_NAME = "local.db";
+ private static final int DATABASE_VERSION = 1;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE data (" +
+ "_id INTEGER PRIMARY KEY," +
+ "_blob TEXT, " +
+ "integer INTEGER);");
+
+ // insert alarms
+ ContentValues values = new ContentValues();
+ values.put("_id", 1);
+ values.put("_blob", TEST_BLOB);
+ values.put("integer", 100);
+ db.insert("data", null, values);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ Log.w(TAG, "Upgrading test database from version " +
+ oldVersion + " to " + currentVersion +
+ ", which will destroy all old data");
+ db.execSQL("DROP TABLE IF EXISTS data");
+ onCreate(db);
+ }
+ }
+
+
+ public MemoryFileProvider() {
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ try {
+ OutputStream out = getContext().openFileOutput(DATA_FILE, Context.MODE_PRIVATE);
+ out.write(TEST_BLOB);
+ out.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ throw new UnsupportedOperationException("query not supported");
+ }
+
+ @Override
+ public String getType(Uri url) {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA_ID_BLOB:
+ return "application/octet-stream";
+ case FILE:
+ return "application/octet-stream";
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws FileNotFoundException {
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case DATA_ID_BLOB:
+ String sql = "SELECT _blob FROM data WHERE _id=" + url.getPathSegments().get(1);
+ return getBlobColumnAsAssetFile(url, mode, sql);
+ case HUGE:
+ try {
+ MemoryFile memoryFile = new MemoryFile(null, 5000000);
+ memoryFile.writeBytes(TEST_BLOB, 0, 1000000, TEST_BLOB.length);
+ memoryFile.deactivate();
+ return AssetFileDescriptor.fromMemoryFile(memoryFile);
+ } catch (IOException ex) {
+ throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
+ }
+ case FILE:
+ File file = getContext().getFileStreamPath(DATA_FILE);
+ ParcelFileDescriptor fd =
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ return new AssetFileDescriptor(fd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
+ default:
+ throw new FileNotFoundException("No files supported by provider at " + url);
+ }
+ }
+
+ private AssetFileDescriptor getBlobColumnAsAssetFile(Uri url, String mode, String sql)
+ throws FileNotFoundException {
+ if (!"r".equals(mode)) {
+ throw new FileNotFoundException("Mode " + mode + " not supported for " + url);
+ }
+ try {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ MemoryFile file = simpleQueryForBlobMemoryFile(db, sql);
+ if (file == null) throw new FileNotFoundException("No such entry: " + url);
+ AssetFileDescriptor afd = AssetFileDescriptor.fromMemoryFile(file);
+ file.deactivate();
+ // need to dup and then close? openFileHelper() doesn't do that though
+ return afd;
+ } catch (IOException ex) {
+ throw new FileNotFoundException("Error reading " + url + ":" + ex.toString());
+ }
+ }
+
+ private MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql) throws IOException {
+ Cursor cursor = db.rawQuery(sql, null);
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ byte[] bytes = cursor.getBlob(0);
+ MemoryFile file = new MemoryFile(null, bytes.length);
+ file.writeBytes(bytes, 0, 0, bytes.length);
+ return file;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("update not supported");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("insert not supported");
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ throw new UnsupportedOperationException("delete not supported");
+ }
+}
diff --git a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java
new file mode 100644
index 0000000..6708af6
--- /dev/null
+++ b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Tests reading a MemoryFile-based AssestFile from a ContentProvider running
+ * in a different process.
+ */
+public class MemoryFileProviderTest extends AndroidTestCase {
+
+ // reads from a cross-process AssetFileDescriptor for a MemoryFile
+ @MediumTest
+ public void testRead() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ Uri uri = Uri.parse("content://android.content.MemoryFileProvider/data/1/blob");
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull(in);
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertEquals(-1, in.read());
+ in.close();
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ }
+
+ // tests that we don't leak file descriptors or virtual address space
+ @MediumTest
+ public void testClose() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ // open enough file descriptors that we will crash something if we leak FDs
+ // or address space
+ for (int i = 0; i < 1025; i++) {
+ Uri uri = Uri.parse("content://android.content.MemoryFileProvider/huge");
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull("Failed to open stream number " + i, in);
+ assertEquals(1000000, in.skip(1000000));
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ in.close();
+ }
+ }
+
+ // tests that we haven't broken AssestFileDescriptors for normal files.
+ @MediumTest
+ public void testFile() throws Exception {
+ ContentResolver resolver = getContext().getContentResolver();
+ Uri uri = Uri.parse("content://android.content.MemoryFileProvider/file");
+ byte[] buf = new byte[MemoryFileProvider.TEST_BLOB.length];
+ InputStream in = resolver.openInputStream(uri);
+ assertNotNull(in);
+ int count = in.read(buf);
+ assertEquals(buf.length, count);
+ assertEquals(-1, in.read());
+ in.close();
+ assertTrue(Arrays.equals(MemoryFileProvider.TEST_BLOB, buf));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/content/ObserverNodeTest.java b/core/tests/coretests/src/android/content/ObserverNodeTest.java
new file mode 100644
index 0000000..736c759
--- /dev/null
+++ b/core/tests/coretests/src/android/content/ObserverNodeTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import java.util.ArrayList;
+
+import android.content.ContentService.ObserverCall;
+import android.content.ContentService.ObserverNode;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.test.AndroidTestCase;
+
+public class ObserverNodeTest extends AndroidTestCase {
+ static class TestObserver extends ContentObserver {
+ public TestObserver() {
+ super(new Handler());
+ }
+ }
+
+ public void testUri() {
+ ObserverNode root = new ObserverNode("");
+ Uri[] uris = new Uri[] {
+ Uri.parse("content://c/a/"),
+ Uri.parse("content://c/"),
+ Uri.parse("content://x/"),
+ Uri.parse("content://c/b/"),
+ Uri.parse("content://c/a/a1/1/"),
+ Uri.parse("content://c/a/a1/2/"),
+ Uri.parse("content://c/b/1/"),
+ Uri.parse("content://c/b/2/"),
+ };
+
+ int[] nums = new int[] {4, 7, 1, 4, 2, 2, 3, 3};
+
+ // special case
+ root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root);
+ for(int i = 1; i < uris.length; i++) {
+ root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root);
+ }
+
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+
+ for (int i = nums.length - 1; i >=0; --i) {
+ root.collectObserversLocked(uris[i], 0, null, false, calls);
+ assertEquals(nums[i], calls.size());
+ calls.clear();
+ }
+ }
+
+ public void testUriNotNotify() {
+ ObserverNode root = new ObserverNode("");
+ Uri[] uris = new Uri[] {
+ Uri.parse("content://c/"),
+ Uri.parse("content://x/"),
+ Uri.parse("content://c/a/"),
+ Uri.parse("content://c/b/"),
+ Uri.parse("content://c/a/1/"),
+ Uri.parse("content://c/a/2/"),
+ Uri.parse("content://c/b/1/"),
+ Uri.parse("content://c/b/2/"),
+ };
+ int[] nums = new int[] {7, 1, 3, 3, 1, 1, 1, 1};
+
+ for(int i = 0; i < uris.length; i++) {
+ root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root);
+ }
+
+ ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+
+ for (int i = uris.length - 1; i >=0; --i) {
+ root.collectObserversLocked(uris[i], 0, null, false, calls);
+ assertEquals(nums[i], calls.size());
+ calls.clear();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java b/core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java
new file mode 100644
index 0000000..a4c33f5
--- /dev/null
+++ b/core/tests/coretests/src/android/content/SearchRecentSuggestionsProviderTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.app.SearchManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.SearchRecentSuggestions;
+import android.test.ProviderTestCase2;
+import android.test.suitebuilder.annotation.Suppress;
+
+/**
+ * Very simple provider that I can instantiate right here.
+ */
+class TestProvider extends SearchRecentSuggestionsProvider {
+ final static String AUTHORITY = "android.content.TestProvider";
+ final static int MODE = DATABASE_MODE_QUERIES + DATABASE_MODE_2LINES;
+
+ public TestProvider() {
+ super();
+ setupSuggestions(AUTHORITY, MODE);
+ }
+}
+
+/**
+ * ProviderTestCase that performs unit tests of SearchRecentSuggestionsProvider.
+ *
+ * You can run this test in isolation via the commands:
+ *
+ * $ (cd tests/FrameworkTests/ && mm) && adb sync
+ * $ adb shell am instrument -w \
+ * -e class android.content.SearchRecentSuggestionsProviderTest
+ * com.android.frameworktest.tests/android.test.InstrumentationTestRunner
+ */
+// Suppress these until bug http://b/issue?id=1416586 is fixed.
+@Suppress
+public class SearchRecentSuggestionsProviderTest extends ProviderTestCase2<TestProvider> {
+
+ // Elements prepared by setUp()
+ SearchRecentSuggestions mSearchHelper;
+
+ public SearchRecentSuggestionsProviderTest() {
+ super(TestProvider.class, TestProvider.AUTHORITY);
+ }
+
+ /**
+ * During setup, grab a helper for DB access
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Use the recent suggestions helper. As long as we pass in our isolated context,
+ // it should correctly access the provider under test.
+ mSearchHelper = new SearchRecentSuggestions(getMockContext(),
+ TestProvider.AUTHORITY, TestProvider.MODE);
+
+ // test for empty database at setup time
+ checkOpenCursorCount(0);
+ }
+
+ /**
+ * Simple test to see if we can instantiate the whole mess.
+ */
+ public void testSetup() {
+ assertTrue(true);
+ }
+
+ /**
+ * Simple test to see if we can write and read back a single query
+ */
+ public void testOneQuery() {
+ final String TEST_LINE1 = "test line 1";
+ final String TEST_LINE2 = "test line 2";
+ mSearchHelper.saveRecentQuery(TEST_LINE1, TEST_LINE2);
+
+ // make sure that there are is exactly one entry returned by a non-filtering cursor
+ checkOpenCursorCount(1);
+
+ // test non-filtering cursor for correct entry
+ checkResultCounts(null, 1, 1, TEST_LINE1, TEST_LINE2);
+
+ // test filtering cursor for correct entry
+ checkResultCounts(TEST_LINE1, 1, 1, TEST_LINE1, TEST_LINE2);
+ checkResultCounts(TEST_LINE2, 1, 1, TEST_LINE1, TEST_LINE2);
+
+ // test that a different filter returns zero results
+ checkResultCounts("bad filter", 0, 0, null, null);
+ }
+
+ /**
+ * Simple test to see if we can write and read back a diverse set of queries
+ */
+ public void testMixedQueries() {
+ // we'll make 10 queries named "query x" and 10 queries named "test x"
+ final String TEST_GROUP_1 = "query ";
+ final String TEST_GROUP_2 = "test ";
+ final String TEST_LINE2 = "line2 ";
+ final int GROUP_COUNT = 10;
+
+ writeEntries(GROUP_COUNT, TEST_GROUP_1, TEST_LINE2);
+ writeEntries(GROUP_COUNT, TEST_GROUP_2, TEST_LINE2);
+
+ // check counts
+ checkOpenCursorCount(2 * GROUP_COUNT);
+
+ // check that each query returns the right result counts
+ checkResultCounts(TEST_GROUP_1, GROUP_COUNT, GROUP_COUNT, null, null);
+ checkResultCounts(TEST_GROUP_2, GROUP_COUNT, GROUP_COUNT, null, null);
+ checkResultCounts(TEST_LINE2, 2 * GROUP_COUNT, 2 * GROUP_COUNT, null, null);
+ }
+
+ /**
+ * Test that the reordering code works properly. The most recently injected queries
+ * should replace existing queries and be sorted to the top of the list.
+ */
+ public void testReordering() {
+ // first we'll make 10 queries named "group1 x"
+ final int GROUP_1_COUNT = 10;
+ final String GROUP_1_QUERY = "group1 ";
+ final String GROUP_1_LINE2 = "line2 ";
+ writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
+
+ // check totals
+ checkOpenCursorCount(GROUP_1_COUNT);
+
+ // guarantee that group 1 has older timestamps
+ writeDelay();
+
+ // next we'll add 10 entries named "group2 x"
+ final int GROUP_2_COUNT = 10;
+ final String GROUP_2_QUERY = "group2 ";
+ final String GROUP_2_LINE2 = "line2 ";
+ writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
+
+ // check totals
+ checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
+
+ // guarantee that group 2 has older timestamps
+ writeDelay();
+
+ // now refresh 5 of the 10 from group 1
+ // change line2 so they can be more easily tracked
+ final int GROUP_3_COUNT = 5;
+ final String GROUP_3_QUERY = GROUP_1_QUERY;
+ final String GROUP_3_LINE2 = "refreshed ";
+ writeEntries(GROUP_3_COUNT, GROUP_3_QUERY, GROUP_3_LINE2);
+
+ // confirm that the total didn't change (those were replacements, not adds)
+ checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
+
+ // confirm that the are now 5 in group 1, 10 in group 2, and 5 in group 3
+ int newGroup1Count = GROUP_1_COUNT - GROUP_3_COUNT;
+ checkResultCounts(GROUP_1_QUERY, newGroup1Count, newGroup1Count, null, GROUP_1_LINE2);
+ checkResultCounts(GROUP_2_QUERY, GROUP_2_COUNT, GROUP_2_COUNT, null, null);
+ checkResultCounts(GROUP_3_QUERY, GROUP_3_COUNT, GROUP_3_COUNT, null, GROUP_3_LINE2);
+
+ // finally, spot check that the right groups are in the right places
+ // the ordering should be group 3 (newest), group 2, group 1 (oldest)
+ Cursor c = getQueryCursor(null);
+ int colQuery = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_QUERY);
+ int colDisplay1 = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ int colDisplay2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+
+ // Spot check the first and last expected entries of group 3
+ c.moveToPosition(0);
+ assertTrue("group 3 did not properly reorder to head of list",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_3_QUERY, GROUP_3_LINE2));
+ c.move(GROUP_3_COUNT - 1);
+ assertTrue("group 3 did not properly reorder to head of list",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_3_QUERY, GROUP_3_LINE2));
+
+ // Spot check the first and last expected entries of group 2
+ c.move(1);
+ assertTrue("group 2 not in expected position after reordering",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_2_QUERY, GROUP_2_LINE2));
+ c.move(GROUP_2_COUNT - 1);
+ assertTrue("group 2 not in expected position after reordering",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_2_QUERY, GROUP_2_LINE2));
+
+ // Spot check the first and last expected entries of group 1
+ c.move(1);
+ assertTrue("group 1 not in expected position after reordering",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_1_QUERY, GROUP_1_LINE2));
+ c.move(newGroup1Count - 1);
+ assertTrue("group 1 not in expected position after reordering",
+ checkRow(c, colQuery, colDisplay1, colDisplay2, GROUP_1_QUERY, GROUP_1_LINE2));
+
+ c.close();
+ }
+
+ /**
+ * Test that the pruning code works properly, The database should not go beyond 250 entries,
+ * and the oldest entries should always be discarded first.
+ *
+ * TODO: This is a slow test, do we have annotation for that?
+ */
+ public void testPruning() {
+ // first we'll make 50 queries named "group1 x"
+ final int GROUP_1_COUNT = 50;
+ final String GROUP_1_QUERY = "group1 ";
+ final String GROUP_1_LINE2 = "line2 ";
+ writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
+
+ // check totals
+ checkOpenCursorCount(GROUP_1_COUNT);
+
+ // guarantee that group 1 has older timestamps (and will be pruned first)
+ writeDelay();
+
+ // next we'll add 200 entries named "group2 x"
+ final int GROUP_2_COUNT = 200;
+ final String GROUP_2_QUERY = "group2 ";
+ final String GROUP_2_LINE2 = "line2 ";
+ writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
+
+ // check totals
+ checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
+
+ // Finally we'll add 10 more entries named "group3 x"
+ // These should push out 10 entries from group 1
+ final int GROUP_3_COUNT = 10;
+ final String GROUP_3_QUERY = "group3 ";
+ final String GROUP_3_LINE2 = "line2 ";
+ writeEntries(GROUP_3_COUNT, GROUP_3_QUERY, GROUP_3_LINE2);
+
+ // total should still be 250
+ checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
+
+ // there should be 40 group 1, 200 group 2, and 10 group 3
+ int group1NewCount = GROUP_1_COUNT-GROUP_3_COUNT;
+ checkResultCounts(GROUP_1_QUERY, group1NewCount, group1NewCount, null, null);
+ checkResultCounts(GROUP_2_QUERY, GROUP_2_COUNT, GROUP_2_COUNT, null, null);
+ checkResultCounts(GROUP_3_QUERY, GROUP_3_COUNT, GROUP_3_COUNT, null, null);
+ }
+
+ /**
+ * Test that the clear history code works properly.
+ */
+ public void testClear() {
+ // first we'll make 10 queries named "group1 x"
+ final int GROUP_1_COUNT = 10;
+ final String GROUP_1_QUERY = "group1 ";
+ final String GROUP_1_LINE2 = "line2 ";
+ writeEntries(GROUP_1_COUNT, GROUP_1_QUERY, GROUP_1_LINE2);
+
+ // next we'll add 10 entries named "group2 x"
+ final int GROUP_2_COUNT = 10;
+ final String GROUP_2_QUERY = "group2 ";
+ final String GROUP_2_LINE2 = "line2 ";
+ writeEntries(GROUP_2_COUNT, GROUP_2_QUERY, GROUP_2_LINE2);
+
+ // check totals
+ checkOpenCursorCount(GROUP_1_COUNT + GROUP_2_COUNT);
+
+ // delete all
+ mSearchHelper.clearHistory();
+
+ // check totals
+ checkOpenCursorCount(0);
+ }
+
+ /**
+ * Write a sequence of queries into the database, with incrementing counters in the strings.
+ */
+ private void writeEntries(int groupCount, String line1Base, String line2Base) {
+ for (int i = 0; i < groupCount; i++) {
+ final String line1 = line1Base + i;
+ final String line2 = line2Base + i;
+ mSearchHelper.saveRecentQuery(line1, line2);
+ }
+ }
+
+ /**
+ * A very slight delay to ensure that successive groups of queries in the DB cannot
+ * have the same timestamp.
+ */
+ private void writeDelay() {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ fail("Interrupted sleep.");
+ }
+ }
+
+ /**
+ * Access an "open" (no selection) suggestions cursor and confirm that it has the specified
+ * number of entries.
+ *
+ * @param expectCount The expected number of entries returned by the cursor.
+ */
+ private void checkOpenCursorCount(int expectCount) {
+ Cursor c = getQueryCursor(null);
+ assertEquals(expectCount, c.getCount());
+ c.close();
+ }
+
+ /**
+ * Set up a filter cursor and then scan it for specific results.
+ *
+ * @param queryString The query string to apply.
+ * @param minRows The minimum number of matching rows that must be found.
+ * @param maxRows The maximum number of matching rows that must be found.
+ * @param matchDisplay1 If non-null, must match DISPLAY1 column if row counts as match
+ * @param matchDisplay2 If non-null, must match DISPLAY2 column if row counts as match
+ */
+ private void checkResultCounts(String queryString, int minRows, int maxRows,
+ String matchDisplay1, String matchDisplay2) {
+
+ // get the cursor and apply sanity checks to result
+ Cursor c = getQueryCursor(queryString);
+ assertNotNull(c);
+ assertTrue("Insufficient rows in filtered cursor", c.getCount() >= minRows);
+
+ // look for minimum set of columns (note, display2 is optional)
+ int colQuery = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_QUERY);
+ int colDisplay1 = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
+ int colDisplay2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
+
+ // now loop through rows and look for desired rows
+ int foundRows = 0;
+ c.moveToFirst();
+ while (!c.isAfterLast()) {
+ if (checkRow(c, colQuery, colDisplay1, colDisplay2, matchDisplay1, matchDisplay2)) {
+ foundRows++;
+ }
+ c.moveToNext();
+ }
+
+ // now check the results
+ assertTrue(minRows <= foundRows);
+ assertTrue(foundRows <= maxRows);
+
+ c.close();
+ }
+
+ /**
+ * Check a single row for equality with target strings.
+ *
+ * @param c The cursor, already moved to the row
+ * @param colQuery The column # containing the query. The query must match display1.
+ * @param colDisp1 The column # containing display line 1.
+ * @param colDisp2 The column # containing display line 2, or -1 if no column
+ * @param matchDisplay1 If non-null, this must be the prefix of display1
+ * @param matchDisplay2 If non-null, this must be the prefix of display2
+ * @return Returns true if the row is a "match"
+ */
+ private boolean checkRow(Cursor c, int colQuery, int colDisp1, int colDisp2,
+ String matchDisplay1, String matchDisplay2) {
+ // Get the data from the row
+ String query = c.getString(colQuery);
+ String display1 = c.getString(colDisp1);
+ String display2 = (colDisp2 >= 0) ? c.getString(colDisp2) : null;
+
+ assertEquals(query, display1);
+ boolean result = true;
+ if (matchDisplay1 != null) {
+ result = result && (display1 != null) && display1.startsWith(matchDisplay1);
+ }
+ if (matchDisplay2 != null) {
+ result = result && (display2 != null) && display2.startsWith(matchDisplay2);
+ }
+
+ return result;
+ }
+
+ /**
+ * Generate a query cursor in a manner like the search dialog would.
+ *
+ * @param queryString The search string, or, null for "all"
+ * @return Returns a cursor, or null if there was some problem. Be sure to close the cursor
+ * when done with it.
+ */
+ private Cursor getQueryCursor(String queryString) {
+ ContentResolver cr = getMockContext().getContentResolver();
+
+ String uriStr = "content://" + TestProvider.AUTHORITY +
+ '/' + SearchManager.SUGGEST_URI_PATH_QUERY;
+ Uri contentUri = Uri.parse(uriStr);
+
+ String[] selArgs = new String[] {queryString};
+
+ Cursor c = cr.query(contentUri, null, null, selArgs, null);
+
+ assertNotNull(c);
+ return c;
+ }
+}
diff --git a/core/tests/coretests/src/android/content/SyncQueueTest.java b/core/tests/coretests/src/android/content/SyncQueueTest.java
new file mode 100644
index 0000000..1da59d1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/SyncQueueTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContext;
+import android.test.mock.MockContentResolver;
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+public class SyncQueueTest extends AndroidTestCase {
+ private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
+ private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
+ private static final String AUTHORITY1 = "test.authority1";
+ private static final String AUTHORITY2 = "test.authority2";
+ private static final String AUTHORITY3 = "test.authority3";
+
+ private SyncStorageEngine mSettings;
+ private SyncQueue mSyncQueue;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockContentResolver mockResolver = new MockContentResolver();
+ mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
+ mSyncQueue = new SyncQueue(mSettings);
+ }
+
+ public void testSyncQueueOrder() throws Exception {
+ final SyncOperation op1 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+ final SyncOperation op2 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+ final SyncOperation op3 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+ final SyncOperation op4 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
+ final SyncOperation op5 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+ final SyncOperation op6 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+ op6.expedited = true;
+
+ mSyncQueue.add(op1);
+ mSyncQueue.add(op2);
+ mSyncQueue.add(op3);
+ mSyncQueue.add(op4);
+ mSyncQueue.add(op5);
+ mSyncQueue.add(op6);
+
+ long now = SystemClock.elapsedRealtime() + 200;
+
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op6);
+
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op1);
+
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op4);
+
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op5);
+
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op2);
+
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op3);
+ }
+
+ public void testOrderWithBackoff() throws Exception {
+ final SyncOperation op1 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+ final SyncOperation op2 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+ final SyncOperation op3 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+ final SyncOperation op4 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
+ final SyncOperation op5 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+ final SyncOperation op6 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+ op6.expedited = true;
+
+ mSyncQueue.add(op1);
+ mSyncQueue.add(op2);
+ mSyncQueue.add(op3);
+ mSyncQueue.add(op4);
+ mSyncQueue.add(op5);
+ mSyncQueue.add(op6);
+
+ long now = SystemClock.elapsedRealtime() + 200;
+
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op6);
+
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op1);
+
+ mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
+
+ mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
+
+ mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
+
+ mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op4);
+
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op5);
+
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op2);
+
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now).first);
+ mSyncQueue.remove(op3);
+ }
+
+ Bundle newTestBundle(String val) {
+ Bundle bundle = new Bundle();
+ bundle.putString("test", val);
+ return bundle;
+ }
+
+ static class TestContext extends ContextWrapper {
+ ContentResolver mResolver;
+
+ public TestContext(ContentResolver resolver, Context realContext) {
+ super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+ mResolver = resolver;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
new file mode 100644
index 0000000..f840512
--- /dev/null
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import com.android.internal.os.AtomicFile;
+
+import android.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.mock.MockContext;
+import android.test.mock.MockContentResolver;
+import android.accounts.Account;
+import android.os.Bundle;
+
+import java.util.List;
+import java.io.File;
+import java.io.FileOutputStream;
+
+public class SyncStorageEngineTest extends AndroidTestCase {
+
+ /**
+ * Test that we handle the case of a history row being old enough to purge before the
+ * correcponding sync is finished. This can happen if the clock changes while we are syncing.
+ *
+ */
+ @SmallTest
+ public void testPurgeActiveSync() throws Exception {
+ final Account account = new Account("a@example.com", "example.type");
+ final String authority = "testprovider";
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ long time0 = 1000;
+ long historyId = engine.insertStartSyncEvent(
+ account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
+ long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
+ engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
+ }
+
+ /**
+ * Test that we can create, remove and retrieve periodic syncs
+ */
+ @SmallTest
+ public void testPeriodics() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority = "testprovider";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
+ PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, authority);
+ removePeriodicSyncs(engine, account2, authority);
+
+ // this should add two distinct periodic syncs for account1 and one for account2
+ engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority);
+
+ assertEquals(2, syncs.size());
+
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync3, syncs.get(1));
+
+ engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras);
+
+ syncs = engine.getPeriodicSyncs(account1, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account2, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync4, syncs.get(0));
+ }
+
+ private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) {
+ engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority));
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority);
+ for (PeriodicSync sync : syncs) {
+ engine.removePeriodicSync(sync.account, sync.authority, sync.extras);
+ }
+ }
+
+ @SmallTest
+ public void testAuthorityPersistence() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority1 = "testprovider1";
+ final String authority2 = "testprovider2";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ extras2.putLong("b", 2);
+ extras2.putInt("c", 1);
+ extras2.putBoolean("d", true);
+ extras2.putDouble("e", 1.2);
+ extras2.putFloat("f", 4.5f);
+ extras2.putParcelable("g", account1);
+ final int period1 = 200;
+ final int period2 = 1000;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
+ PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
+ PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+
+ removePeriodicSyncs(engine, account1, authority1);
+ removePeriodicSyncs(engine, account2, authority1);
+ removePeriodicSyncs(engine, account1, authority2);
+ removePeriodicSyncs(engine, account2, authority2);
+
+ engine.setMasterSyncAutomatically(false);
+
+ engine.setIsSyncable(account1, authority1, 1);
+ engine.setSyncAutomatically(account1, authority1, true);
+
+ engine.setIsSyncable(account2, authority1, 1);
+ engine.setSyncAutomatically(account2, authority1, true);
+
+ engine.setIsSyncable(account1, authority2, 1);
+ engine.setSyncAutomatically(account1, authority2, false);
+
+ engine.setIsSyncable(account2, authority2, 0);
+ engine.setSyncAutomatically(account2, authority2, true);
+
+ engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
+ engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
+ engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+ engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period);
+
+ engine.writeAllState();
+ engine.clearAndReadState();
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1);
+ assertEquals(2, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync2, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account1, authority2);
+ assertEquals(2, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+ assertEquals(sync4, syncs.get(1));
+
+ syncs = engine.getPeriodicSyncs(account2, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync5, syncs.get(0));
+
+ assertEquals(true, engine.getSyncAutomatically(account1, authority1));
+ assertEquals(true, engine.getSyncAutomatically(account2, authority1));
+ assertEquals(false, engine.getSyncAutomatically(account1, authority2));
+ assertEquals(true, engine.getSyncAutomatically(account2, authority2));
+
+ assertEquals(1, engine.getIsSyncable(account1, authority1));
+ assertEquals(1, engine.getIsSyncable(account2, authority1));
+ assertEquals(1, engine.getIsSyncable(account1, authority2));
+ assertEquals(0, engine.getIsSyncable(account2, authority2));
+ }
+
+ @SmallTest
+ public void testAuthorityParsing() throws Exception {
+ final Account account = new Account("account1", "type1");
+ final String authority1 = "auth1";
+ final String authority2 = "auth2";
+ final String authority3 = "auth3";
+ final Bundle extras = new Bundle();
+ PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, (long) (60 * 60 * 24));
+ PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, 1000);
+ PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, 1000);
+ PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, 1000);
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, authority1);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, authority2);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, authority3);
+ assertEquals(0, syncs.size());
+
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync1s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3s, syncs.get(0));
+ }
+
+ @SmallTest
+ public void testAuthorityRenaming() throws Exception {
+ final Account account1 = new Account("acc1", "type1");
+ final Account account2 = new Account("acc2", "type2");
+ final String authorityContacts = "contacts";
+ final String authorityCalendar = "calendar";
+ final String authorityOther = "other";
+ final String authorityContactsNew = "com.android.contacts";
+ final String authorityCalendarNew = "com.android.calendar";
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" account=\"acc1\" type=\"type1\" authority=\"contacts\" />\n"
+ + "<authority id=\"1\" account=\"acc1\" type=\"type1\" authority=\"calendar\" />\n"
+ + "<authority id=\"2\" account=\"acc1\" type=\"type1\" authority=\"other\" />\n"
+ + "<authority id=\"3\" account=\"acc2\" type=\"type2\" authority=\"contacts\" />\n"
+ + "<authority id=\"4\" account=\"acc2\" type=\"type2\" authority=\"calendar\" />\n"
+ + "<authority id=\"5\" account=\"acc2\" type=\"type2\" authority=\"other\" />\n"
+ + "<authority id=\"6\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ + " authority=\"com.android.calendar\" />\n"
+ + "<authority id=\"7\" account=\"acc2\" type=\"type2\" enabled=\"false\""
+ + " authority=\"com.android.contacts\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ assertEquals(false, engine.getSyncAutomatically(account1, authorityContacts));
+ assertEquals(false, engine.getSyncAutomatically(account1, authorityCalendar));
+ assertEquals(true, engine.getSyncAutomatically(account1, authorityOther));
+ assertEquals(true, engine.getSyncAutomatically(account1, authorityContactsNew));
+ assertEquals(true, engine.getSyncAutomatically(account1, authorityCalendarNew));
+
+ assertEquals(false, engine.getSyncAutomatically(account2, authorityContacts));
+ assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendar));
+ assertEquals(true, engine.getSyncAutomatically(account2, authorityOther));
+ assertEquals(false, engine.getSyncAutomatically(account2, authorityContactsNew));
+ assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendarNew));
+ }
+
+ @SmallTest
+ public void testSyncableMigration() throws Exception {
+ final Account account = new Account("acc", "type");
+
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts>\n"
+ + "<authority id=\"0\" account=\"acc\" authority=\"other1\" />\n"
+ + "<authority id=\"1\" account=\"acc\" type=\"type\" authority=\"other2\" />\n"
+ + "<authority id=\"2\" account=\"acc\" type=\"type\" syncable=\"false\""
+ + " authority=\"other3\" />\n"
+ + "<authority id=\"3\" account=\"acc\" type=\"type\" syncable=\"true\""
+ + " authority=\"other4\" />\n"
+ + "</accounts>\n").getBytes();
+
+ File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ assertEquals(-1, engine.getIsSyncable(account, "other1"));
+ assertEquals(1, engine.getIsSyncable(account, "other2"));
+ assertEquals(0, engine.getIsSyncable(account, "other3"));
+ assertEquals(1, engine.getIsSyncable(account, "other4"));
+ }
+}
+
+class TestContext extends ContextWrapper {
+
+ ContentResolver mResolver;
+
+ private final Context mRealContext;
+
+ public TestContext(ContentResolver resolver, Context realContext) {
+ super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+ mRealContext = realContext;
+ mResolver = resolver;
+ }
+
+ @Override
+ public File getFilesDir() {
+ return mRealContext.getFilesDir();
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
new file mode 100755
index 0000000..dbb10b1
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
@@ -0,0 +1,745 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageStats;
+import android.content.pm.IPackageManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+
+public class AppCacheTest extends AndroidTestCase {
+ private static final boolean localLOGV = false;
+ public static final String TAG="AppCacheTest";
+ public final long MAX_WAIT_TIME=60*1000;
+ public final long WAIT_TIME_INCR=10*1000;
+ private static final int THRESHOLD=5;
+ private static final int ACTUAL_THRESHOLD=10;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if(localLOGV) Log.i(TAG, "Cleaning up cache directory first");
+ cleanUpCacheDirectory();
+ }
+
+ void cleanUpDirectory(File pDir, String dirName) {
+ File testDir = new File(pDir, dirName);
+ if(!testDir.exists()) {
+ return;
+ }
+ String fList[] = testDir.list();
+ for(int i = 0; i < fList.length; i++) {
+ File file = new File(testDir, fList[i]);
+ if(file.isDirectory()) {
+ cleanUpDirectory(testDir, fList[i]);
+ } else {
+ file.delete();
+ }
+ }
+ testDir.delete();
+ }
+
+ void cleanUpCacheDirectory() {
+ File testDir = mContext.getCacheDir();
+ if(!testDir.exists()) {
+ return;
+ }
+
+ String fList[] = testDir.list();
+ if(fList == null) {
+ testDir.delete();
+ return;
+ }
+ for(int i = 0; i < fList.length; i++) {
+ File file = new File(testDir, fList[i]);
+ if(file.isDirectory()) {
+ cleanUpDirectory(testDir, fList[i]);
+ } else {
+ file.delete();
+ }
+ }
+ }
+
+ @SmallTest
+ public void testDeleteAllCacheFiles() {
+ String testName="testDeleteAllCacheFiles";
+ cleanUpCacheDirectory();
+ }
+
+ void failStr(String errMsg) {
+ Log.w(TAG, "errMsg="+errMsg);
+ fail(errMsg);
+ }
+ void failStr(Exception e) {
+ Log.w(TAG, "e.getMessage="+e.getMessage());
+ Log.w(TAG, "e="+e);
+ }
+ long getFreeStorageBlks(StatFs st) {
+ st.restat("/data");
+ return st.getFreeBlocks();
+ }
+
+ long getFreeStorageSize(StatFs st) {
+ st.restat("/data");
+ return (st.getFreeBlocks()*st.getBlockSize());
+ }
+ @LargeTest
+ public void testFreeApplicationCacheAllFiles() throws Exception {
+ boolean TRACKING = true;
+ StatFs st = new StatFs("/data");
+ long blks1 = getFreeStorageBlks(st);
+ long availableMem = getFreeStorageSize(st);
+ File cacheDir = mContext.getCacheDir();
+ assertNotNull(cacheDir);
+ createTestFiles1(cacheDir, "testtmpdir", 5);
+ long blks2 = getFreeStorageBlks(st);
+ if(localLOGV || TRACKING) Log.i(TAG, "blk1="+blks1+", blks2="+blks2);
+ //this should free up the test files that were created earlier
+ invokePMFreeApplicationCache(availableMem);
+ long blks3 = getFreeStorageBlks(st);
+ if(localLOGV || TRACKING) Log.i(TAG, "blks3="+blks3);
+ verifyTestFiles1(cacheDir, "testtmpdir", 5);
+ }
+
+ public void testFreeApplicationCacheSomeFiles() throws Exception {
+ StatFs st = new StatFs("/data");
+ long blks1 = getFreeStorageBlks(st);
+ File cacheDir = mContext.getCacheDir();
+ assertNotNull(cacheDir);
+ createTestFiles1(cacheDir, "testtmpdir", 5);
+ long blks2 = getFreeStorageBlks(st);
+ Log.i(TAG, "blk1="+blks1+", blks2="+blks2);
+ long diff = (blks1-blks2-2);
+ assertTrue(invokePMFreeApplicationCache(diff*st.getBlockSize()));
+ long blks3 = getFreeStorageBlks(st);
+ //blks3 should be greater than blks2 and less than blks1
+ if(!((blks3 <= blks1) && (blks3 >= blks2))) {
+ failStr("Expected "+(blks1-blks2)+" number of blocks to be freed but freed only "
+ +(blks1-blks3));
+ }
+ }
+
+ /**
+ * This method opens an output file writes to it, opens the same file as an input
+ * stream, reads the contents and verifies the data that was written earlier can be read
+ */
+ public void openOutFileInAppFilesDir(File pFile, String pFileOut) {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(pFile);
+ } catch (FileNotFoundException e1) {
+ failStr("Error when opening file "+e1);
+ return;
+ }
+ try {
+ fos.write(pFileOut.getBytes());
+ fos.close();
+ } catch (FileNotFoundException e) {
+ failStr(e.getMessage());
+ } catch (IOException e) {
+ failStr(e.getMessage());
+ }
+ int count = pFileOut.getBytes().length;
+ byte[] buffer = new byte[count];
+ try {
+ FileInputStream fis = new FileInputStream(pFile);
+ fis.read(buffer, 0, count);
+ fis.close();
+ } catch (FileNotFoundException e) {
+ failStr("Failed when verifing output opening file "+e.getMessage());
+ } catch (IOException e) {
+ failStr("Failed when verifying output, reading from written file "+e);
+ }
+ String str = new String(buffer);
+ assertEquals(str, pFileOut);
+ }
+
+ /*
+ * This test case verifies that output written to a file
+ * using Context.openFileOutput has executed successfully.
+ * The operation is verified by invoking Context.openFileInput
+ */
+ @MediumTest
+ public void testAppFilesCreateFile() {
+ String fileName = "testFile1.txt";
+ String fileOut = "abcdefghijklmnopqrstuvwxyz";
+ Context con = super.getContext();
+ try {
+ FileOutputStream fos = con.openFileOutput(fileName, Context.MODE_PRIVATE);
+ fos.close();
+ } catch (FileNotFoundException e) {
+ failStr(e);
+ } catch (IOException e) {
+ failStr(e);
+ }
+ }
+
+ @SmallTest
+ public void testAppCacheCreateFile() {
+ String fileName = "testFile1.txt";
+ String fileOut = "abcdefghijklmnopqrstuvwxyz";
+ Context con = super.getContext();
+ File file = new File(con.getCacheDir(), fileName);
+ openOutFileInAppFilesDir(file, fileOut);
+ cleanUpCacheDirectory();
+ }
+
+ @MediumTest
+ public void testAppCreateCacheFiles() {
+ File cacheDir = mContext.getCacheDir();
+ String testDirName = "testtmp";
+ File testTmpDir = new File(cacheDir, testDirName);
+ testTmpDir.mkdir();
+ int numDirs = 3;
+ File fileArr[] = new File[numDirs];
+ for(int i = 0; i < numDirs; i++) {
+ fileArr[i] = new File(testTmpDir, "dir"+(i+1));
+ fileArr[i].mkdir();
+ }
+ byte buffer[] = getBuffer();
+ Log.i(TAG, "Size of bufer="+buffer.length);
+ for(int i = 0; i < numDirs; i++) {
+ for(int j = 1; j <= (i); j++) {
+ File file1 = new File(fileArr[i], "testFile"+j+".txt");
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file1);
+ for(int k = 1; k < 10; k++) {
+ fos.write(buffer);
+ }
+ Log.i(TAG, "wrote 10K bytes to "+file1);
+ fos.close();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Excetion ="+e);
+ fail("Error when creating outputstream "+e);
+ } catch(IOException e) {
+ Log.i(TAG, "Excetion ="+e);
+ fail("Error when writing output "+e);
+ }
+ }
+ }
+ }
+
+ byte[] getBuffer() {
+ String sbuffer = "a";
+ for(int i = 0; i < 10; i++) {
+ sbuffer += sbuffer;
+ }
+ return sbuffer.getBytes();
+ }
+
+ long getFileNumBlocks(long fileSize, int blkSize) {
+ long ret = fileSize/blkSize;
+ if(ret*blkSize < fileSize) {
+ ret++;
+ }
+ return ret;
+ }
+
+ //@LargeTest
+ public void testAppCacheClear() {
+ String dataDir="/data/data";
+ StatFs st = new StatFs(dataDir);
+ int blkSize = st.getBlockSize();
+ int totBlks = st.getBlockCount();
+ long availableBlks = st.getFreeBlocks();
+ long thresholdBlks = (totBlks*THRESHOLD)/100;
+ String testDirName = "testdir";
+ //create directory in cache
+ File testDir = new File(mContext.getCacheDir(), testDirName);
+ testDir.mkdirs();
+ byte[] buffer = getBuffer();
+ int i = 1;
+ if(localLOGV) Log.i(TAG, "availableBlks="+availableBlks+", thresholdBlks="+thresholdBlks);
+ long createdFileBlks = 0;
+ int imax = 300;
+ while((availableBlks > thresholdBlks) &&(i < imax)) {
+ File testFile = new File(testDir, "testFile"+i+".txt");
+ if(localLOGV) Log.i(TAG, "Creating "+i+"th test file "+testFile);
+ int jmax = i;
+ i++;
+ FileOutputStream fos;
+ try {
+ fos = new FileOutputStream(testFile);
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Failed creating test file:"+testFile);
+ continue;
+ }
+ boolean err = false;
+ for(int j = 1; j <= jmax;j++) {
+ try {
+ fos.write(buffer);
+ } catch (IOException e) {
+ Log.i(TAG, "Failed to write to file:"+testFile);
+ err = true;
+ }
+ }
+ try {
+ fos.close();
+ } catch (IOException e) {
+ Log.i(TAG, "Failed closing file:"+testFile);
+ }
+ if(err) {
+ continue;
+ }
+ createdFileBlks += getFileNumBlocks(testFile.length(), blkSize);
+ st.restat(dataDir);
+ availableBlks = st.getFreeBlocks();
+ }
+ st.restat(dataDir);
+ long availableBytes = st.getFreeBlocks()*blkSize;
+ long shouldFree = (ACTUAL_THRESHOLD-THRESHOLD)*totBlks;
+ //would have run out of memory
+ //wait for some time and confirm cache is deleted
+ try {
+ Log.i(TAG, "Sleeping for 2 minutes...");
+ Thread.sleep(2*60*1000);
+ } catch (InterruptedException e) {
+ fail("Exception when sleeping "+e);
+ }
+ boolean removedFlag = false;
+ long existingFileBlks = 0;
+ for(int k = 1; k <i; k++) {
+ File testFile = new File(testDir, "testFile"+k+".txt");
+ if(!testFile.exists()) {
+ removedFlag = true;
+ if(localLOGV) Log.i(TAG, testFile+" removed");
+ } else {
+ existingFileBlks += getFileNumBlocks(testFile.length(), blkSize);
+ }
+ }
+ if(localLOGV) Log.i(TAG, "createdFileBlks="+createdFileBlks+
+ ", existingFileBlks="+existingFileBlks);
+ long fileSize = createdFileBlks-existingFileBlks;
+ //verify fileSize number of bytes have been cleared from cache
+ if(localLOGV) Log.i(TAG, "deletedFileBlks="+fileSize+" shouldFreeBlks="+shouldFree);
+ if((fileSize > (shouldFree-blkSize) && (fileSize < (shouldFree+blkSize)))) {
+ Log.i(TAG, "passed");
+ }
+ assertTrue(removedFlag);
+ }
+
+ //createTestFiles(new File(super.getContext().getCacheDir(), "testtmp", "dir", 3)
+ void createTestFiles1(File cacheDir, String testFilePrefix, int numTestFiles) {
+ byte buffer[] = getBuffer();
+ for(int i = 0; i < numTestFiles; i++) {
+ File file1 = new File(cacheDir, testFilePrefix+i+".txt");
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file1);
+ for(int k = 1; k < 10; k++) {
+ fos.write(buffer);
+ }
+ fos.close();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Exception ="+e);
+ fail("Error when creating outputstream "+e);
+ } catch(IOException e) {
+ Log.i(TAG, "Exception ="+e);
+ fail("Error when writing output "+e);
+ }
+ try {
+ //introduce sleep for 1 s to avoid common time stamps for files being created
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ fail("Exception when sleeping "+e);
+ }
+ }
+ }
+
+ void verifyTestFiles1(File cacheDir, String testFilePrefix, int numTestFiles) {
+ for(int i = 0; i < numTestFiles; i++) {
+ File file1 = new File(cacheDir, testFilePrefix+i+".txt");
+ if(file1.exists()) {
+ fail("file:"+file1+" should not exist");
+ }
+ }
+ }
+
+ void createTestFiles2(File cacheDir, String rootTestDirName, String subDirPrefix, int numDirs, String testFilePrefix) {
+ Context con = super.getContext();
+ File testTmpDir = new File(cacheDir, rootTestDirName);
+ testTmpDir.mkdir();
+ File fileArr[] = new File[numDirs];
+ for(int i = 0; i < numDirs; i++) {
+ fileArr[i] = new File(testTmpDir, subDirPrefix+(i+1));
+ fileArr[i].mkdir();
+ }
+ byte buffer[] = getBuffer();
+ for(int i = 0; i < numDirs; i++) {
+ for(int j = 1; j <= (i); j++) {
+ File file1 = new File(fileArr[i], testFilePrefix+j+".txt");
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file1);
+ for(int k = 1; k < 10; k++) {
+ fos.write(buffer);
+ }
+ fos.close();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "Exception ="+e);
+ fail("Error when creating outputstream "+e);
+ } catch(IOException e) {
+ Log.i(TAG, "Exception ="+e);
+ fail("Error when writing output "+e);
+ }
+ try {
+ //introduce sleep for 10 ms to avoid common time stamps for files being created
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ fail("Exception when sleeping "+e);
+ }
+ }
+ }
+ }
+
+ class PackageDataObserver extends IPackageDataObserver.Stub {
+ public boolean retValue = false;
+ private boolean doneFlag = false;
+ public void onRemoveCompleted(String packageName, boolean succeeded)
+ throws RemoteException {
+ synchronized(this) {
+ retValue = succeeded;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ IPackageManager getPm() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+
+ boolean invokePMDeleteAppCacheFiles() throws Exception {
+ try {
+ String packageName = mContext.getPackageName();
+ PackageDataObserver observer = new PackageDataObserver();
+ //wait on observer
+ synchronized(observer) {
+ getPm().deleteApplicationCacheFiles(packageName, observer);
+ long waitTime = 0;
+ while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
+ }
+ }
+ return observer.retValue;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+ return false;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "InterruptedException :"+e);
+ return false;
+ }
+ }
+
+ boolean invokePMFreeApplicationCache(long idealStorageSize) throws Exception {
+ try {
+ String packageName = mContext.getPackageName();
+ PackageDataObserver observer = new PackageDataObserver();
+ //wait on observer
+ synchronized(observer) {
+ getPm().freeStorageAndNotify(idealStorageSize, observer);
+ long waitTime = 0;
+ while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
+ }
+ }
+ return observer.retValue;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+ return false;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "InterruptedException :"+e);
+ return false;
+ }
+ }
+
+ boolean invokePMFreeStorage(long idealStorageSize, FreeStorageReceiver r,
+ PendingIntent pi) throws Exception {
+ try {
+ // Spin lock waiting for call back
+ synchronized(r) {
+ getPm().freeStorage(idealStorageSize, pi.getIntentSender());
+ long waitTime = 0;
+ while(!r.isDone() && (waitTime < MAX_WAIT_TIME)) {
+ r.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!r.isDone()) {
+ throw new Exception("timed out waiting for call back from PendingIntent");
+ }
+ }
+ return r.getResultCode() == 1;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+ return false;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "InterruptedException :"+e);
+ return false;
+ }
+ }
+
+ @LargeTest
+ public void testDeleteAppCacheFiles() throws Exception {
+ String testName="testDeleteAppCacheFiles";
+ File cacheDir = mContext.getCacheDir();
+ createTestFiles1(cacheDir, "testtmpdir", 5);
+ assertTrue(invokePMDeleteAppCacheFiles());
+ //confirm files dont exist
+ verifyTestFiles1(cacheDir, "testtmpdir", 5);
+ }
+
+ class PackageStatsObserver extends IPackageStatsObserver.Stub {
+ public boolean retValue = false;
+ public PackageStats stats;
+ private boolean doneFlag = false;
+
+ public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
+ throws RemoteException {
+ synchronized(this) {
+ retValue = succeeded;
+ stats = pStats;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ public PackageStats invokePMGetPackageSizeInfo() throws Exception {
+ try {
+ String packageName = mContext.getPackageName();
+ PackageStatsObserver observer = new PackageStatsObserver();
+ //wait on observer
+ synchronized(observer) {
+ getPm().getPackageSizeInfo(packageName, observer);
+ long waitTime = 0;
+ while((!observer.isDone()) || (waitTime > MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for PackageStatsObserver.onGetStatsCompleted");
+ }
+ }
+ if(localLOGV) Log.i(TAG, "OBSERVER RET VALUES code="+observer.stats.codeSize+
+ ", data="+observer.stats.dataSize+", cache="+observer.stats.cacheSize);
+ return observer.stats;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+ return null;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "InterruptedException :"+e);
+ return null;
+ }
+ }
+
+ @SmallTest
+ public void testGetPackageSizeInfo() throws Exception {
+ String testName="testGetPackageSizeInfo";
+ PackageStats stats = invokePMGetPackageSizeInfo();
+ assertTrue(stats!=null);
+ //confirm result
+ if(localLOGV) Log.i(TAG, "code="+stats.codeSize+", data="+stats.dataSize+
+ ", cache="+stats.cacheSize);
+ }
+
+ @SmallTest
+ public void testGetSystemSharedLibraryNames() throws Exception {
+ try {
+ String[] sharedLibs = getPm().getSystemSharedLibraryNames();
+ if (localLOGV) {
+ for (String str : sharedLibs) {
+ Log.i(TAG, str);
+ }
+ }
+ } catch (RemoteException e) {
+ fail("Failed invoking getSystemSharedLibraryNames with exception:" + e);
+ }
+ }
+
+ class FreeStorageReceiver extends BroadcastReceiver {
+ public static final String ACTION_FREE = "com.android.unit_tests.testcallback";
+ private boolean doneFlag = false;
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if(intent.getAction().equalsIgnoreCase(ACTION_FREE)) {
+ if (localLOGV) Log.i(TAG, "Got notification: clear cache succeeded "+getResultCode());
+ synchronized (this) {
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+ }
+ }
+
+ @SmallTest
+ public void testFreeStorage() throws Exception {
+ boolean TRACKING = true;
+ StatFs st = new StatFs("/data");
+ long blks1 = getFreeStorageBlks(st);
+ if(localLOGV || TRACKING) Log.i(TAG, "Available free blocks="+blks1);
+ long availableMem = getFreeStorageSize(st);
+ File cacheDir = mContext.getCacheDir();
+ assertNotNull(cacheDir);
+ createTestFiles1(cacheDir, "testtmpdir", 5);
+ long blks2 = getFreeStorageBlks(st);
+ if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after writing test files in application cache="+blks2);
+ // Create receiver and register it
+ FreeStorageReceiver receiver = new FreeStorageReceiver();
+ mContext.registerReceiver(receiver, new IntentFilter(FreeStorageReceiver.ACTION_FREE));
+ PendingIntent pi = PendingIntent.getBroadcast(mContext,
+ 0, new Intent(FreeStorageReceiver.ACTION_FREE), 0);
+ // Invoke PackageManager api
+ invokePMFreeStorage(availableMem, receiver, pi);
+ long blks3 = getFreeStorageBlks(st);
+ if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after freeing cache"+blks3);
+ assertEquals(receiver.getResultCode(), 1);
+ mContext.unregisterReceiver(receiver);
+ // Verify result
+ verifyTestFiles1(cacheDir, "testtmpdir", 5);
+ }
+
+ /* utility method used to create observer and check async call back from PackageManager.
+ * ClearApplicationUserData
+ */
+ boolean invokePMClearApplicationUserData() throws Exception {
+ try {
+ String packageName = mContext.getPackageName();
+ PackageDataObserver observer = new PackageDataObserver();
+ //wait on observer
+ synchronized(observer) {
+ getPm().clearApplicationUserData(packageName, observer);
+ long waitTime = 0;
+ while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
+ }
+ }
+ return observer.retValue;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+ return false;
+ } catch (InterruptedException e) {
+ Log.w(TAG, "InterruptedException :"+e);
+ return false;
+ }
+ }
+
+ void verifyUserDataCleared(File pDir) {
+ if(localLOGV) Log.i(TAG, "Verifying "+pDir);
+ if(pDir == null) {
+ return;
+ }
+ String fileList[] = pDir.list();
+ if(fileList == null) {
+ return;
+ }
+ int imax = fileList.length;
+ //look recursively in user data dir
+ for(int i = 0; i < imax; i++) {
+ if(localLOGV) Log.i(TAG, "Found entry "+fileList[i]+ "in "+pDir);
+ if("lib".equalsIgnoreCase(fileList[i])) {
+ if(localLOGV) Log.i(TAG, "Ignoring lib directory");
+ continue;
+ }
+ fail(pDir+" should be empty or contain only lib subdirectory. Found "+fileList[i]);
+ }
+ }
+
+ File getDataDir() {
+ try {
+ ApplicationInfo appInfo = getPm().getApplicationInfo(mContext.getPackageName(), 0);
+ return new File(appInfo.dataDir);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Pacakge manager dead", e);
+ }
+ }
+
+ @LargeTest
+ public void testClearApplicationUserDataWithTestData() throws Exception {
+ File cacheDir = mContext.getCacheDir();
+ createTestFiles1(cacheDir, "testtmpdir", 5);
+ if(localLOGV) {
+ Log.i(TAG, "Created test data Waiting for 60seconds before continuing");
+ Thread.sleep(60*1000);
+ }
+ assertTrue(invokePMClearApplicationUserData());
+ //confirm files dont exist
+ verifyUserDataCleared(getDataDir());
+ }
+
+ @SmallTest
+ public void testClearApplicationUserDataWithNoTestData() throws Exception {
+ assertTrue(invokePMClearApplicationUserData());
+ //confirm files dont exist
+ verifyUserDataCleared(getDataDir());
+ }
+
+ @LargeTest
+ public void testClearApplicationUserDataNoObserver() throws Exception {
+ getPm().clearApplicationUserData(mContext.getPackageName(), null);
+ //sleep for 1 minute
+ Thread.sleep(60*1000);
+ //confirm files dont exist
+ verifyUserDataCleared(getDataDir());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/content/pm/ComponentTest.java b/core/tests/coretests/src/android/content/pm/ComponentTest.java
new file mode 100644
index 0000000..ebfbd68
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ComponentTest.java
@@ -0,0 +1,738 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import com.android.frameworks.coretests.enabled_app.DisabledActivity;
+import com.android.frameworks.coretests.enabled_app.DisabledProvider;
+import com.android.frameworks.coretests.enabled_app.DisabledReceiver;
+import com.android.frameworks.coretests.enabled_app.DisabledService;
+import com.android.frameworks.coretests.enabled_app.EnabledActivity;
+import com.android.frameworks.coretests.enabled_app.EnabledProvider;
+import com.android.frameworks.coretests.enabled_app.EnabledReceiver;
+import com.android.frameworks.coretests.enabled_app.EnabledService;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.GET_DISABLED_COMPONENTS;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+/**
+ * Tests for disabling and enabling application components.
+ *
+ * Note: These tests are on the slow side. This is probably because most of the tests trigger the
+ * package settings file to get written out by the PackageManagerService. Better, more unit-y test
+ * would fix this.
+ */
+
+public class ComponentTest extends AndroidTestCase {
+
+ private PackageManager mPackageManager;
+ private Intent mDisabledActivityIntent;
+ private Intent mEnabledActivityIntent;
+ private Intent mDisabledServiceIntent;
+ private Intent mEnabledServiceIntent;
+ private Intent mDisabledReceiverIntent;
+ private Intent mEnabledReceiverIntent;
+ private Intent mDisabledAppEnabledActivityIntent;
+
+ private static final String ENABLED_PACKAGENAME =
+ "com.android.frameworks.coretests.enabled_app";
+ private static final String DISABLED_PACKAGENAME =
+ "com.android.frameworks.coretests.disabled_app";
+ private static final String DISABLED_ACTIVITY_CLASSNAME =
+ DisabledActivity.class.getName();
+ private static final ComponentName DISABLED_ACTIVITY_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, DISABLED_ACTIVITY_CLASSNAME);
+ private static final String ENABLED_ACTIVITY_CLASSNAME =
+ EnabledActivity.class.getName();
+ private static final ComponentName ENABLED_ACTIVITY_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, ENABLED_ACTIVITY_CLASSNAME);
+ private static final String DISABLED_SERVICE_CLASSNAME =
+ DisabledService.class.getName();
+ private static final ComponentName DISABLED_SERVICE_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, DISABLED_SERVICE_CLASSNAME);
+ private static final String DISABLED_PROVIDER_CLASSNAME =
+ DisabledProvider.class.getName();
+ private static final ComponentName DISABLED_PROVIDER_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, DISABLED_PROVIDER_CLASSNAME);
+ private static final String DISABLED_PROVIDER_NAME = DisabledProvider.class.getName();
+ private static final String ENABLED_SERVICE_CLASSNAME =
+ EnabledService.class.getName();
+ private static final ComponentName ENABLED_SERVICE_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, ENABLED_SERVICE_CLASSNAME);
+ private static final String DISABLED_RECEIVER_CLASSNAME =
+ DisabledReceiver.class.getName();
+ private static final ComponentName DISABLED_RECEIVER_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, DISABLED_RECEIVER_CLASSNAME);
+ private static final String ENABLED_RECEIVER_CLASSNAME =
+ EnabledReceiver.class.getName();
+ private static final ComponentName ENABLED_RECEIVER_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, ENABLED_RECEIVER_CLASSNAME);
+ private static final String ENABLED_PROVIDER_CLASSNAME =
+ EnabledProvider.class.getName();
+ private static final ComponentName ENABLED_PROVIDER_COMPONENTNAME =
+ new ComponentName(ENABLED_PACKAGENAME, ENABLED_PROVIDER_CLASSNAME);
+ private static final String ENABLED_PROVIDER_NAME = EnabledProvider.class.getName();
+ private static final String DISABLED_APP_ENABLED_ACTIVITY_CLASSNAME =
+ com.android.frameworks.coretests.disabled_app.EnabledActivity.class.getName();
+ private static final ComponentName DISABLED_APP_ENABLED_ACTIVITY_COMPONENTNAME =
+ new ComponentName(DISABLED_PACKAGENAME, DISABLED_APP_ENABLED_ACTIVITY_CLASSNAME);
+ private static final String TEST_CATEGORY =
+ "com.android.frameworks.coretests.enabled_app.TEST_CATEGORY";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPackageManager = mContext.getPackageManager();
+ mDisabledActivityIntent = new Intent();
+ mDisabledActivityIntent.setComponent(DISABLED_ACTIVITY_COMPONENTNAME);
+ mEnabledActivityIntent = new Intent();
+ mEnabledActivityIntent.setComponent(ENABLED_ACTIVITY_COMPONENTNAME);
+ mDisabledServiceIntent = new Intent();
+ mDisabledServiceIntent.setComponent(DISABLED_SERVICE_COMPONENTNAME);
+ mEnabledServiceIntent = new Intent();
+ mEnabledServiceIntent.setComponent(ENABLED_SERVICE_COMPONENTNAME);
+ mDisabledReceiverIntent = new Intent("android.intent.action.ENABLED_APP_DISABLED_RECEIVER");
+ mDisabledReceiverIntent.setComponent(DISABLED_RECEIVER_COMPONENTNAME);
+ mEnabledReceiverIntent = new Intent("android.intent.action.ENABLED_APP_ENABLED_RECEIVER");
+ mEnabledReceiverIntent.setComponent(ENABLED_RECEIVER_COMPONENTNAME);
+ mDisabledAppEnabledActivityIntent = new Intent();
+ mDisabledAppEnabledActivityIntent.setComponent(DISABLED_APP_ENABLED_ACTIVITY_COMPONENTNAME);
+ }
+
+ @SmallTest
+ public void testContextNotNull() throws Exception {
+ assertNotNull(mContext);
+ }
+
+ @MediumTest
+ public void testResolveDisabledActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveActivity(mDisabledActivityIntent, 0);
+ assertNull(info);
+
+ final ResolveInfo info2 = mPackageManager.resolveActivity(
+ mDisabledActivityIntent, GET_DISABLED_COMPONENTS);
+ assertNotNull(info2);
+ assertNotNull(info2.activityInfo);
+ assertFalse(info2.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testResolveEnabledActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveActivity(mEnabledActivityIntent, 0);
+ assertNotNull(info);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertTrue(info.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryDisabledActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentActivities(mDisabledActivityIntent, 0);
+ assertEquals(0, infoList.size());
+
+ final List<ResolveInfo> infoList2 =
+ mPackageManager.queryIntentActivities(mDisabledActivityIntent,
+ GET_DISABLED_COMPONENTS);
+ assertEquals(1, infoList2.size());
+ final ResolveInfo info = infoList2.get(0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertFalse(info.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryEnabledActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentActivities(mEnabledActivityIntent, 0);
+ assertEquals(1, infoList.size());
+ final ResolveInfo info = infoList.get(0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertTrue(info.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetDisabledActivityInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ try {
+ mPackageManager.getActivityInfo(DISABLED_ACTIVITY_COMPONENTNAME, 0);
+ fail("Attempt to get info on disabled component should fail.");
+ } catch (PackageManager.NameNotFoundException e) {
+ // expected
+ }
+
+ final ActivityInfo activityInfo =
+ mPackageManager.getActivityInfo(DISABLED_ACTIVITY_COMPONENTNAME,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(activityInfo);
+ assertFalse(activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetEnabledActivityInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ActivityInfo activityInfo =
+ mPackageManager.getActivityInfo(ENABLED_ACTIVITY_COMPONENTNAME, 0);
+ assertNotNull(activityInfo);
+ assertTrue(activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testEnableActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveActivity(mDisabledActivityIntent, 0);
+ assertNull(info);
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ final ResolveInfo info2 =
+ mPackageManager.resolveActivity(mDisabledActivityIntent,
+ 0);
+ assertNotNull(info2);
+ assertNotNull(info2.activityInfo);
+ assertFalse(info2.activityInfo.enabled);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentActivities(mDisabledActivityIntent, 0);
+ assertEquals(1, infoList.size());
+ }
+
+ @LargeTest
+ public void testDisableActivity() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveActivity(mEnabledActivityIntent, 0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ final ResolveInfo info2 =
+ mPackageManager.resolveActivity(mEnabledActivityIntent,
+ 0);
+ assertNull(info2);
+
+ final ResolveInfo info3 = mPackageManager.resolveActivity(mEnabledActivityIntent,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(info3);
+ assertNotNull(info3.activityInfo);
+ assertTrue(info3.activityInfo.enabled);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentActivities(mEnabledActivityIntent, 0);
+ assertEquals(0, infoList.size());
+ }
+
+ @MediumTest
+ public void testResolveDisabledService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveService(mDisabledServiceIntent, 0);
+ assertNull(info);
+
+ final ResolveInfo info2 = mPackageManager.resolveService(
+ mDisabledServiceIntent, GET_DISABLED_COMPONENTS);
+ assertNotNull(info2);
+ assertNotNull(info2.serviceInfo);
+ assertFalse(info2.serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testResolveEnabledService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveService(mEnabledServiceIntent, 0);
+ assertNotNull(info);
+ assertNotNull(info);
+ assertNotNull(info.serviceInfo);
+ assertTrue(info.serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryDisabledService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentServices(mDisabledServiceIntent, 0);
+ assertEquals(0, infoList.size());
+
+ final List<ResolveInfo> infoList2 =
+ mPackageManager.queryIntentServices(mDisabledServiceIntent,
+ GET_DISABLED_COMPONENTS);
+ assertEquals(1, infoList2.size());
+ final ResolveInfo info = infoList2.get(0);
+ assertNotNull(info);
+ assertNotNull(info.serviceInfo);
+ assertFalse(info.serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryEnabledService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryIntentServices(mEnabledServiceIntent, 0);
+ assertEquals(1, infoList.size());
+ final ResolveInfo info = infoList.get(0);
+ assertNotNull(info);
+ assertNotNull(info.serviceInfo);
+ assertTrue(info.serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetDisabledServiceInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ try {
+ mPackageManager.getServiceInfo(DISABLED_SERVICE_COMPONENTNAME, 0);
+ fail("Attempt to get info on disabled component should fail.");
+ } catch (PackageManager.NameNotFoundException e) {
+ // expected
+ }
+
+ final ServiceInfo serviceInfo =
+ mPackageManager.getServiceInfo(DISABLED_SERVICE_COMPONENTNAME,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(serviceInfo);
+ assertFalse(serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetEnabledServiceInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ServiceInfo serviceInfo =
+ mPackageManager.getServiceInfo(ENABLED_SERVICE_COMPONENTNAME, 0);
+ assertNotNull(serviceInfo);
+ assertTrue(serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testEnableService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveService(mDisabledServiceIntent, 0);
+ assertNull(info);
+ mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ final ResolveInfo info2 =
+ mPackageManager.resolveService(mDisabledServiceIntent,
+ 0);
+ assertNotNull(info2);
+ assertNotNull(info2.serviceInfo);
+ assertFalse(info2.serviceInfo.enabled);
+ }
+
+ @LargeTest
+ public void testDisableService() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveService(mEnabledServiceIntent, 0);
+ assertNotNull(info);
+ assertNotNull(info.serviceInfo);
+ mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ final ResolveInfo info2 =
+ mPackageManager.resolveService(mEnabledServiceIntent,
+ 0);
+ assertNull(info2);
+
+ final ResolveInfo info3 = mPackageManager.resolveService(mEnabledServiceIntent,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(info3);
+ assertNotNull(info3.serviceInfo);
+ assertTrue(info3.serviceInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryDisabledReceiver() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryBroadcastReceivers(mDisabledReceiverIntent, 0);
+ assertEquals(0, infoList.size());
+
+ final List<ResolveInfo> infoList2 =
+ mPackageManager.queryBroadcastReceivers(mDisabledReceiverIntent,
+ GET_DISABLED_COMPONENTS);
+ assertEquals(1, infoList2.size());
+ final ResolveInfo info = infoList2.get(0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertFalse(info.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testQueryEnabledReceiver() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> infoList =
+ mPackageManager.queryBroadcastReceivers(mEnabledReceiverIntent, 0);
+ assertEquals(1, infoList.size());
+ final ResolveInfo info = infoList.get(0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertTrue(info.activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetDisabledReceiverInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ try {
+ mPackageManager.getReceiverInfo(DISABLED_RECEIVER_COMPONENTNAME, 0);
+ fail("Attempt to get info on disabled component should fail.");
+ } catch (PackageManager.NameNotFoundException e) {
+ // expected
+ }
+
+ final ActivityInfo activityInfo =
+ mPackageManager.getReceiverInfo(DISABLED_RECEIVER_COMPONENTNAME,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(activityInfo);
+ assertFalse(activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testGetEnabledReceiverInfo() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ActivityInfo activityInfo =
+ mPackageManager.getReceiverInfo(ENABLED_RECEIVER_COMPONENTNAME, 0);
+ assertNotNull(activityInfo);
+ assertTrue(activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testEnableReceiver() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ try {
+ mPackageManager.getReceiverInfo(DISABLED_RECEIVER_COMPONENTNAME, 0);
+ fail("Attempt to get info on disabled component should fail.");
+ } catch (PackageManager.NameNotFoundException e) {
+ // expected
+ }
+
+ mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ ActivityInfo activityInfo =
+ mPackageManager.getReceiverInfo(DISABLED_RECEIVER_COMPONENTNAME, 0);
+ assertNotNull(activityInfo);
+ assertFalse(activityInfo.enabled);
+ }
+
+ @MediumTest
+ public void testDisableReceiver() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ActivityInfo activityInfo =
+ mPackageManager.getReceiverInfo(ENABLED_RECEIVER_COMPONENTNAME, 0);
+ assertNotNull(activityInfo);
+ assertTrue(activityInfo.enabled);
+ mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ try {
+ mPackageManager.getReceiverInfo(DISABLED_RECEIVER_COMPONENTNAME, 0);
+ fail("Attempt to get info on disabled component should fail.");
+ } catch (PackageManager.NameNotFoundException e) {
+ // expected
+ }
+ }
+
+ @MediumTest
+ public void testResolveEnabledProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(ENABLED_PROVIDER_NAME, 0);
+ assertNotNull(providerInfo);
+ assertTrue(providerInfo.enabled);
+ }
+
+ @MediumTest
+ public void testResolveDisabledProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(DISABLED_PROVIDER_NAME, 0);
+ assertNull(providerInfo);
+ ProviderInfo providerInfo2 =
+ mPackageManager.resolveContentProvider(DISABLED_PROVIDER_NAME,
+ GET_DISABLED_COMPONENTS);
+ assertNotNull(providerInfo2);
+ assertFalse(providerInfo2.enabled);
+ }
+
+ @MediumTest
+ public void testEnableProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(DISABLED_PROVIDER_NAME, 0);
+ assertNull(providerInfo);
+
+ mPackageManager.setComponentEnabledSetting(DISABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ ProviderInfo providerInfo2 =
+ mPackageManager.resolveContentProvider(DISABLED_PROVIDER_NAME, 0);
+ assertNotNull(providerInfo2);
+ assertFalse(providerInfo2.enabled);
+ }
+
+ @MediumTest
+ public void testDisableProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(ENABLED_PROVIDER_NAME, 0);
+ assertNotNull(providerInfo);
+ assertTrue(providerInfo.enabled);
+
+ mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ ProviderInfo providerInfo2 =
+ mPackageManager.resolveContentProvider(ENABLED_PROVIDER_NAME, 0);
+ assertNull(providerInfo2);
+ }
+
+ @MediumTest
+ public void testQueryEnabledProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ String enabledProviderProcessName = getComponentProcessName(ENABLED_PROVIDER_NAME);
+ PackageInfo pi = mPackageManager.getPackageInfo(ENABLED_PACKAGENAME, 0);
+ List<ProviderInfo> providerInfoList =
+ mPackageManager.queryContentProviders(enabledProviderProcessName,
+ pi.applicationInfo.uid, 0);
+ assertNotNull(providerInfoList);
+ assertEquals(1, providerInfoList.size());
+ assertEquals(ENABLED_PROVIDER_CLASSNAME,
+ providerInfoList.get(0).name);
+ }
+
+ @MediumTest
+ public void testQueryDisabledProvider() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_PROVIDER_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ PackageInfo pi = mPackageManager.getPackageInfo(ENABLED_PACKAGENAME, 0);
+
+ String disabledProviderProcessName = getComponentProcessName(DISABLED_PROVIDER_NAME);
+ List<ProviderInfo> providerInfoList =
+ mPackageManager.queryContentProviders(disabledProviderProcessName,
+ pi.applicationInfo.uid, 0);
+ assertNull(providerInfoList);
+
+
+ List<ProviderInfo> providerInfoList2 =
+ mPackageManager.queryContentProviders(disabledProviderProcessName,
+ pi.applicationInfo.uid, GET_DISABLED_COMPONENTS);
+ assertNotNull(providerInfoList2);
+ assertEquals(1, providerInfoList2.size());
+ assertEquals(DISABLED_PROVIDER_CLASSNAME,
+ providerInfoList2.get(0).name);
+ }
+
+ private String getComponentProcessName(String componentNameStr) {
+ ComponentInfo providerInfo =
+ mPackageManager.resolveContentProvider(componentNameStr,
+ GET_DISABLED_COMPONENTS);
+ return providerInfo.processName;
+ }
+
+ public void DISABLED_testResolveEnabledActivityInDisabledApp() throws Exception {
+ mPackageManager.setApplicationEnabledSetting(DISABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ 0);
+ mPackageManager.setComponentEnabledSetting(DISABLED_APP_ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info =
+ mPackageManager.resolveActivity(mDisabledAppEnabledActivityIntent, 0);
+ assertNull(info);
+
+ final ResolveInfo info2 = mPackageManager.resolveActivity(
+ mDisabledAppEnabledActivityIntent, GET_DISABLED_COMPONENTS);
+ assertNotNull(info2);
+ assertNotNull(info2.activityInfo);
+ assertTrue(info2.activityInfo.enabled);
+ }
+
+ public void DISABLED_testEnableApplication() throws Exception {
+ mPackageManager.setApplicationEnabledSetting(DISABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ 0);
+ mPackageManager.setComponentEnabledSetting(DISABLED_APP_ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info =
+ mPackageManager.resolveActivity(mDisabledAppEnabledActivityIntent, 0);
+ assertNull(info);
+
+ mPackageManager.setApplicationEnabledSetting(DISABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ 0);
+ final ResolveInfo info2 = mPackageManager.resolveActivity(
+ mDisabledAppEnabledActivityIntent, 0);
+ assertNotNull(info2);
+ assertNotNull(info2.activityInfo);
+ assertTrue(info2.activityInfo.enabled);
+
+ }
+
+ public void DISABLED_testDisableApplication() throws Exception {
+ mPackageManager.setApplicationEnabledSetting(ENABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ 0);
+ mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ final ResolveInfo info = mPackageManager.resolveActivity(mEnabledActivityIntent, 0);
+ assertNotNull(info);
+ assertNotNull(info.activityInfo);
+ assertTrue(info.activityInfo.enabled);
+
+ mPackageManager.setApplicationEnabledSetting(ENABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_DISABLED,
+ 0);
+ final ResolveInfo info2 = mPackageManager.resolveActivity(mEnabledActivityIntent, 0);
+ assertNull(info2);
+
+ // Clean up
+ mPackageManager.setApplicationEnabledSetting(ENABLED_PACKAGENAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ 0);
+
+ }
+
+ @MediumTest
+ public void testNonExplicitResolveAfterEnabling() throws Exception {
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_DEFAULT,
+ PackageManager.DONT_KILL_APP);
+
+ Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ intent.addCategory(TEST_CATEGORY);
+
+ final List<ResolveInfo> launchables =
+ mPackageManager.queryIntentActivities(intent, 0);
+
+ int numItems = launchables.size();
+ assertEquals(0, numItems);
+
+ mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME,
+ COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+
+ final List<ResolveInfo> launchables2 =
+ mPackageManager.queryIntentActivities(intent, 0);
+
+ int numItems2 = launchables2.size();
+ assertEquals(1, numItems2);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
new file mode 100755
index 0000000..82834b6
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -0,0 +1,2738 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.content.PackageHelper;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.storage.IMountService;
+import android.os.storage.StorageListener;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageResultCode;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class PackageManagerTests extends AndroidTestCase {
+ private static final boolean localLOGV = true;
+ public static final String TAG="PackageManagerTests";
+ public final long MAX_WAIT_TIME = 25*1000;
+ public final long WAIT_TIME_INCR = 5*1000;
+ private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
+ private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
+ private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
+ private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
+ private boolean mOrigState;
+
+ void failStr(String errMsg) {
+ Log.w(TAG, "errMsg="+errMsg);
+ fail(errMsg);
+ }
+ void failStr(Exception e) {
+ failStr(e.getMessage());
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mOrigState = getMediaState();
+ if (!mountMedia()) {
+ Log.i(TAG, "sdcard not mounted? Some of these tests might fail");
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Restore media state.
+ boolean newState = getMediaState();
+ if (newState != mOrigState) {
+ if (mOrigState) {
+ getMs().mountVolume(Environment.getExternalStorageDirectory().getPath());
+ } else {
+ getMs().unmountVolume(Environment.getExternalStorageDirectory().getPath(), true);
+ }
+ }
+ super.tearDown();
+ }
+
+ private class PackageInstallObserver extends IPackageInstallObserver.Stub {
+ public int returnCode;
+ private boolean doneFlag = false;
+
+ public void packageInstalled(String packageName, int returnCode) {
+ synchronized(this) {
+ this.returnCode = returnCode;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ abstract class GenericReceiver extends BroadcastReceiver {
+ private boolean doneFlag = false;
+ boolean received = false;
+ Intent intent;
+ IntentFilter filter;
+ abstract boolean notifyNow(Intent intent);
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (notifyNow(intent)) {
+ synchronized (this) {
+ received = true;
+ doneFlag = true;
+ this.intent = intent;
+ notifyAll();
+ }
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ public void setFilter(IntentFilter filter) {
+ this.filter = filter;
+ }
+ }
+
+ class InstallReceiver extends GenericReceiver {
+ String pkgName;
+
+ InstallReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ return false;
+ }
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName.equals(installedPkg)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private PackageManager getPm() {
+ return mContext.getPackageManager();
+ }
+
+ private IPackageManager getIPm() {
+ IPackageManager ipm = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+ return ipm;
+ }
+
+ public boolean invokeInstallPackage(Uri packageURI, int flags,
+ GenericReceiver receiver) throws Exception {
+ PackageInstallObserver observer = new PackageInstallObserver();
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ final boolean DEBUG = true;
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().installPackage(packageURI, observer, flags, null);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ if (observer.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ Log.i(TAG, "Failed to install with error code = " + observer.returnCode);
+ return false;
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for PACKAGE_ADDED notification");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ public void invokeInstallPackageFail(Uri packageURI, int flags, int result) throws Exception {
+ PackageInstallObserver observer = new PackageInstallObserver();
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ getPm().installPackage(packageURI, observer, flags, null);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ assertEquals(observer.returnCode, result);
+ }
+ } finally {
+ }
+ }
+
+ Uri getInstallablePackage(int fileResId, File outFile) {
+ Resources res = mContext.getResources();
+ InputStream is = null;
+ try {
+ is = res.openRawResource(fileResId);
+ } catch (NotFoundException e) {
+ failStr("Failed to load resource with id: " + fileResId);
+ }
+ FileUtils.setPermissions(outFile.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+ -1, -1);
+ assertTrue(FileUtils.copyToFile(is, outFile));
+ FileUtils.setPermissions(outFile.getPath(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO,
+ -1, -1);
+ return Uri.fromFile(outFile);
+ }
+
+ private PackageParser.Package parsePackage(Uri packageURI) {
+ final String archiveFilePath = packageURI.getPath();
+ PackageParser packageParser = new PackageParser(archiveFilePath);
+ File sourceFile = new File(archiveFilePath);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ PackageParser.Package pkg = packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
+ packageParser = null;
+ return pkg;
+ }
+ private boolean checkSd(long pkgLen) {
+ String status = Environment.getExternalStorageState();
+ if (!status.equals(Environment.MEDIA_MOUNTED)) {
+ return false;
+ }
+ long sdSize = -1;
+ StatFs sdStats = new StatFs(
+ Environment.getExternalStorageDirectory().getPath());
+ sdSize = (long)sdStats.getAvailableBlocks() *
+ (long)sdStats.getBlockSize();
+ // TODO check for thesholds here
+ return pkgLen <= sdSize;
+
+ }
+ private boolean checkInt(long pkgLen) {
+ StatFs intStats = new StatFs(Environment.getDataDirectory().getPath());
+ long intSize = (long)intStats.getBlockCount() *
+ (long)intStats.getBlockSize();
+ long iSize = (long)intStats.getAvailableBlocks() *
+ (long)intStats.getBlockSize();
+ // TODO check for thresholds here?
+ return pkgLen <= iSize;
+ }
+ private static final int INSTALL_LOC_INT = 1;
+ private static final int INSTALL_LOC_SD = 2;
+ private static final int INSTALL_LOC_ERR = -1;
+ private int getInstallLoc(int flags, int expInstallLocation, long pkgLen) {
+ // Flags explicitly over ride everything else.
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0 ) {
+ return INSTALL_LOC_INT;
+ } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0 ) {
+ return INSTALL_LOC_SD;
+ } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
+ return INSTALL_LOC_INT;
+ }
+ // Manifest option takes precedence next
+ if (expInstallLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+ if (checkSd(pkgLen)) {
+ return INSTALL_LOC_SD;
+ }
+ if (checkInt(pkgLen)) {
+ return INSTALL_LOC_INT;
+ }
+ return INSTALL_LOC_ERR;
+ }
+ if (expInstallLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+ if (checkInt(pkgLen)) {
+ return INSTALL_LOC_INT;
+ }
+ return INSTALL_LOC_ERR;
+ }
+ if (expInstallLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
+ // Check for free memory internally
+ if (checkInt(pkgLen)) {
+ return INSTALL_LOC_INT;
+ }
+ // Check for free memory externally
+ if (checkSd(pkgLen)) {
+ return INSTALL_LOC_SD;
+ }
+ return INSTALL_LOC_ERR;
+ }
+ // Check for settings preference.
+ boolean checkSd = false;
+ int userPref = getDefaultInstallLoc();
+ if (userPref == APP_INSTALL_DEVICE) {
+ if (checkInt(pkgLen)) {
+ return INSTALL_LOC_INT;
+ }
+ return INSTALL_LOC_ERR;
+ } else if (userPref == APP_INSTALL_SDCARD) {
+ if (checkSd(pkgLen)) {
+ return INSTALL_LOC_SD;
+ }
+ return INSTALL_LOC_ERR;
+ }
+ // Default system policy for apps with no manifest option specified.
+ // Check for free memory internally
+ if (checkInt(pkgLen)) {
+ return INSTALL_LOC_INT;
+ }
+ return INSTALL_LOC_ERR;
+ }
+
+ private void assertInstall(PackageParser.Package pkg, int flags, int expInstallLocation) {
+ try {
+ String pkgName = pkg.packageName;
+ ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
+ assertNotNull(info);
+ assertEquals(pkgName, info.packageName);
+ File dataDir = Environment.getDataDirectory();
+ String appInstallPath = new File(dataDir, "app").getPath();
+ String drmInstallPath = new File(dataDir, "app-private").getPath();
+ File srcDir = new File(info.sourceDir);
+ String srcPath = srcDir.getParent();
+ File publicSrcDir = new File(info.publicSourceDir);
+ String publicSrcPath = publicSrcDir.getParent();
+ long pkgLen = new File(info.sourceDir).length();
+
+ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
+ assertTrue((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
+ assertEquals(srcPath, drmInstallPath);
+ assertEquals(publicSrcPath, appInstallPath);
+ } else {
+ assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
+ int rLoc = getInstallLoc(flags, expInstallLocation, pkgLen);
+ if (rLoc == INSTALL_LOC_INT) {
+ assertEquals(srcPath, appInstallPath);
+ assertEquals(publicSrcPath, appInstallPath);
+ assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+ } else if (rLoc == INSTALL_LOC_SD){
+ assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+ assertTrue(srcPath.startsWith(SECURE_CONTAINERS_PREFIX));
+ assertTrue(publicSrcPath.startsWith(SECURE_CONTAINERS_PREFIX));
+ } else {
+ // TODO handle error. Install should have failed.
+ }
+ }
+ } catch (NameNotFoundException e) {
+ failStr("failed with exception : " + e);
+ }
+ }
+
+ private void assertNotInstalled(String pkgName) {
+ try {
+ ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0);
+ fail(pkgName + " shouldnt be installed");
+ } catch (NameNotFoundException e) {
+ }
+ }
+
+ class InstallParams {
+ Uri packageURI;
+ PackageParser.Package pkg;
+ InstallParams(String outFileName, int rawResId) {
+ this.pkg = getParsedPackage(outFileName, rawResId);
+ this.packageURI = Uri.fromFile(new File(pkg.mScanPath));
+ }
+ InstallParams(PackageParser.Package pkg) {
+ this.packageURI = Uri.fromFile(new File(pkg.mScanPath));
+ this.pkg = pkg;
+ }
+ long getApkSize() {
+ File file = new File(pkg.mScanPath);
+ return file.length();
+ }
+ }
+
+ private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp) {
+ return installFromRawResource("install.apk", R.raw.install, flags, cleanUp,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ static final String PERM_PACKAGE = "package";
+ static final String PERM_DEFINED = "defined";
+ static final String PERM_UNDEFINED = "undefined";
+ static final String PERM_USED = "used";
+ static final String PERM_NOTUSED = "notused";
+
+ private void assertPermissions(String[] cmds) {
+ final PackageManager pm = getPm();
+ String pkg = null;
+ PackageInfo pkgInfo = null;
+ String mode = PERM_DEFINED;
+ int i = 0;
+ while (i < cmds.length) {
+ String cmd = cmds[i++];
+ if (cmd == PERM_PACKAGE) {
+ pkg = cmds[i++];
+ try {
+ pkgInfo = pm.getPackageInfo(pkg,
+ PackageManager.GET_PERMISSIONS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException e) {
+ pkgInfo = null;
+ }
+ } else if (cmd == PERM_DEFINED || cmd == PERM_UNDEFINED
+ || cmd == PERM_USED || cmd == PERM_NOTUSED) {
+ mode = cmds[i++];
+ } else {
+ if (mode == PERM_DEFINED) {
+ try {
+ PermissionInfo pi = pm.getPermissionInfo(cmd, 0);
+ assertNotNull(pi);
+ assertEquals(pi.packageName, pkg);
+ assertEquals(pi.name, cmd);
+ assertNotNull(pkgInfo);
+ boolean found = false;
+ for (int j=0; j<pkgInfo.permissions.length && !found; j++) {
+ if (pkgInfo.permissions[j].name.equals(cmd)) {
+ found = true;
+ }
+ }
+ if (!found) {
+ fail("Permission not found: " + cmd);
+ }
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else if (mode == PERM_UNDEFINED) {
+ try {
+ pm.getPermissionInfo(cmd, 0);
+ throw new RuntimeException("Permission exists: " + cmd);
+ } catch (NameNotFoundException e) {
+ }
+ if (pkgInfo != null) {
+ boolean found = false;
+ for (int j=0; j<pkgInfo.permissions.length && !found; j++) {
+ if (pkgInfo.permissions[j].name.equals(cmd)) {
+ found = true;
+ }
+ }
+ if (found) {
+ fail("Permission still exists: " + cmd);
+ }
+ }
+ } else if (mode == PERM_USED || mode == PERM_NOTUSED) {
+ boolean found = false;
+ for (int j=0; j<pkgInfo.requestedPermissions.length && !found; j++) {
+ if (pkgInfo.requestedPermissions[j].equals(cmd)) {
+ found = true;
+ }
+ }
+ if (!found) {
+ fail("Permission not requested: " + cmd);
+ }
+ if (mode == PERM_USED) {
+ if (pm.checkPermission(cmd, pkg)
+ != PackageManager.PERMISSION_GRANTED) {
+ fail("Permission not granted: " + cmd);
+ }
+ } else {
+ if (pm.checkPermission(cmd, pkg)
+ != PackageManager.PERMISSION_DENIED) {
+ fail("Permission granted: " + cmd);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private PackageParser.Package getParsedPackage(String outFileName, int rawResId) {
+ PackageManager pm = mContext.getPackageManager();
+ File filesDir = mContext.getFilesDir();
+ File outFile = new File(filesDir, outFileName);
+ Uri packageURI = getInstallablePackage(rawResId, outFile);
+ PackageParser.Package pkg = parsePackage(packageURI);
+ return pkg;
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install it.
+ */
+ private void installFromRawResource(InstallParams ip,
+ int flags, boolean cleanUp, boolean fail, int result,
+ int expInstallLocation) {
+ PackageManager pm = mContext.getPackageManager();
+ PackageParser.Package pkg = ip.pkg;
+ Uri packageURI = ip.packageURI;
+ if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+ // Make sure the package doesn't exist
+ try {
+ ApplicationInfo appInfo = pm.getApplicationInfo(pkg.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ GenericReceiver receiver = new DeleteReceiver(pkg.packageName);
+ invokeDeletePackage(pkg.packageName, 0, receiver);
+ } catch (NameNotFoundException e1) {
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+ try {
+ if (fail) {
+ invokeInstallPackageFail(packageURI, flags, result);
+ if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+ assertNotInstalled(pkg.packageName);
+ }
+ } else {
+ InstallReceiver receiver = new InstallReceiver(pkg.packageName);
+ assertTrue(invokeInstallPackage(packageURI, flags, receiver));
+ // Verify installed information
+ assertInstall(pkg, flags, expInstallLocation);
+ }
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ if (cleanUp) {
+ cleanUpInstall(ip);
+ }
+ }
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install it.
+ */
+ private InstallParams installFromRawResource(String outFileName,
+ int rawResId, int flags, boolean cleanUp, boolean fail, int result,
+ int expInstallLocation) {
+ PackageManager pm = mContext.getPackageManager();
+ InstallParams ip = new InstallParams(outFileName, rawResId);
+ installFromRawResource(ip, flags, cleanUp, fail, result, expInstallLocation);
+ return ip;
+ }
+
+ @MediumTest
+ public void testInstallNormalInternal() {
+ sampleInstallFromRawResource(0, true);
+ }
+
+ @MediumTest
+ public void testInstallFwdLockedInternal() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, true);
+ }
+
+ @MediumTest
+ public void testInstallSdcard() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, true);
+ }
+
+ /* ------------------------- Test replacing packages --------------*/
+ class ReplaceReceiver extends GenericReceiver {
+ String pkgName;
+ final static int INVALID = -1;
+ final static int REMOVED = 1;
+ final static int ADDED = 2;
+ final static int REPLACED = 3;
+ int removed = INVALID;
+ // for updated system apps only
+ boolean update = false;
+
+ ReplaceReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ if (update) {
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ }
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName == null || !pkgName.equals(installedPkg)) {
+ return false;
+ }
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ removed = REMOVED;
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ if (removed != REMOVED) {
+ return false;
+ }
+ boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ if (!replacing) {
+ return false;
+ }
+ removed = ADDED;
+ if (!update) {
+ return true;
+ }
+ } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+ if (removed != ADDED) {
+ return false;
+ }
+ removed = REPLACED;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install first and then replace it
+ * again.
+ */
+ private void sampleReplaceFromRawResource(int flags) {
+ InstallParams ip = sampleInstallFromRawResource(flags, false);
+ boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0);
+ Log.i(TAG, "replace=" + replace);
+ GenericReceiver receiver;
+ if (replace) {
+ receiver = new ReplaceReceiver(ip.pkg.packageName);
+ Log.i(TAG, "Creating replaceReceiver");
+ } else {
+ receiver = new InstallReceiver(ip.pkg.packageName);
+ }
+ try {
+ try {
+ assertEquals(invokeInstallPackage(ip.packageURI, flags, receiver), replace);
+ if (replace) {
+ assertInstall(ip.pkg, flags, ip.pkg.installLocation);
+ }
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ }
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testReplaceFailNormalInternal() {
+ sampleReplaceFromRawResource(0);
+ }
+
+ @MediumTest
+ public void testReplaceFailFwdLockedInternal() {
+ sampleReplaceFromRawResource(PackageManager.INSTALL_FORWARD_LOCK);
+ }
+
+ @MediumTest
+ public void testReplaceFailSdcard() {
+ sampleReplaceFromRawResource(PackageManager.INSTALL_EXTERNAL);
+ }
+
+ @MediumTest
+ public void testReplaceNormalInternal() {
+ sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING);
+ }
+
+ @MediumTest
+ public void testReplaceFwdLockedInternal() {
+ sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING |
+ PackageManager.INSTALL_FORWARD_LOCK);
+ }
+
+ @MediumTest
+ public void testReplaceSdcard() {
+ sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING |
+ PackageManager.INSTALL_EXTERNAL);
+ }
+
+ /* -------------- Delete tests ---*/
+ class DeleteObserver extends IPackageDeleteObserver.Stub {
+
+ public boolean succeeded;
+ private boolean doneFlag = false;
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ public void packageDeleted(boolean succeeded) throws RemoteException {
+ synchronized(this) {
+ this.succeeded = succeeded;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+ }
+
+ class DeleteReceiver extends GenericReceiver {
+ String pkgName;
+
+ DeleteReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme("package");
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ return false;
+ }
+ Uri data = intent.getData();
+ String installedPkg = data.getEncodedSchemeSpecificPart();
+ if (pkgName.equals(installedPkg)) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public boolean invokeDeletePackage(final String pkgName, int flags,
+ GenericReceiver receiver) throws Exception {
+ DeleteObserver observer = new DeleteObserver();
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().deletePackage(pkgName, observer, flags);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for PACKAGE_REMOVED notification");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ public void deleteFromRawResource(int iFlags, int dFlags) {
+ InstallParams ip = sampleInstallFromRawResource(iFlags, false);
+ boolean retainData = ((dFlags & PackageManager.DONT_DELETE_DATA) != 0);
+ GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName);
+ DeleteObserver observer = new DeleteObserver();
+ try {
+ assertTrue(invokeDeletePackage(ip.pkg.packageName, dFlags, receiver));
+ ApplicationInfo info = null;
+ Log.i(TAG, "okay4");
+ try {
+ info = getPm().getApplicationInfo(ip.pkg.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (NameNotFoundException e) {
+ info = null;
+ }
+ if (retainData) {
+ assertNotNull(info);
+ assertEquals(info.packageName, ip.pkg.packageName);
+ File file = new File(info.dataDir);
+ assertTrue(file.exists());
+ } else {
+ assertNull(info);
+ }
+ } catch (Exception e) {
+ failStr(e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testDeleteNormalInternal() {
+ deleteFromRawResource(0, 0);
+ }
+
+ @MediumTest
+ public void testDeleteFwdLockedInternal() {
+ deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, 0);
+ }
+
+ @MediumTest
+ public void testDeleteSdcard() {
+ deleteFromRawResource(PackageManager.INSTALL_EXTERNAL, 0);
+ }
+
+ @MediumTest
+ public void testDeleteNormalInternalRetainData() {
+ deleteFromRawResource(0, PackageManager.DONT_DELETE_DATA);
+ }
+
+ @MediumTest
+ public void testDeleteFwdLockedInternalRetainData() {
+ deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, PackageManager.DONT_DELETE_DATA);
+ }
+
+ @MediumTest
+ public void testDeleteSdcardRetainData() {
+ deleteFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.DONT_DELETE_DATA);
+ }
+
+ /* sdcard mount/unmount tests ******/
+
+ class SdMountReceiver extends GenericReceiver {
+ String pkgNames[];
+ boolean status = true;
+
+ SdMountReceiver(String[] pkgNames) {
+ this.pkgNames = pkgNames;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ Log.i(TAG, "okay 1");
+ String action = intent.getAction();
+ if (!Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ return false;
+ }
+ String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (String pkg : pkgNames) {
+ boolean found = false;
+ for (String rpkg : rpkgList) {
+ if (rpkg.equals(pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ status = false;
+ return true;
+ }
+ }
+ return true;
+ }
+ }
+
+ class SdUnMountReceiver extends GenericReceiver {
+ String pkgNames[];
+ boolean status = true;
+
+ SdUnMountReceiver(String[] pkgNames) {
+ this.pkgNames = pkgNames;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ if (!Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ return false;
+ }
+ String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (String pkg : pkgNames) {
+ boolean found = false;
+ for (String rpkg : rpkgList) {
+ if (rpkg.equals(pkg)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ status = false;
+ return true;
+ }
+ }
+ return true;
+ }
+ }
+
+ IMountService getMs() {
+ IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ } else {
+ Log.e(TAG, "Can't get mount service");
+ }
+ return null;
+ }
+
+ boolean getMediaState() {
+ try {
+ String mPath = Environment.getExternalStorageDirectory().getPath();
+ String state = getMs().getVolumeState(mPath);
+ return Environment.MEDIA_MOUNTED.equals(state);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ boolean mountMedia() {
+ if (getMediaState()) {
+ return true;
+ }
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ int ret = getMs().mountVolume(mPath);
+ return ret == StorageResultCode.OperationSucceeded;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ private boolean unmountMedia() {
+ String path = Environment.getExternalStorageDirectory().getPath();
+ try {
+ String state = getMs().getVolumeState(path);
+ if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ failStr(e);
+ }
+
+ StorageListener observer = new StorageListener();
+ StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+ sm.registerListener(observer);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ getMs().unmountVolume(path, true);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for unmount media notification");
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception : " + e);
+ return false;
+ } finally {
+ sm.unregisterListener(observer);
+ }
+ }
+
+ private boolean mountFromRawResource() {
+ // Install pkg on sdcard
+ InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, false);
+ if (localLOGV) Log.i(TAG, "Installed pkg on sdcard");
+ boolean origState = getMediaState();
+ boolean registeredReceiver = false;
+ SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName});
+ try {
+ if (localLOGV) Log.i(TAG, "Unmounting media");
+ // Unmount media
+ assertTrue(unmountMedia());
+ if (localLOGV) Log.i(TAG, "Unmounted media");
+ // Register receiver here
+ PackageManager pm = getPm();
+ mContext.registerReceiver(receiver, receiver.filter);
+ registeredReceiver = true;
+
+ // Wait on receiver
+ synchronized (receiver) {
+ if (localLOGV) Log.i(TAG, "Mounting media");
+ // Mount media again
+ assertTrue(mountMedia());
+ if (localLOGV) Log.i(TAG, "Mounted media");
+ if (localLOGV) Log.i(TAG, "Waiting for notification");
+ long waitTime = 0;
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ failStr("Timed out waiting for EXTERNAL_APPLICATIONS notification");
+ }
+ return receiver.received;
+ }
+ } catch (InterruptedException e) {
+ failStr(e);
+ return false;
+ } finally {
+ if (registeredReceiver) mContext.unregisterReceiver(receiver);
+ // Restore original media state
+ if (origState) {
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ if (localLOGV) Log.i(TAG, "Cleaning up install");
+ cleanUpInstall(ip);
+ }
+ }
+
+ /*
+ * Install package on sdcard. Unmount and then mount the media.
+ * (Use PackageManagerService private api for now)
+ * Make sure the installed package is available.
+ * STOPSHIP will uncomment when MountService api's to mount/unmount
+ * are made asynchronous.
+ */
+ public void xxxtestMountSdNormalInternal() {
+ assertTrue(mountFromRawResource());
+ }
+
+ void cleanUpInstall(InstallParams ip) {
+ if (ip == null) {
+ return;
+ }
+ Runtime.getRuntime().gc();
+ Log.i(TAG, "Deleting package : " + ip.pkg.packageName);
+ getPm().deletePackage(ip.pkg.packageName, null, 0);
+ File outFile = new File(ip.pkg.mScanPath);
+ if (outFile != null && outFile.exists()) {
+ outFile.delete();
+ }
+ }
+ void cleanUpInstall(String pkgName) {
+ if (pkgName == null) {
+ return;
+ }
+ Log.i(TAG, "Deleting package : " + pkgName);
+ try {
+ ApplicationInfo info = getPm().getApplicationInfo(pkgName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ if (info != null) {
+ getPm().deletePackage(pkgName, null, 0);
+ }
+ } catch (NameNotFoundException e) {}
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationInternal() {
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationSdcard() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationAuto() {
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationUnspecified() {
+ installFromRawResource("install.apk", R.raw.install_loc_unspecified,
+ 0, true, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationFwdLockedFlagSdcard() {
+ installFromRawResource("install.apk", R.raw.install_loc_unspecified,
+ PackageManager.INSTALL_FORWARD_LOCK |
+ PackageManager.INSTALL_EXTERNAL, true, true,
+ PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationFwdLockedSdcard() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ PackageManager.INSTALL_FORWARD_LOCK, true, false,
+ -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+
+ /*
+ * Install a package on internal flash via PackageManager install flag. Replace
+ * the package via flag to install on sdcard. Make sure the new flag overrides
+ * the old install location.
+ */
+ @MediumTest
+ public void testReplaceFlagInternalSdcard() {
+ int iFlags = 0;
+ int rFlags = PackageManager.INSTALL_EXTERNAL;
+ InstallParams ip = sampleInstallFromRawResource(iFlags, false);
+ GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+ int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+ try {
+ assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags, receiver), true);
+ assertInstall(ip.pkg, rFlags, ip.pkg.installLocation);
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ /*
+ * Install a package on sdcard via PackageManager install flag. Replace
+ * the package with no flags or manifest option and make sure the old
+ * install location is retained.
+ */
+ @MediumTest
+ public void testReplaceFlagSdcardInternal() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = 0;
+ InstallParams ip = sampleInstallFromRawResource(iFlags, false);
+ GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+ int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+ try {
+ assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags, receiver), true);
+ assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationReplaceInternalSdcard() {
+ int iFlags = 0;
+ int iApk = R.raw.install_loc_internal;
+ int rFlags = 0;
+ int rApk = R.raw.install_loc_sdcard;
+ InstallParams ip = installFromRawResource("install.apk", iApk,
+ iFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+ int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+ try {
+ InstallParams rp = installFromRawResource("install.apk", rApk,
+ replaceFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ assertInstall(rp.pkg, replaceFlags, rp.pkg.installLocation);
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ @MediumTest
+ public void testManifestInstallLocationReplaceSdcardInternal() {
+ int iFlags = 0;
+ int iApk = R.raw.install_loc_sdcard;
+ int rFlags = 0;
+ int rApk = R.raw.install_loc_unspecified;
+ InstallParams ip = installFromRawResource("install.apk", iApk,
+ iFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+ try {
+ InstallParams rp = installFromRawResource("install.apk", rApk,
+ replaceFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ assertInstall(rp.pkg, replaceFlags, ip.pkg.installLocation);
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ cleanUpInstall(ip);
+ }
+ }
+
+ class MoveReceiver extends GenericReceiver {
+ String pkgName;
+ final static int INVALID = -1;
+ final static int REMOVED = 1;
+ final static int ADDED = 2;
+ int removed = INVALID;
+
+ MoveReceiver(String pkgName) {
+ this.pkgName = pkgName;
+ filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ super.setFilter(filter);
+ }
+
+ public boolean notifyNow(Intent intent) {
+ String action = intent.getAction();
+ Log.i(TAG, "MoveReceiver::" + action);
+ if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+ String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null) {
+ for (String pkg : list) {
+ if (pkg.equals(pkgName)) {
+ removed = REMOVED;
+ break;
+ }
+ }
+ }
+ removed = REMOVED;
+ } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
+ if (removed != REMOVED) {
+ return false;
+ }
+ String[] list = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (list != null) {
+ for (String pkg : list) {
+ if (pkg.equals(pkgName)) {
+ removed = ADDED;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private class PackageMoveObserver extends IPackageMoveObserver.Stub {
+ public int returnCode;
+ private boolean doneFlag = false;
+ public String packageName;
+ public PackageMoveObserver(String pkgName) {
+ packageName = pkgName;
+ }
+ public void packageMoved(String packageName, int returnCode) {
+ Log.i("DEBUG_MOVE::", "pkg = " + packageName + ", " + "ret = " + returnCode);
+ if (!packageName.equals(this.packageName)) {
+ return;
+ }
+ synchronized(this) {
+ this.returnCode = returnCode;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ }
+
+ public boolean invokeMovePackage(String pkgName, int flags,
+ GenericReceiver receiver) throws Exception {
+ PackageMoveObserver observer = new PackageMoveObserver(pkgName);
+ final boolean received = false;
+ mContext.registerReceiver(receiver, receiver.filter);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ synchronized (receiver) {
+ getPm().movePackage(pkgName, observer, flags);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for pkgmove callback");
+ }
+ if (observer.returnCode != PackageManager.MOVE_SUCCEEDED) {
+ return false;
+ }
+ // Verify we received the broadcast
+ waitTime = 0;
+ while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ receiver.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!receiver.isDone()) {
+ throw new Exception("Timed out waiting for MOVE notifications");
+ }
+ return receiver.received;
+ }
+ }
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+ private boolean invokeMovePackageFail(String pkgName, int flags, int errCode) throws Exception {
+ PackageMoveObserver observer = new PackageMoveObserver(pkgName);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ getPm().movePackage(pkgName, observer, flags);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for pkgmove callback");
+ }
+ assertEquals(errCode, observer.returnCode);
+ }
+ } finally {
+ }
+ return true;
+ }
+
+ private int getDefaultInstallLoc() {
+ int origDefaultLoc = PackageInfo.INSTALL_LOCATION_AUTO;
+ try {
+ origDefaultLoc = Settings.System.getInt(mContext.getContentResolver(), Settings.Secure.DEFAULT_INSTALL_LOCATION);
+ } catch (SettingNotFoundException e1) {
+ }
+ return origDefaultLoc;
+ }
+
+ private void setInstallLoc(int loc) {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INSTALL_LOCATION, loc);
+ }
+ /*
+ * Tests for moving apps between internal and external storage
+ */
+ /*
+ * Utility function that reads a apk bundled as a raw resource
+ * copies it into own data directory and invokes
+ * PackageManager api to install first and then replace it
+ * again.
+ */
+
+ private void moveFromRawResource(String outFileName,
+ int rawResId, int installFlags, int moveFlags, boolean cleanUp,
+ boolean fail, int result) {
+ int origDefaultLoc = getDefaultInstallLoc();
+ InstallParams ip = null;
+ try {
+ setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ // Install first
+ ip = installFromRawResource("install.apk", rawResId, installFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ ApplicationInfo oldAppInfo = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ if (fail) {
+ assertTrue(invokeMovePackageFail(ip.pkg.packageName, moveFlags, result));
+ ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ assertNotNull(info);
+ assertEquals(oldAppInfo.flags, info.flags);
+ } else {
+ // Create receiver based on expRetCode
+ MoveReceiver receiver = new MoveReceiver(ip.pkg.packageName);
+ boolean retCode = invokeMovePackage(ip.pkg.packageName, moveFlags,
+ receiver);
+ assertTrue(retCode);
+ ApplicationInfo info = getPm().getApplicationInfo(ip.pkg.packageName, 0);
+ assertNotNull(info);
+ if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) {
+ assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0);
+ } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0){
+ assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
+ }
+ }
+ } catch (NameNotFoundException e) {
+ failStr("Pkg hasnt been installed correctly");
+ } catch (Exception e) {
+ failStr("Failed with exception : " + e);
+ } finally {
+ if (ip != null) {
+ cleanUpInstall(ip);
+ }
+ // Restore default install location
+ setInstallLoc(origDefaultLoc);
+ }
+ }
+ private void sampleMoveFromRawResource(int installFlags, int moveFlags, boolean fail,
+ int result) {
+ moveFromRawResource("install.apk",
+ R.raw.install, installFlags, moveFlags, true,
+ fail, result);
+ }
+
+ @MediumTest
+ public void testMoveAppInternalToExternal() {
+ int installFlags = PackageManager.INSTALL_INTERNAL;
+ int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+ boolean fail = false;
+ int result = PackageManager.MOVE_SUCCEEDED;
+ sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+ }
+
+ @MediumTest
+ public void testMoveAppInternalToInternal() {
+ int installFlags = PackageManager.INSTALL_INTERNAL;
+ int moveFlags = PackageManager.MOVE_INTERNAL;
+ boolean fail = true;
+ int result = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+ sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+ }
+
+ @MediumTest
+ public void testMoveAppExternalToExternal() {
+ int installFlags = PackageManager.INSTALL_EXTERNAL;
+ int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+ boolean fail = true;
+ int result = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+ sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+ }
+ @MediumTest
+ public void testMoveAppExternalToInternal() {
+ int installFlags = PackageManager.INSTALL_EXTERNAL;
+ int moveFlags = PackageManager.MOVE_INTERNAL;
+ boolean fail = false;
+ int result = PackageManager.MOVE_SUCCEEDED;
+ sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+ }
+ @MediumTest
+ public void testMoveAppForwardLocked() {
+ int installFlags = PackageManager.INSTALL_FORWARD_LOCK;
+ int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+ boolean fail = true;
+ int result = PackageManager.MOVE_FAILED_FORWARD_LOCKED;
+ sampleMoveFromRawResource(installFlags, moveFlags, fail, result);
+ }
+ //TODO: To be reenabled after investigation
+ public void testMoveAppFailInternalToExternalDelete() {
+ int installFlags = 0;
+ int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA;
+ boolean fail = true;
+ final int result = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+
+ int rawResId = R.raw.install;
+ int origDefaultLoc = getDefaultInstallLoc();
+ InstallParams ip = null;
+ try {
+ PackageManager pm = getPm();
+ setInstallLoc(PackageHelper.APP_INSTALL_AUTO);
+ // Install first
+ ip = installFromRawResource("install.apk", R.raw.install, installFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ // Delete the package now retaining data.
+ pm.deletePackage(ip.pkg.packageName, null, PackageManager.DONT_DELETE_DATA);
+ assertTrue(invokeMovePackageFail(ip.pkg.packageName, moveFlags, result));
+ } catch (Exception e) {
+ failStr(e);
+ } finally {
+ if (ip != null) {
+ cleanUpInstall(ip);
+ }
+ // Restore default install location
+ setInstallLoc(origDefaultLoc);
+ }
+ }
+ /*
+ * Test that an install error code is returned when media is unmounted
+ * and package installed on sdcard via package manager flag.
+ */
+ @MediumTest
+ public void testInstallSdcardUnmount() {
+ boolean origState = getMediaState();
+ try {
+ // Unmount sdcard
+ assertTrue(unmountMedia());
+ // Try to install and make sure an error code is returned.
+ installFromRawResource("install.apk", R.raw.install,
+ PackageManager.INSTALL_EXTERNAL, false,
+ true, PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ } finally {
+ // Restore original media state
+ if (origState) {
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+
+ /*
+ * Unmount sdcard. Try installing an app with manifest option to install
+ * on sdcard. Make sure it gets installed on internal flash.
+ */
+ @MediumTest
+ public void testInstallManifestSdcardUnmount() {
+ boolean origState = getMediaState();
+ try {
+ // Unmount sdcard
+ assertTrue(unmountMedia());
+ InstallParams ip = new InstallParams("install.apk", R.raw.install_loc_sdcard);
+ installFromRawResource(ip, 0, true, false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ } finally {
+ // Restore original media state
+ if (origState) {
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+
+ /*---------- Recommended install location tests ----*/
+ /* Precedence: FlagManifestExistingUser
+ * PrecedenceSuffixes:
+ * Flag : FlagI, FlagE, FlagF
+ * I - internal, E - external, F - forward locked, Flag suffix absent if not using any option.
+ * Manifest: ManifestI, ManifestE, ManifestA, Manifest suffix absent if not using any option.
+ * Existing: Existing suffix absent if not existing.
+ * User: UserI, UserE, UserA, User suffix absent if not existing.
+ *
+ */
+ /*
+ * Install an app on internal flash
+ */
+ @MediumTest
+ public void testFlagI() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, true);
+ }
+ /*
+ * Install an app on sdcard.
+ */
+ @MediumTest
+ public void testFlagE() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, true);
+ }
+
+ /*
+ * Install an app on sdcard.
+ */
+ @MediumTest
+ public void testFlagF() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, true);
+ }
+ /*
+ * Install an app with both internal and external flags set. should fail
+ */
+ @MediumTest
+ public void testFlagIE() {
+ installFromRawResource("install.apk", R.raw.install,
+ PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_INTERNAL,
+ false,
+ true, PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+
+ /*
+ * Install an app with both internal and external flags set. should fail
+ */
+ @MediumTest
+ public void testFlagIF() {
+ sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK |
+ PackageManager.INSTALL_INTERNAL, true);
+ }
+ /*
+ * Install an app with both internal and external flags set. should fail
+ */
+ @MediumTest
+ public void testFlagEF() {
+ installFromRawResource("install.apk", R.raw.install,
+ PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_EXTERNAL,
+ false,
+ true, PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+ /*
+ * Install an app with both internal and external flags set. should fail
+ */
+ @MediumTest
+ public void testFlagIEF() {
+ installFromRawResource("install.apk", R.raw.install,
+ PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_INTERNAL |
+ PackageManager.INSTALL_EXTERNAL,
+ false,
+ true, PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+ /*
+ * Install an app with both internal and manifest option set.
+ * should install on internal.
+ */
+ @MediumTest
+ public void testFlagIManifestI() {
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ PackageManager.INSTALL_INTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ /*
+ * Install an app with both internal and manifest preference for
+ * preferExternal. Should install on internal.
+ */
+ @MediumTest
+ public void testFlagIManifestE() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ PackageManager.INSTALL_INTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ /*
+ * Install an app with both internal and manifest preference for
+ * auto. should install internal.
+ */
+ @MediumTest
+ public void testFlagIManifestA() {
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ PackageManager.INSTALL_INTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ /*
+ * Install an app with both external and manifest option set.
+ * should install externally.
+ */
+ @MediumTest
+ public void testFlagEManifestI() {
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * Install an app with both external and manifest preference for
+ * preferExternal. Should install externally.
+ */
+ @MediumTest
+ public void testFlagEManifestE() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * Install an app with both external and manifest preference for
+ * auto. should install on external media.
+ */
+ @MediumTest
+ public void testFlagEManifestA() {
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * Install an app with fwd locked flag set and install location set to
+ * internal. should install internally.
+ */
+ @MediumTest
+ public void testFlagFManifestI() {
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * Install an app with fwd locked flag set and install location set to
+ * preferExternal. should install internally.
+ */
+ @MediumTest
+ public void testFlagFManifestE() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * Install an app with fwd locked flag set and install location set to
+ * auto. should install internally.
+ */
+ @MediumTest
+ public void testFlagFManifestA() {
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ PackageManager.INSTALL_EXTERNAL,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /* The following test functions verify install location for existing apps.
+ * ie existing app can be installed internally or externally. If install
+ * flag is explicitly set it should override current location. If manifest location
+ * is set, that should over ride current location too. if not the existing install
+ * location should be honoured.
+ * testFlagI/E/F/ExistingI/E -
+ */
+ @MediumTest
+ public void testFlagIExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ @MediumTest
+ public void testFlagIExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ @MediumTest
+ public void testFlagEExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ @MediumTest
+ public void testFlagEExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ @MediumTest
+ public void testFlagFExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ @MediumTest
+ public void testFlagFExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ -1);
+ }
+ /*
+ * The following set of tests verify the installation of apps with
+ * install location attribute set to internalOnly, preferExternal and auto.
+ * The manifest option should dictate the install location.
+ * public void testManifestI/E/A
+ * TODO out of memory fall back behaviour.
+ */
+ @MediumTest
+ public void testManifestI() {
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ 0,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testManifestE() {
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ 0,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ @MediumTest
+ public void testManifestA() {
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ 0,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ /*
+ * The following set of tests verify the installation of apps
+ * with install location attribute set to internalOnly, preferExternal and auto
+ * for already existing apps. The manifest option should take precedence.
+ * TODO add out of memory fall back behaviour.
+ * testManifestI/E/AExistingI/E
+ */
+ @MediumTest
+ public void testManifestIExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testManifestIExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_internal,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testManifestEExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ @MediumTest
+ public void testManifestEExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ @MediumTest
+ public void testManifestAExistingI() {
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_AUTO);
+ }
+ @MediumTest
+ public void testManifestAExistingE() {
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ -1);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install_loc_auto,
+ rFlags,
+ true,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * The following set of tests check install location for existing
+ * application based on user setting.
+ */
+ private int getExpectedInstallLocation(int userSetting) {
+ int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ boolean enable = getUserSettingSetInstallLocation();
+ if (enable) {
+ if (userSetting == PackageHelper.APP_INSTALL_AUTO) {
+ iloc = PackageInfo.INSTALL_LOCATION_AUTO;
+ } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) {
+ iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
+ } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) {
+ iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+ }
+ }
+ return iloc;
+ }
+ private void setExistingXUserX(int userSetting, int iFlags, int iloc) {
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ // First install.
+ installFromRawResource("install.apk", R.raw.install,
+ iFlags,
+ false,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ int origSetting = getDefaultInstallLoc();
+ try {
+ // Set user setting
+ setInstallLoc(userSetting);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ rFlags,
+ true,
+ false, -1,
+ iloc);
+ } finally {
+ setInstallLoc(origSetting);
+ }
+ }
+ @MediumTest
+ public void testExistingIUserI() {
+ int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testExistingIUserE() {
+ int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testExistingIUserA() {
+ int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ }
+ @MediumTest
+ public void testExistingEUserI() {
+ int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ @MediumTest
+ public void testExistingEUserE() {
+ int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ @MediumTest
+ public void testExistingEUserA() {
+ int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int iFlags = PackageManager.INSTALL_EXTERNAL;
+ setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+ }
+ /*
+ * The following set of tests verify that the user setting defines
+ * the install location.
+ *
+ */
+ private boolean getUserSettingSetInstallLocation() {
+ try {
+ return Settings.System.getInt(mContext.getContentResolver(), Settings.Secure.SET_INSTALL_LOCATION) != 0;
+
+ } catch (SettingNotFoundException e1) {
+ }
+ return false;
+ }
+
+ private void setUserSettingSetInstallLocation(boolean value) {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.Secure.SET_INSTALL_LOCATION, value ? 1 : 0);
+ }
+ private void setUserX(boolean enable, int userSetting, int iloc) {
+ boolean origUserSetting = getUserSettingSetInstallLocation();
+ int origSetting = getDefaultInstallLoc();
+ try {
+ setUserSettingSetInstallLocation(enable);
+ // Set user setting
+ setInstallLoc(userSetting);
+ // Replace now
+ installFromRawResource("install.apk", R.raw.install,
+ 0,
+ true,
+ false, -1,
+ iloc);
+ } finally {
+ // Restore original setting
+ setUserSettingSetInstallLocation(origUserSetting);
+ setInstallLoc(origSetting);
+ }
+ }
+ @MediumTest
+ public void testUserI() {
+ int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int iloc = getExpectedInstallLocation(userSetting);
+ setUserX(true, userSetting, iloc);
+ }
+ @MediumTest
+ public void testUserE() {
+ int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int iloc = getExpectedInstallLocation(userSetting);
+ setUserX(true, userSetting, iloc);
+ }
+ @MediumTest
+ public void testUserA() {
+ int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int iloc = getExpectedInstallLocation(userSetting);
+ setUserX(true, userSetting, iloc);
+ }
+ /*
+ * The following set of tests turn on/off the basic
+ * user setting for turning on install location.
+ */
+ @MediumTest
+ public void testUserPrefOffUserI() {
+ int userSetting = PackageHelper.APP_INSTALL_INTERNAL;
+ int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ setUserX(false, userSetting, iloc);
+ }
+ @MediumTest
+ public void testUserPrefOffUserE() {
+ int userSetting = PackageHelper.APP_INSTALL_EXTERNAL;
+ int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ setUserX(false, userSetting, iloc);
+ }
+ @MediumTest
+ public void testUserPrefOffA() {
+ int userSetting = PackageHelper.APP_INSTALL_AUTO;
+ int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ setUserX(false, userSetting, iloc);
+ }
+
+ static final String BASE_PERMISSIONS_DEFINED[] = new String[] {
+ PERM_PACKAGE, "com.android.unit_tests.install_decl_perm",
+ PERM_DEFINED,
+ "com.android.frameworks.coretests.NORMAL",
+ "com.android.frameworks.coretests.DANGEROUS",
+ "com.android.frameworks.coretests.SIGNATURE",
+ };
+
+ static final String BASE_PERMISSIONS_UNDEFINED[] = new String[] {
+ PERM_PACKAGE, "com.android.frameworks.coretests.install_decl_perm",
+ PERM_UNDEFINED,
+ "com.android.frameworks.coretests.NORMAL",
+ "com.android.frameworks.coretests.DANGEROUS",
+ "com.android.frameworks.coretests.SIGNATURE",
+ };
+
+ static final String BASE_PERMISSIONS_USED[] = new String[] {
+ PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+ PERM_USED,
+ "com.android.frameworks.coretests.NORMAL",
+ "com.android.frameworks.coretests.DANGEROUS",
+ "com.android.frameworks.coretests.SIGNATURE",
+ };
+
+ static final String BASE_PERMISSIONS_NOTUSED[] = new String[] {
+ PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+ PERM_NOTUSED,
+ "com.android.frameworks.coretests.NORMAL",
+ "com.android.frameworks.coretests.DANGEROUS",
+ "com.android.frameworks.coretests.SIGNATURE",
+ };
+
+ static final String BASE_PERMISSIONS_SIGUSED[] = new String[] {
+ PERM_PACKAGE, "com.android.frameworks.coretests.install_use_perm_good",
+ PERM_USED,
+ "com.android.frameworks.coretests.SIGNATURE",
+ PERM_NOTUSED,
+ "com.android.frameworks.coretests.NORMAL",
+ "com.android.frameworks.coretests.DANGEROUS",
+ };
+
+ /*
+ * Ensure that permissions are properly declared.
+ */
+ @LargeTest
+ public void testInstallDeclaresPermissions() {
+ InstallParams ip = null;
+ InstallParams ip2 = null;
+ try {
+ // **: Upon installing a package, are its declared permissions published?
+
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int iApk = R.raw.install_decl_perm;
+ ip = installFromRawResource("install.apk", iApk,
+ iFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+
+ // **: Upon installing package, are its permissions granted?
+
+ int i2Flags = PackageManager.INSTALL_INTERNAL;
+ int i2Apk = R.raw.install_use_perm_good;
+ ip2 = installFromRawResource("install2.apk", i2Apk,
+ i2Flags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_USED);
+
+ // **: Upon removing but not deleting, are permissions retained?
+
+ GenericReceiver receiver = new DeleteReceiver(ip.pkg.packageName);
+
+ try {
+ invokeDeletePackage(ip.pkg.packageName, PackageManager.DONT_DELETE_DATA, receiver);
+ } catch (Exception e) {
+ failStr(e);
+ }
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+ assertPermissions(BASE_PERMISSIONS_USED);
+
+ // **: Upon re-installing, are permissions retained?
+
+ ip = installFromRawResource("install.apk", iApk,
+ iFlags | PackageManager.INSTALL_REPLACE_EXISTING, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+ assertPermissions(BASE_PERMISSIONS_USED);
+
+ // **: Upon deleting package, are all permissions removed?
+
+ try {
+ invokeDeletePackage(ip.pkg.packageName, 0, receiver);
+ ip = null;
+ } catch (Exception e) {
+ failStr(e);
+ }
+ assertPermissions(BASE_PERMISSIONS_UNDEFINED);
+ assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+ // **: Delete package using permissions; nothing to check here.
+
+ GenericReceiver receiver2 = new DeleteReceiver(ip2.pkg.packageName);
+ try {
+ invokeDeletePackage(ip2.pkg.packageName, 0, receiver);
+ ip2 = null;
+ } catch (Exception e) {
+ failStr(e);
+ }
+
+ // **: Re-install package using permissions; no permissions can be granted.
+
+ ip2 = installFromRawResource("install2.apk", i2Apk,
+ i2Flags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+ // **: Upon installing declaring package, are sig permissions granted
+ // to other apps (but not other perms)?
+
+ ip = installFromRawResource("install.apk", iApk,
+ iFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+ assertPermissions(BASE_PERMISSIONS_SIGUSED);
+
+ // **: Re-install package using permissions; are all permissions granted?
+
+ ip2 = installFromRawResource("install2.apk", i2Apk,
+ i2Flags | PackageManager.INSTALL_REPLACE_EXISTING, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip2.pkg, i2Flags, ip2.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+ // **: Upon deleting package, are all permissions removed?
+
+ try {
+ invokeDeletePackage(ip.pkg.packageName, 0, receiver);
+ ip = null;
+ } catch (Exception e) {
+ failStr(e);
+ }
+ assertPermissions(BASE_PERMISSIONS_UNDEFINED);
+ assertPermissions(BASE_PERMISSIONS_NOTUSED);
+
+ // **: Delete package using permissions; nothing to check here.
+
+ try {
+ invokeDeletePackage(ip2.pkg.packageName, 0, receiver);
+ ip2 = null;
+ } catch (Exception e) {
+ failStr(e);
+ }
+
+ } finally {
+ if (ip2 != null) {
+ cleanUpInstall(ip2);
+ }
+ if (ip != null) {
+ cleanUpInstall(ip);
+ }
+ }
+ }
+
+ /*
+ * Ensure that permissions are properly declared.
+ */
+ @MediumTest
+ public void testInstallOnSdPermissionsUnmount() {
+ InstallParams ip = null;
+ boolean origMediaState = getMediaState();
+ try {
+ // **: Upon installing a package, are its declared permissions published?
+ int iFlags = PackageManager.INSTALL_INTERNAL;
+ int iApk = R.raw.install_decl_perm;
+ ip = installFromRawResource("install.apk", iApk,
+ iFlags, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+ assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+ // Unmount media here
+ assertTrue(unmountMedia());
+ // Mount media again
+ mountMedia();
+ //Check permissions now
+ assertPermissions(BASE_PERMISSIONS_DEFINED);
+ } finally {
+ if (ip != null) {
+ cleanUpInstall(ip);
+ }
+ }
+ }
+
+ /* This test creates a stale container via MountService and then installs
+ * a package and verifies that the stale container is cleaned up and install
+ * is successful.
+ * Please note that this test is very closely tied to the framework's
+ * naming convention for secure containers.
+ */
+ @MediumTest
+ public void testInstallSdcardStaleContainer() {
+ boolean origMediaState = getMediaState();
+ try {
+ String outFileName = "install.apk";
+ int rawResId = R.raw.install;
+ PackageManager pm = mContext.getPackageManager();
+ File filesDir = mContext.getFilesDir();
+ File outFile = new File(filesDir, outFileName);
+ Uri packageURI = getInstallablePackage(rawResId, outFile);
+ PackageParser.Package pkg = parsePackage(packageURI);
+ assertNotNull(pkg);
+ // Install an app on sdcard.
+ installFromRawResource(outFileName, rawResId,
+ PackageManager.INSTALL_EXTERNAL, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ // Unmount sdcard
+ unmountMedia();
+ // Delete the app on sdcard to leave a stale container on sdcard.
+ GenericReceiver receiver = new DeleteReceiver(pkg.packageName);
+ assertTrue(invokeDeletePackage(pkg.packageName, 0, receiver));
+ mountMedia();
+ // Reinstall the app and make sure it gets installed.
+ installFromRawResource(outFileName, rawResId,
+ PackageManager.INSTALL_EXTERNAL, true,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ } catch (Exception e) {
+ failStr(e.getMessage());
+ } finally {
+ if (origMediaState) {
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+
+ }
+ }
+ /*
+ * The following series of tests are related to upgrading apps with
+ * different certificates.
+ */
+ private int APP1_UNSIGNED = R.raw.install_app1_unsigned;
+ private int APP1_CERT1 = R.raw.install_app1_cert1;
+ private int APP1_CERT2 = R.raw.install_app1_cert2;
+ private int APP1_CERT1_CERT2 = R.raw.install_app1_cert1_cert2;
+ private int APP1_CERT3_CERT4 = R.raw.install_app1_cert3_cert4;
+ private int APP1_CERT3 = R.raw.install_app1_cert3;
+ private int APP2_UNSIGNED = R.raw.install_app2_unsigned;
+ private int APP2_CERT1 = R.raw.install_app2_cert1;
+ private int APP2_CERT2 = R.raw.install_app2_cert2;
+ private int APP2_CERT1_CERT2 = R.raw.install_app2_cert1_cert2;
+ private int APP2_CERT3 = R.raw.install_app2_cert3;
+
+ private InstallParams replaceCerts(int apk1, int apk2, boolean cleanUp, boolean fail, int retCode) {
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ String apk1Name = "install1.apk";
+ String apk2Name = "install2.apk";
+ PackageParser.Package pkg1 = getParsedPackage(apk1Name, apk1);
+ try {
+ InstallParams ip = installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ installFromRawResource(apk2Name, apk2, rFlags, false,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ return ip;
+ } catch (Exception e) {
+ failStr(e.getMessage());
+ } finally {
+ if (cleanUp) {
+ cleanUpInstall(pkg1.packageName);
+ }
+ }
+ return null;
+ }
+ /*
+ * Test that an app signed with two certificates can be upgraded by the
+ * same app signed with two certificates.
+ */
+ @MediumTest
+ public void testReplaceMatchAllCerts() {
+ replaceCerts(APP1_CERT1_CERT2, APP1_CERT1_CERT2, true, false, -1);
+ }
+
+ /*
+ * Test that an app signed with two certificates cannot be upgraded
+ * by an app signed with a different certificate.
+ */
+ @MediumTest
+ public void testReplaceMatchNoCerts1() {
+ replaceCerts(APP1_CERT1_CERT2, APP1_CERT3, true, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ }
+ /*
+ * Test that an app signed with two certificates cannot be upgraded
+ * by an app signed with a different certificate.
+ */
+ @MediumTest
+ public void testReplaceMatchNoCerts2() {
+ replaceCerts(APP1_CERT1_CERT2, APP1_CERT3_CERT4, true, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ }
+ /*
+ * Test that an app signed with two certificates cannot be upgraded by
+ * an app signed with a subset of initial certificates.
+ */
+ @MediumTest
+ public void testReplaceMatchSomeCerts1() {
+ replaceCerts(APP1_CERT1_CERT2, APP1_CERT1, true, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ }
+ /*
+ * Test that an app signed with two certificates cannot be upgraded by
+ * an app signed with the last certificate.
+ */
+ @MediumTest
+ public void testReplaceMatchSomeCerts2() {
+ replaceCerts(APP1_CERT1_CERT2, APP1_CERT2, true, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ }
+ /*
+ * Test that an app signed with a certificate can be upgraded by app
+ * signed with a superset of certificates.
+ */
+ @MediumTest
+ public void testReplaceMatchMoreCerts() {
+ replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, true, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ }
+ /*
+ * Test that an app signed with a certificate can be upgraded by app
+ * signed with a superset of certificates. Then verify that the an app
+ * signed with the original set of certs cannot upgrade the new one.
+ */
+ @MediumTest
+ public void testReplaceMatchMoreCertsReplaceSomeCerts() {
+ InstallParams ip = replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, false, true,
+ PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES);
+ try {
+ int rFlags = PackageManager.INSTALL_REPLACE_EXISTING;
+ installFromRawResource("install.apk", APP1_CERT1, rFlags, false,
+ false, -1,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ } catch (Exception e) {
+ failStr(e.getMessage());
+ } finally {
+ if (ip != null) {
+ cleanUpInstall(ip);
+ }
+ }
+ }
+ /*
+ * The following tests are related to testing the checkSignatures
+ * api.
+ */
+ private void checkSignatures(int apk1, int apk2, int expMatchResult) {
+ checkSharedSignatures(apk1, apk2, true, false, -1, expMatchResult);
+ }
+ @MediumTest
+ public void testCheckSignaturesAllMatch() {
+ int apk1 = APP1_CERT1_CERT2;
+ int apk2 = APP2_CERT1_CERT2;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+ }
+ @MediumTest
+ public void testCheckSignaturesNoMatch() {
+ int apk1 = APP1_CERT1;
+ int apk2 = APP2_CERT2;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+ }
+ @MediumTest
+ public void testCheckSignaturesSomeMatch1() {
+ int apk1 = APP1_CERT1_CERT2;
+ int apk2 = APP2_CERT1;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+ }
+ @MediumTest
+ public void testCheckSignaturesSomeMatch2() {
+ int apk1 = APP1_CERT1_CERT2;
+ int apk2 = APP2_CERT2;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+ }
+ @MediumTest
+ public void testCheckSignaturesMoreMatch() {
+ int apk1 = APP1_CERT1;
+ int apk2 = APP2_CERT1_CERT2;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH);
+ }
+ @MediumTest
+ public void testCheckSignaturesUnknown() {
+ int apk1 = APP1_CERT1_CERT2;
+ int apk2 = APP2_CERT1_CERT2;
+ String apk1Name = "install1.apk";
+ String apk2Name = "install2.apk";
+ InstallParams ip1 = null;
+
+ try {
+ ip1 = installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ PackageManager pm = mContext.getPackageManager();
+ // Delete app2
+ File filesDir = mContext.getFilesDir();
+ File outFile = new File(filesDir, apk2Name);
+ int rawResId = apk2;
+ Uri packageURI = getInstallablePackage(rawResId, outFile);
+ PackageParser.Package pkg = parsePackage(packageURI);
+ getPm().deletePackage(pkg.packageName, null, 0);
+ // Check signatures now
+ int match = mContext.getPackageManager().checkSignatures(
+ ip1.pkg.packageName, pkg.packageName);
+ assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
+ } finally {
+ if (ip1 != null) {
+ cleanUpInstall(ip1);
+ }
+ }
+ }
+ @MediumTest
+ public void testInstallNoCertificates() {
+ int apk1 = APP1_UNSIGNED;
+ String apk1Name = "install1.apk";
+ InstallParams ip1 = null;
+
+ try {
+ installFromRawResource(apk1Name, apk1, 0, false,
+ true, PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+ PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ } finally {
+ }
+ }
+ /* The following tests are related to apps using shared uids signed
+ * with different certs.
+ */
+ private int SHARED1_UNSIGNED = R.raw.install_shared1_unsigned;
+ private int SHARED1_CERT1 = R.raw.install_shared1_cert1;
+ private int SHARED1_CERT2 = R.raw.install_shared1_cert2;
+ private int SHARED1_CERT1_CERT2 = R.raw.install_shared1_cert1_cert2;
+ private int SHARED2_UNSIGNED = R.raw.install_shared2_unsigned;
+ private int SHARED2_CERT1 = R.raw.install_shared2_cert1;
+ private int SHARED2_CERT2 = R.raw.install_shared2_cert2;
+ private int SHARED2_CERT1_CERT2 = R.raw.install_shared2_cert1_cert2;
+ private void checkSharedSignatures(int apk1, int apk2, boolean cleanUp, boolean fail, int retCode, int expMatchResult) {
+ String apk1Name = "install1.apk";
+ String apk2Name = "install2.apk";
+ PackageParser.Package pkg1 = getParsedPackage(apk1Name, apk1);
+ PackageParser.Package pkg2 = getParsedPackage(apk2Name, apk2);
+
+ try {
+ // Clean up before testing first.
+ cleanUpInstall(pkg1.packageName);
+ cleanUpInstall(pkg2.packageName);
+ installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ if (fail) {
+ installFromRawResource(apk2Name, apk2, 0, false,
+ true, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ } else {
+ installFromRawResource(apk2Name, apk2, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ int match = mContext.getPackageManager().checkSignatures(
+ pkg1.packageName, pkg2.packageName);
+ assertEquals(expMatchResult, match);
+ }
+ } finally {
+ if (cleanUp) {
+ cleanUpInstall(pkg1.packageName);
+ cleanUpInstall(pkg2.packageName);
+ }
+ }
+ }
+ @MediumTest
+ public void testCheckSignaturesSharedAllMatch() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT1_CERT2;
+ boolean fail = false;
+ int retCode = -1;
+ int expMatchResult = PackageManager.SIGNATURE_MATCH;
+ checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+ }
+ @MediumTest
+ public void testCheckSignaturesSharedNoMatch() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+ int expMatchResult = -1;
+ checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+ }
+ /*
+ * Test that an app signed with cert1 and cert2 cannot be replaced when signed with cert1 alone.
+ */
+ @MediumTest
+ public void testCheckSignaturesSharedSomeMatch1() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT1;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+ int expMatchResult = -1;
+ checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+ }
+ /*
+ * Test that an app signed with cert1 and cert2 cannot be replaced when signed with cert2 alone.
+ */
+ @MediumTest
+ public void testCheckSignaturesSharedSomeMatch2() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+ int expMatchResult = -1;
+ checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult);
+ }
+ @MediumTest
+ public void testCheckSignaturesSharedUnknown() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT1_CERT2;
+ String apk1Name = "install1.apk";
+ String apk2Name = "install2.apk";
+ InstallParams ip1 = null;
+
+ try {
+ ip1 = installFromRawResource(apk1Name, apk1, 0, false,
+ false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ PackageManager pm = mContext.getPackageManager();
+ // Delete app2
+ PackageParser.Package pkg = getParsedPackage(apk2Name, apk2);
+ getPm().deletePackage(pkg.packageName, null, 0);
+ // Check signatures now
+ int match = mContext.getPackageManager().checkSignatures(
+ ip1.pkg.packageName, pkg.packageName);
+ assertEquals(PackageManager.SIGNATURE_UNKNOWN_PACKAGE, match);
+ } finally {
+ if (ip1 != null) {
+ cleanUpInstall(ip1);
+ }
+ }
+ }
+
+ @MediumTest
+ public void testReplaceFirstSharedMatchAllCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk1 = SHARED1_CERT1;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+ replaceCerts(apk1, rapk1, true, false, -1);
+ }
+ @MediumTest
+ public void testReplaceSecondSharedMatchAllCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk2 = SHARED2_CERT1;
+ checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH);
+ replaceCerts(apk2, rapk2, true, false, -1);
+ }
+ @MediumTest
+ public void testReplaceFirstSharedMatchSomeCerts() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT1_CERT2;
+ int rapk1 = SHARED1_CERT1;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ @MediumTest
+ public void testReplaceSecondSharedMatchSomeCerts() {
+ int apk1 = SHARED1_CERT1_CERT2;
+ int apk2 = SHARED2_CERT1_CERT2;
+ int rapk2 = SHARED2_CERT1;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ @MediumTest
+ public void testReplaceFirstSharedMatchNoCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk1 = SHARED1_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ @MediumTest
+ public void testReplaceSecondSharedMatchNoCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk2 = SHARED2_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ @MediumTest
+ public void testReplaceFirstSharedMatchMoreCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk1 = SHARED1_CERT1_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ @MediumTest
+ public void testReplaceSecondSharedMatchMoreCerts() {
+ int apk1 = SHARED1_CERT1;
+ int apk2 = SHARED2_CERT1;
+ int rapk2 = SHARED2_CERT1_CERT2;
+ boolean fail = true;
+ int retCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ checkSharedSignatures(apk1, apk2, false, false, -1, PackageManager.SIGNATURE_MATCH);
+ installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true,
+ fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
+ }
+ /*---------- Recommended install location tests ----*/
+ /*
+ * TODO's
+ * check version numbers for upgrades
+ * check permissions of installed packages
+ * how to do tests on updated system apps?
+ * verify updates to system apps cannot be installed on the sdcard.
+ */
+}
diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java
new file mode 100644
index 0000000..07e75cb
--- /dev/null
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.database.AbstractCursor;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.common.ArrayListCursor;
+import android.database.CursorWindow;
+import android.test.PerformanceTestCase;
+
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class CursorWindowTest extends TestCase implements PerformanceTestCase {
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ @SmallTest
+ public void testWriteCursorToWindow() throws Exception {
+ // create cursor
+ String[] colNames = new String[]{"name", "number", "profit"};
+ int colsize = colNames.length;
+ ArrayList<ArrayList> list = createTestList(10, colsize);
+ AbstractCursor cursor = new ArrayListCursor(colNames, (ArrayList<ArrayList>) list);
+
+ // fill window
+ CursorWindow window = new CursorWindow(false);
+ cursor.fillWindow(0, window);
+
+ // read from cursor window
+ for (int i = 0; i < list.size(); i++) {
+ ArrayList<Integer> col = list.get(i);
+ for (int j = 0; j < colsize; j++) {
+ String s = window.getString(i, j);
+ int r2 = col.get(j);
+ int r1 = Integer.parseInt(s);
+ assertEquals(r2, r1);
+ }
+ }
+
+ // test cursor window handle startpos != 0
+ window.clear();
+ cursor.fillWindow(1, window);
+ // read from cursor from window
+ for (int i = 1; i < list.size(); i++) {
+ ArrayList<Integer> col = list.get(i);
+ for (int j = 0; j < colsize; j++) {
+ String s = window.getString(i, j);
+ int r2 = col.get(j);
+ int r1 = Integer.parseInt(s);
+ assertEquals(r2, r1);
+ }
+ }
+
+ // Clear the window and make sure it's empty
+ window.clear();
+ assertEquals(0, window.getNumRows());
+ }
+
+ @SmallTest
+ public void testValuesLocalWindow() {
+ doTestValues(new CursorWindow(true));
+ }
+
+ @SmallTest
+ public void testValuesRemoteWindow() {
+ doTestValues(new CursorWindow(false));
+ }
+
+ private void doTestValues(CursorWindow window) {
+ assertTrue(window.setNumColumns(7));
+ assertTrue(window.allocRow());
+ double db1 = 1.26;
+ assertTrue(window.putDouble(db1, 0, 0));
+ double db2 = window.getDouble(0, 0);
+ assertEquals(db1, db2);
+
+ long int1 = Long.MAX_VALUE;
+ assertTrue(window.putLong(int1, 0, 1));
+ long int2 = window.getLong(0, 1);
+ assertEquals(int1, int2);
+
+ assertTrue(window.putString("1198032740000", 0, 3));
+ assertEquals("1198032740000", window.getString(0, 3));
+ assertEquals(1198032740000L, window.getLong(0, 3));
+
+ assertTrue(window.putString(Long.toString(1198032740000L), 0, 3));
+ assertEquals(Long.toString(1198032740000L), window.getString(0, 3));
+ assertEquals(1198032740000L, window.getLong(0, 3));
+
+ assertTrue(window.putString(Double.toString(42.0), 0, 4));
+ assertEquals(Double.toString(42.0), window.getString(0, 4));
+ assertEquals(42.0, window.getDouble(0, 4));
+
+ // put blob
+ byte[] blob = new byte[1000];
+ byte value = 99;
+ Arrays.fill(blob, value);
+ assertTrue(window.putBlob(blob, 0, 6));
+ assertTrue(Arrays.equals(blob, window.getBlob(0, 6)));
+ }
+
+ @SmallTest
+ public void testNull() {
+ CursorWindow window = getOneByOneWindow();
+
+ // Put in a null value and read it back as various types
+ assertTrue(window.putNull(0, 0));
+ assertNull(window.getString(0, 0));
+ assertEquals(0, window.getLong(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0));
+ assertNull(window.getBlob(0, 0));
+ }
+
+ @SmallTest
+ public void testEmptyString() {
+ CursorWindow window = getOneByOneWindow();
+
+ // put size 0 string and read it back as various types
+ assertTrue(window.putString("", 0, 0));
+ assertEquals("", window.getString(0, 0));
+ assertEquals(0, window.getLong(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0));
+ }
+
+ private CursorWindow getOneByOneWindow() {
+ CursorWindow window = new CursorWindow(false);
+ assertTrue(window.setNumColumns(1));
+ assertTrue(window.allocRow());
+ return window;
+ }
+
+ private static ArrayList<ArrayList> createTestList(int rows, int cols) {
+ ArrayList<ArrayList> list = Lists.newArrayList();
+ Random generator = new Random();
+
+ for (int i = 0; i < rows; i++) {
+ ArrayList<Integer> col = Lists.newArrayList();
+ list.add(col);
+ for (int j = 0; j < cols; j++) {
+ // generate random number
+ Integer r = generator.nextInt();
+ col.add(r);
+ }
+ }
+ return list;
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseCursorTest.java b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
new file mode 100644
index 0000000..fb5a36f
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseCursorTest.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import dalvik.annotation.BrokenTest;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.CursorIndexOutOfBoundsException;
+import android.database.DataSetObserver;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteCursorDriver;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQuery;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTestCase {
+
+ private static final String sString1 = "this is a test";
+ private static final String sString2 = "and yet another test";
+ private static final String sString3 = "this string is a little longer, but still a test";
+
+ private static final int CURRENT_DATABASE_VERSION = 42;
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ private void populateDefaultTable() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
+ }
+
+ @MediumTest
+ public void testCursorUpdate() {
+ mDatabase.execSQL(
+ "CREATE TABLE test (_id INTEGER PRIMARY KEY, d INTEGER, s INTEGER);");
+ for(int i = 0; i < 20; i++) {
+ mDatabase.execSQL("INSERT INTO test (d, s) VALUES (" + i +
+ "," + i%2 + ");");
+ }
+
+ Cursor c = mDatabase.query("test", null, "s = 0", null, null, null, null);
+ int dCol = c.getColumnIndexOrThrow("d");
+ int sCol = c.getColumnIndexOrThrow("s");
+
+ int count = 0;
+ while (c.moveToNext()) {
+ assertTrue(c.updateInt(dCol, 3));
+ count++;
+ }
+ assertEquals(10, count);
+
+ assertTrue(c.commitUpdates());
+
+ assertTrue(c.requery());
+
+ count = 0;
+ while (c.moveToNext()) {
+ assertEquals(3, c.getInt(dCol));
+ count++;
+ }
+
+ assertEquals(10, count);
+ assertTrue(c.moveToFirst());
+ assertTrue(c.deleteRow());
+ assertEquals(9, c.getCount());
+ c.close();
+ }
+
+ @MediumTest
+ public void testBlob() throws Exception {
+ // create table
+ mDatabase.execSQL(
+ "CREATE TABLE test (_id INTEGER PRIMARY KEY, s TEXT, d REAL, l INTEGER, b BLOB);");
+ // insert blob
+ Object[] args = new Object[4];
+
+ byte[] blob = new byte[1000];
+ byte value = 99;
+ Arrays.fill(blob, value);
+ args[3] = blob;
+
+ String s = new String("text");
+ args[0] = s;
+ Double d = 99.9;
+ args[1] = d;
+ Long l = (long)1000;
+ args[2] = l;
+
+ String sql = "INSERT INTO test (s, d, l, b) VALUES (?,?,?,?)";
+ mDatabase.execSQL(sql, args);
+ // use cursor to access blob
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ c.moveToNext();
+ ContentValues cv = new ContentValues();
+ DatabaseUtils.cursorRowToContentValues(c, cv);
+
+ int bCol = c.getColumnIndexOrThrow("b");
+ int sCol = c.getColumnIndexOrThrow("s");
+ int dCol = c.getColumnIndexOrThrow("d");
+ int lCol = c.getColumnIndexOrThrow("l");
+ byte[] cBlob = c.getBlob(bCol);
+ assertTrue(Arrays.equals(blob, cBlob));
+ assertEquals(s, c.getString(sCol));
+ assertEquals((double)d, c.getDouble(dCol));
+ assertEquals((long)l, c.getLong(lCol));
+
+ // new byte[]
+ byte[] newblob = new byte[1000];
+ value = 98;
+ Arrays.fill(blob, value);
+
+ c.updateBlob(bCol, newblob);
+ cBlob = c.getBlob(bCol);
+ assertTrue(Arrays.equals(newblob, cBlob));
+
+ // commit
+ assertTrue(c.commitUpdates());
+ assertTrue(c.requery());
+ c.moveToNext();
+ cBlob = c.getBlob(bCol);
+ assertTrue(Arrays.equals(newblob, cBlob));
+ c.close();
+ }
+
+ @MediumTest
+ public void testRealColumns() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data REAL);");
+ ContentValues values = new ContentValues();
+ values.put("data", 42.11);
+ long id = mDatabase.insert("test", "data", values);
+ assertTrue(id > 0);
+ Cursor c = mDatabase.rawQuery("SELECT data FROM test", null);
+ assertNotNull(c);
+ assertTrue(c.moveToFirst());
+ assertEquals(42.11, c.getDouble(0));
+ c.close();
+ }
+
+ @MediumTest
+ public void testCursor1() throws Exception {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+
+ int dataColumn = c.getColumnIndexOrThrow("data");
+
+ // The cursor should ignore text before the last period when looking for a column. (This
+ // is a temporary hack in all implementations of getColumnIndex.)
+ int dataColumn2 = c.getColumnIndexOrThrow("junk.data");
+ assertEquals(dataColumn, dataColumn2);
+
+ assertSame(3, c.getCount());
+
+ assertTrue(c.isBeforeFirst());
+
+ try {
+ c.getInt(0);
+ fail("CursorIndexOutOfBoundsException expected");
+ } catch (CursorIndexOutOfBoundsException ex) {
+ // expected
+ }
+
+ c.moveToNext();
+ assertEquals(1, c.getInt(0));
+
+ String s = c.getString(dataColumn);
+ assertEquals(sString1, s);
+
+ c.moveToNext();
+ s = c.getString(dataColumn);
+ assertEquals(sString2, s);
+
+ c.moveToNext();
+ s = c.getString(dataColumn);
+ assertEquals(sString3, s);
+
+ c.moveToPosition(-1);
+ c.moveToNext();
+ s = c.getString(dataColumn);
+ assertEquals(sString1, s);
+
+ c.moveToPosition(2);
+ s = c.getString(dataColumn);
+ assertEquals(sString3, s);
+
+ int i;
+
+ for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) {
+ c.getInt(0);
+ }
+
+ assertEquals(3, i);
+
+ try {
+ c.getInt(0);
+ fail("CursorIndexOutOfBoundsException expected");
+ } catch (CursorIndexOutOfBoundsException ex) {
+ // expected
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testCursor2() throws Exception {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.query("test", null, "_id > 1000", null, null, null, null);
+ assertEquals(0, c.getCount());
+ assertTrue(c.isBeforeFirst());
+
+ try {
+ c.getInt(0);
+ fail("CursorIndexOutOfBoundsException expected");
+ } catch (CursorIndexOutOfBoundsException ex) {
+ // expected
+ }
+
+ int i;
+ for (c.moveToFirst(), i = 0; !c.isAfterLast(); c.moveToNext(), i++) {
+ c.getInt(0);
+ }
+ assertEquals(0, i);
+ try {
+ c.getInt(0);
+ fail("CursorIndexOutOfBoundsException expected");
+ } catch (CursorIndexOutOfBoundsException ex) {
+ // expected
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testLargeField() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ StringBuilder sql = new StringBuilder(2100);
+ sql.append("INSERT INTO test (data) VALUES ('");
+ Random random = new Random(System.currentTimeMillis());
+ StringBuilder randomString = new StringBuilder(1979);
+ for (int i = 0; i < 1979; i++) {
+ randomString.append((random.nextInt() & 0xf) % 10);
+ }
+ sql.append(randomString);
+ sql.append("');");
+ mDatabase.execSQL(sql.toString());
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+
+ assertTrue(c.moveToFirst());
+ assertEquals(0, c.getPosition());
+ String largeString = c.getString(c.getColumnIndexOrThrow("data"));
+ assertNotNull(largeString);
+ assertEquals(randomString.toString(), largeString);
+ c.close();
+ }
+
+ class TestObserver extends DataSetObserver {
+ int total;
+ SQLiteCursor c;
+ boolean quit = false;
+ public TestObserver(int total_, SQLiteCursor cursor) {
+ c = cursor;
+ total = total_;
+ }
+
+ @Override
+ public void onChanged() {
+ int count = c.getCount();
+ if (total == count) {
+ int i = 0;
+ while (c.moveToNext()) {
+ assertEquals(i, c.getInt(1));
+ i++;
+ }
+ assertEquals(count, i);
+ quit = true;
+ Looper.myLooper().quit();
+ }
+ }
+
+ @Override
+ public void onInvalidated() {
+ }
+ }
+
+ //@Large
+ @Suppress
+ public void testLoadingThreadDelayRegisterData() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+ final int count = 505;
+ String sql = "INSERT INTO test (data) VALUES (?);";
+ SQLiteStatement s = mDatabase.compileStatement(sql);
+ for (int i = 0; i < count; i++) {
+ s.bindLong(1, i);
+ s.execute();
+ }
+
+ int maxRead = 500;
+ int initialRead = 5;
+ SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
+ null, initialRead, maxRead);
+
+ TestObserver observer = new TestObserver(count, c);
+ c.getCount();
+ c.registerDataSetObserver(observer);
+ if (!observer.quit) {
+ Looper.loop();
+ }
+ c.close();
+ }
+
+ //@LargeTest
+ @BrokenTest("Consistently times out")
+ @Suppress
+ public void testLoadingThread() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+ final int count = 50000;
+ String sql = "INSERT INTO test (data) VALUES (?);";
+ SQLiteStatement s = mDatabase.compileStatement(sql);
+ for (int i = 0; i < count; i++) {
+ s.bindLong(1, i);
+ s.execute();
+ }
+
+ int maxRead = 1000;
+ int initialRead = 5;
+ SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
+ null, initialRead, maxRead);
+
+ TestObserver observer = new TestObserver(count, c);
+ c.registerDataSetObserver(observer);
+ c.getCount();
+
+ Looper.loop();
+ c.close();
+ }
+
+ //@LargeTest
+ @BrokenTest("Consistently times out")
+ @Suppress
+ public void testLoadingThreadClose() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+ final int count = 1000;
+ String sql = "INSERT INTO test (data) VALUES (?);";
+ SQLiteStatement s = mDatabase.compileStatement(sql);
+ for (int i = 0; i < count; i++) {
+ s.bindLong(1, i);
+ s.execute();
+ }
+
+ int maxRead = 11;
+ int initialRead = 5;
+ SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
+ null, initialRead, maxRead);
+
+ TestObserver observer = new TestObserver(count, c);
+ c.registerDataSetObserver(observer);
+ c.getCount();
+ c.close();
+ }
+
+ @LargeTest
+ public void testLoadingThreadDeactivate() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+ final int count = 1000;
+ String sql = "INSERT INTO test (data) VALUES (?);";
+ SQLiteStatement s = mDatabase.compileStatement(sql);
+ for (int i = 0; i < count; i++) {
+ s.bindLong(1, i);
+ s.execute();
+ }
+
+ int maxRead = 11;
+ int initialRead = 5;
+ SQLiteCursor c = (SQLiteCursor)mDatabase.rawQuery("select * from test;",
+ null, initialRead, maxRead);
+
+ TestObserver observer = new TestObserver(count, c);
+ c.registerDataSetObserver(observer);
+ c.getCount();
+ c.deactivate();
+ c.close();
+ }
+
+ @LargeTest
+ public void testManyRowsLong() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data INT);");
+
+ final int count = 36799;
+ mDatabase.execSQL("BEGIN Transaction;");
+ for (int i = 0; i < count; i++) {
+ mDatabase.execSQL("INSERT INTO test (data) VALUES (" + i + ");");
+ }
+ mDatabase.execSQL("COMMIT;");
+
+ Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null);
+ assertNotNull(c);
+
+ int i = 0;
+ while (c.moveToNext()) {
+ assertEquals(i, c.getInt(0));
+ i++;
+ }
+ assertEquals(count, i);
+ assertEquals(count, c.getCount());
+
+ Log.d("testManyRows", "count " + Integer.toString(i));
+ c.close();
+ }
+
+ @LargeTest
+ public void testManyRowsTxt() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+ StringBuilder sql = new StringBuilder(2100);
+ sql.append("INSERT INTO test (data) VALUES ('");
+ Random random = new Random(System.currentTimeMillis());
+ StringBuilder randomString = new StringBuilder(1979);
+ for (int i = 0; i < 1979; i++) {
+ randomString.append((random.nextInt() & 0xf) % 10);
+ }
+ sql.append(randomString);
+ sql.append("');");
+
+ // if cursor window size changed, adjust this value too
+ final int count = 600; // more than two fillWindow needed
+ mDatabase.execSQL("BEGIN Transaction;");
+ for (int i = 0; i < count; i++) {
+ mDatabase.execSQL(sql.toString());
+ }
+ mDatabase.execSQL("COMMIT;");
+
+ Cursor c = mDatabase.query("test", new String[]{"data"}, null, null, null, null, null);
+ assertNotNull(c);
+
+ int i = 0;
+ while (c.moveToNext()) {
+ assertEquals(randomString.toString(), c.getString(0));
+ i++;
+ }
+ assertEquals(count, i);
+ assertEquals(count, c.getCount());
+ c.close();
+ }
+
+ @LargeTest
+ public void testManyRowsTxtLong() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, txt TEXT, data INT);");
+
+ Random random = new Random(System.currentTimeMillis());
+ StringBuilder randomString = new StringBuilder(1979);
+ for (int i = 0; i < 1979; i++) {
+ randomString.append((random.nextInt() & 0xf) % 10);
+ }
+
+ // if cursor window size changed, adjust this value too
+ final int count = 600;
+ mDatabase.execSQL("BEGIN Transaction;");
+ for (int i = 0; i < count; i++) {
+ StringBuilder sql = new StringBuilder(2100);
+ sql.append("INSERT INTO test (txt, data) VALUES ('");
+ sql.append(randomString);
+ sql.append("','");
+ sql.append(i);
+ sql.append("');");
+ mDatabase.execSQL(sql.toString());
+ }
+ mDatabase.execSQL("COMMIT;");
+
+ Cursor c = mDatabase.query("test", new String[]{"txt", "data"}, null, null, null, null, null);
+ assertNotNull(c);
+
+ int i = 0;
+ while (c.moveToNext()) {
+ assertEquals(randomString.toString(), c.getString(0));
+ assertEquals(i, c.getInt(1));
+ i++;
+ }
+ assertEquals(count, i);
+ assertEquals(count, c.getCount());
+ c.close();
+ }
+
+ @MediumTest
+ public void testRequery() throws Exception {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.rawQuery("SELECT * FROM test", null);
+ assertNotNull(c);
+ assertEquals(3, c.getCount());
+ c.deactivate();
+ c.requery();
+ assertEquals(3, c.getCount());
+ c.close();
+ }
+
+ @MediumTest
+ public void testRequeryWithSelection() throws Exception {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = '" + sString1 + "'",
+ null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+ c.deactivate();
+ c.requery();
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+ c.close();
+ }
+
+ @MediumTest
+ public void testRequeryWithSelectionArgs() throws Exception {
+ populateDefaultTable();
+
+ Cursor c = mDatabase.rawQuery("SELECT data FROM test WHERE data = ?",
+ new String[]{sString1});
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+ c.deactivate();
+ c.requery();
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+ c.close();
+ }
+
+ @MediumTest
+ public void testRequeryWithAlteredSelectionArgs() throws Exception {
+ /**
+ * Test the ability of a subclass of SQLiteCursor to change its query arguments.
+ */
+ populateDefaultTable();
+
+ SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
+ public Cursor newCursor(
+ SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable,
+ SQLiteQuery query) {
+ return new SQLiteCursor(db, masterQuery, editTable, query) {
+ @Override
+ public boolean requery() {
+ setSelectionArguments(new String[]{"2"});
+ return super.requery();
+ }
+ };
+ }
+ };
+ Cursor c = mDatabase.rawQueryWithFactory(
+ factory, "SELECT data FROM test WHERE _id <= ?", new String[]{"1"},
+ null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+
+ // Our hacked requery() changes the query arguments in the cursor.
+ c.requery();
+
+ assertEquals(2, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals(sString1, c.getString(0));
+ assertTrue(c.moveToNext());
+ assertEquals(sString2, c.getString(0));
+
+ // Test that setting query args on a deactivated cursor also works.
+ c.deactivate();
+ c.requery();
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
new file mode 100644
index 0000000..656029d
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -0,0 +1,1112 @@
+/*
+ * 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.database;
+
+import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_COLUMNNAME_INDEX;
+import static android.database.DatabaseUtils.InsertHelper.TABLE_INFO_PRAGMA_DEFAULT_INDEX;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Handler;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Locale;
+
+public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceTestCase {
+ private static final String TAG = "DatabaseGeneralTest";
+
+ private static final String sString1 = "this is a test";
+ private static final String sString2 = "and yet another test";
+ private static final String sString3 = "this string is a little longer, but still a test";
+ private static final String PHONE_NUMBER = "16175551212";
+
+ private static final int CURRENT_DATABASE_VERSION = 42;
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ private void populateDefaultTable() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
+ }
+
+ @MediumTest
+ public void testVersion() throws Exception {
+ assertEquals(CURRENT_DATABASE_VERSION, mDatabase.getVersion());
+ mDatabase.setVersion(11);
+ assertEquals(11, mDatabase.getVersion());
+ }
+
+ @MediumTest
+ public void testUpdate() throws Exception {
+ populateDefaultTable();
+
+ ContentValues values = new ContentValues(1);
+ values.put("data", "this is an updated test");
+ assertEquals(1, mDatabase.update("test", values, "_id=1", null));
+ Cursor c = mDatabase.query("test", null, "_id=1", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ String value = c.getString(c.getColumnIndexOrThrow("data"));
+ assertEquals("this is an updated test", value);
+ }
+
+ @MediumTest
+ public void testPhoneNumbersEqual() throws Exception {
+ mDatabase.execSQL("CREATE TABLE phones (num TEXT);");
+ mDatabase.execSQL("INSERT INTO phones (num) VALUES ('911');");
+ mDatabase.execSQL("INSERT INTO phones (num) VALUES ('5555');");
+ mDatabase.execSQL("INSERT INTO phones (num) VALUES ('+" + PHONE_NUMBER + "');");
+
+ String number;
+ Cursor c;
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '504-555-7683')", null, null, null, null);
+ assertTrue(c == null || c.getCount() == 0);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '911')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("911", number);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '5555')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("5555", number);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '180055555555')", null, null, null, null);
+ assertTrue(c == null || c.getCount() == 0);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '+" + PHONE_NUMBER + "')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '+1 (617).555-1212')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '" + PHONE_NUMBER + "')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+
+ /*
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '5551212')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+ */
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '011" + PHONE_NUMBER + "')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+
+ c = mDatabase.query("phones", null,
+ "PHONE_NUMBERS_EQUAL(num, '00" + PHONE_NUMBER + "')", null, null, null, null);
+ assertNotNull(c);
+ assertEquals(1, c.getCount());
+ c.moveToFirst();
+ number = c.getString(c.getColumnIndexOrThrow("num"));
+ assertEquals("+" + PHONE_NUMBER, number);
+ c.close();
+ }
+
+ private void phoneNumberCompare(String phone1, String phone2, boolean equal,
+ boolean useStrictComparation) {
+ String[] temporalPhoneNumbers = new String[2];
+ temporalPhoneNumbers[0] = phone1;
+ temporalPhoneNumbers[1] = phone2;
+
+ Cursor cursor = mDatabase.rawQuery(
+ String.format(
+ "SELECT CASE WHEN PHONE_NUMBERS_EQUAL(?, ?, %d) " +
+ "THEN 'equal' ELSE 'not equal' END",
+ (useStrictComparation ? 1 : 0)),
+ temporalPhoneNumbers);
+ try {
+ assertNotNull(cursor);
+ assertTrue(cursor.moveToFirst());
+ if (equal) {
+ assertEquals(String.format("Unexpectedly, \"%s != %s\".", phone1, phone2),
+ "equal", cursor.getString(0));
+ } else {
+ assertEquals(String.format("Unexpectedly, \"%s\" == \"%s\".", phone1, phone2),
+ "not equal", cursor.getString(0));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ private void assertPhoneNumberEqual(String phone1, String phone2) throws Exception {
+ assertPhoneNumberEqual(phone1, phone2, true);
+ assertPhoneNumberEqual(phone1, phone2, false);
+ }
+
+ private void assertPhoneNumberEqual(String phone1, String phone2, boolean useStrict)
+ throws Exception {
+ phoneNumberCompare(phone1, phone2, true, useStrict);
+ }
+
+ private void assertPhoneNumberNotEqual(String phone1, String phone2) throws Exception {
+ assertPhoneNumberNotEqual(phone1, phone2, true);
+ assertPhoneNumberNotEqual(phone1, phone2, false);
+ }
+
+ private void assertPhoneNumberNotEqual(String phone1, String phone2, boolean useStrict)
+ throws Exception {
+ phoneNumberCompare(phone1, phone2, false, useStrict);
+ }
+
+ /**
+ * Tests international matching issues for the PHONE_NUMBERS_EQUAL function.
+ *
+ * @throws Exception
+ */
+ @SmallTest
+ public void testPhoneNumbersEqualInternationl() throws Exception {
+ assertPhoneNumberEqual("1", "1");
+ assertPhoneNumberEqual("123123", "123123");
+ assertPhoneNumberNotEqual("123123", "923123");
+ assertPhoneNumberNotEqual("123123", "123129");
+ assertPhoneNumberNotEqual("123123", "1231234");
+ assertPhoneNumberNotEqual("123123", "0123123", false);
+ assertPhoneNumberNotEqual("123123", "0123123", true);
+ assertPhoneNumberEqual("650-253-0000", "6502530000");
+ assertPhoneNumberEqual("650-253-0000", "650 253 0000");
+ assertPhoneNumberEqual("650 253 0000", "6502530000");
+ assertPhoneNumberEqual("+1 650-253-0000", "6502530000");
+ assertPhoneNumberEqual("001 650-253-0000", "6502530000");
+ assertPhoneNumberEqual("0111 650-253-0000", "6502530000");
+
+ // Russian trunk digit
+ assertPhoneNumberEqual("+79161234567", "89161234567");
+
+ // French trunk digit
+ assertPhoneNumberEqual("+33123456789", "0123456789");
+
+ // Trunk digit for city codes in the Netherlands
+ assertPhoneNumberEqual("+31771234567", "0771234567");
+
+ // Test broken caller ID seen on call from Thailand to the US
+ assertPhoneNumberEqual("+66811234567", "166811234567");
+
+ // Test the same in-country number with different country codes
+ assertPhoneNumberNotEqual("+33123456789", "+1123456789");
+
+ // Test one number with country code and the other without
+ assertPhoneNumberEqual("5125551212", "+15125551212");
+
+ // Test two NANP numbers that only differ in the area code
+ assertPhoneNumberNotEqual("5125551212", "6505551212");
+
+ // Japanese phone numbers
+ assertPhoneNumberEqual("090-1234-5678", "+819012345678");
+ assertPhoneNumberEqual("090(1234)5678", "+819012345678");
+ assertPhoneNumberEqual("090-1234-5678", "+81-90-1234-5678");
+
+ // Equador
+ assertPhoneNumberEqual("+593(800)123-1234", "8001231234");
+ assertPhoneNumberEqual("+593-2-1234-123", "21234123");
+
+ // Two continuous 0 at the beginning of the phone string should not be
+ // treated as trunk prefix in the strict comparation.
+ assertPhoneNumberEqual("008001231234", "8001231234", false);
+ assertPhoneNumberNotEqual("008001231234", "8001231234", true);
+
+ // Confirm that the bug found before does not re-appear in the strict compalation
+ assertPhoneNumberEqual("080-1234-5678", "+819012345678", false);
+ assertPhoneNumberNotEqual("080-1234-5678", "+819012345678", true);
+ }
+
+ @MediumTest
+ public void testCopyString() throws Exception {
+ mDatabase.execSQL("CREATE TABLE guess (numi INTEGER, numf FLOAT, str TEXT);");
+ mDatabase.execSQL(
+ "INSERT INTO guess (numi,numf,str) VALUES (0,0.0,'ZoomZoomZoomZoom');");
+ mDatabase.execSQL("INSERT INTO guess (numi,numf,str) VALUES (2000000000,3.1415926535,'');");
+ String chinese = "\u4eac\u4ec5 \u5c3d\u5f84\u60ca";
+ String[] arr = new String[1];
+ arr[0] = chinese;
+ mDatabase.execSQL("INSERT INTO guess (numi,numf,str) VALUES (-32768,-1.0,?)", arr);
+
+ Cursor c;
+
+ c = mDatabase.rawQuery("SELECT * FROM guess", null);
+
+ c.moveToFirst();
+
+ CharArrayBuffer buf = new CharArrayBuffer(14);
+
+ String compareTo = c.getString(c.getColumnIndexOrThrow("numi"));
+ int numiIdx = c.getColumnIndexOrThrow("numi");
+ int numfIdx = c.getColumnIndexOrThrow("numf");
+ int strIdx = c.getColumnIndexOrThrow("str");
+
+ c.copyStringToBuffer(numiIdx, buf);
+ assertEquals(1, buf.sizeCopied);
+ assertEquals(compareTo, new String(buf.data, 0, buf.sizeCopied));
+
+ c.copyStringToBuffer(strIdx, buf);
+ assertEquals("ZoomZoomZoomZoom", new String(buf.data, 0, buf.sizeCopied));
+
+ c.moveToNext();
+ compareTo = c.getString(numfIdx);
+
+ c.copyStringToBuffer(numfIdx, buf);
+ assertEquals(compareTo, new String(buf.data, 0, buf.sizeCopied));
+ c.copyStringToBuffer(strIdx, buf);
+ assertEquals(0, buf.sizeCopied);
+
+ c.moveToNext();
+ c.copyStringToBuffer(numfIdx, buf);
+ assertEquals(-1.0, Double.valueOf(
+ new String(buf.data, 0, buf.sizeCopied)).doubleValue());
+
+ c.copyStringToBuffer(strIdx, buf);
+ compareTo = c.getString(strIdx);
+ assertEquals(chinese, compareTo);
+
+ assertEquals(chinese, new String(buf.data, 0, buf.sizeCopied));
+ c.close();
+ }
+
+ @MediumTest
+ public void testSchemaChange1() throws Exception {
+ SQLiteDatabase db1 = mDatabase;
+ Cursor cursor;
+
+ db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ cursor = db1.query("db1", null, null, null, null, null, null);
+ assertNotNull("Cursor is null", cursor);
+
+ db1.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ assertEquals(0, cursor.getCount());
+ cursor.deactivate();
+ }
+
+ @MediumTest
+ public void testSchemaChange2() throws Exception {
+ SQLiteDatabase db1 = mDatabase;
+ SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
+ Cursor cursor;
+
+ db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ cursor = db1.query("db1", null, null, null, null, null, null);
+ assertNotNull("Cursor is null", cursor);
+ assertEquals(0, cursor.getCount());
+ cursor.deactivate();
+ // this cause exception because we're still using sqlite_prepate16 and not
+ // sqlite_prepare16_v2. The v2 variant added the ability to check the
+ // schema version and handle the case when the schema has changed
+ // Marco Nelissen claim it was 2x slower to compile SQL statements so
+ // I reverted back to the v1 variant.
+ /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ cursor = db1.query("db1", null, null, null, null, null, null);
+ assertNotNull("Cursor is null", cursor);
+ assertEquals(0, cursor.count());
+ cursor.deactivate();
+ */
+ }
+
+ @MediumTest
+ public void testSchemaChange3() throws Exception {
+ SQLiteDatabase db1 = mDatabase;
+ SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
+ Cursor cursor;
+
+
+ db1.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
+ db1.execSQL("INSERT INTO db1 (data) VALUES ('test');");
+
+ cursor = db1.query("db1", null, null, null, null, null, null);
+ // this cause exception because we're still using sqlite_prepate16 and not
+ // sqlite_prepare16_v2. The v2 variant added the ability to check the
+ // schema version and handle the case when the schema has changed
+ // Marco Nelissen claim it was 2x slower to compile SQL statements so
+ // I reverted back to the v1 variant.
+ /* db2.execSQL("CREATE TABLE db2 (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ assertNotNull("Cursor is null", cursor);
+ assertEquals(1, cursor.count());
+ assertTrue(cursor.first());
+ assertEquals("test", cursor.getString(cursor.getColumnIndexOrThrow("data")));
+ cursor.deactivate();
+ */
+ }
+
+ private class ChangeObserver extends ContentObserver {
+ private int mCursorNotificationCount = 0;
+ private int mNotificationCount = 0;
+
+ public int getCursorNotificationCount() {
+ return mCursorNotificationCount;
+ }
+
+ public int getNotificationCount() {
+ return mNotificationCount;
+ }
+
+ public ChangeObserver(boolean cursor) {
+ super(new Handler());
+ mCursor = cursor;
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ if (mCursor) {
+ mCursorNotificationCount++;
+ } else {
+ mNotificationCount++;
+ }
+ }
+
+ boolean mCursor;
+ }
+
+ @MediumTest
+ public void testNotificationTest1() throws Exception {
+ /*
+ Cursor c = mContentResolver.query(Notes.CONTENT_URI,
+ new String[] {Notes._ID, Notes.NOTE},
+ null, null);
+ c.registerContentObserver(new MyContentObserver(true));
+ int count = c.count();
+
+ MyContentObserver observer = new MyContentObserver(false);
+ mContentResolver.registerContentObserver(Notes.CONTENT_URI, true, observer);
+
+ Uri uri;
+
+ HashMap<String, String> values = new HashMap<String, String>();
+ values.put(Notes.NOTE, "test note1");
+ uri = mContentResolver.insert(Notes.CONTENT_URI, values);
+ assertEquals(1, mCursorNotificationCount);
+ assertEquals(1, mNotificationCount);
+
+ c.requery();
+ assertEquals(count + 1, c.count());
+ c.first();
+ assertEquals("test note1", c.getString(c.getColumnIndex(Notes.NOTE)));
+ c.updateString(c.getColumnIndex(Notes.NOTE), "test note2");
+ c.commitUpdates();
+
+ assertEquals(2, mCursorNotificationCount);
+ assertEquals(2, mNotificationCount);
+
+ mContentResolver.delete(uri, null);
+
+ assertEquals(3, mCursorNotificationCount);
+ assertEquals(3, mNotificationCount);
+
+ mContentResolver.unregisterContentObserver(observer);
+ */
+ }
+
+ @MediumTest
+ public void testSelectionArgs() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+ ContentValues values = new ContentValues(1);
+ values.put("data", "don't forget to handled 's");
+ mDatabase.insert("test", "data", values);
+ values.clear();
+ values.put("data", "no apostrophes here");
+ mDatabase.insert("test", "data", values);
+ Cursor c = mDatabase.query(
+ "test", null, "data GLOB ?", new String[]{"*'*"}, null, null, null);
+ assertEquals(1, c.getCount());
+ assertTrue(c.moveToFirst());
+ assertEquals("don't forget to handled 's", c.getString(1));
+ c.deactivate();
+
+ // make sure code should checking null string properly so that
+ // it won't crash
+ try {
+ mDatabase.query("test", new String[]{"_id"},
+ "_id=?", new String[]{null}, null, null, null);
+ fail("expected exception not thrown");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ @MediumTest
+ public void testTokenize() throws Exception {
+ Cursor c;
+ mDatabase.execSQL("CREATE TABLE tokens (" +
+ "token TEXT COLLATE unicode," +
+ "source INTEGER," +
+ "token_index INTEGER," +
+ "tag TEXT" +
+ ");");
+ mDatabase.execSQL("CREATE TABLE tokens_no_index (" +
+ "token TEXT COLLATE unicode," +
+ "source INTEGER" +
+ ");");
+
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null));
+
+ Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null));
+ Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null));
+
+ Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null));
+ Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null));
+
+ // test Chinese
+ String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca");
+ Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null));
+
+ String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g");
+
+ Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null));
+
+ Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens;", null));
+
+ String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ key = DatabaseUtils.getHexCollationKey("Hjonneva");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("some string ok");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
+ "SELECT tag from tokens where token GLOB '" + key + "*'", null));
+ key = DatabaseUtils.getHexCollationKey("string");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
+ "SELECT tag from tokens where token GLOB '" + key + "*'", null));
+ key = DatabaseUtils.getHexCollationKey("ok");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
+ "SELECT tag from tokens where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("second field");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
+ "SELECT tag from tokens where token GLOB '" + key + "*'", null));
+ key = DatabaseUtils.getHexCollationKey("field");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
+ "SELECT tag from tokens where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey(chinese);
+ String[] a = new String[1];
+ a[0] = key;
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token= ?", a));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token= ?", a));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token= ?", a));
+ a[0] += "*";
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB ?", a));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB ?", a));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB ?", a));
+
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token= '" + key + "'", null));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token= '" + key + "'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token= '" + key + "'", null));
+
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca");
+ Log.d("DatabaseGeneralTest", "key = " + key);
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
+
+ Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens where token GLOB 'ab*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("some string ok");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
+
+ key = DatabaseUtils.getHexCollationKey("bar");
+ Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
+ Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase,
+ "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
+ }
+
+ @MediumTest
+ public void testTransactions() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
+ mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
+
+ // Make sure that things work outside an explicit transaction.
+ setNum(1);
+ checkNum(1);
+
+ // Test a single-level transaction.
+ setNum(0);
+ mDatabase.beginTransaction();
+ setNum(1);
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ checkNum(1);
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+
+ // Test a rolled-back transaction.
+ setNum(0);
+ mDatabase.beginTransaction();
+ setNum(1);
+ mDatabase.endTransaction();
+ checkNum(0);
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+
+ // We should get an error if we end a non-existent transaction.
+ assertThrowsIllegalState(new Runnable() { public void run() {
+ mDatabase.endTransaction();
+ }});
+
+ // We should get an error if a set a non-existent transaction as clean.
+ assertThrowsIllegalState(new Runnable() { public void run() {
+ mDatabase.setTransactionSuccessful();
+ }});
+
+ mDatabase.beginTransaction();
+ mDatabase.setTransactionSuccessful();
+ // We should get an error if we mark a transaction as clean twice.
+ assertThrowsIllegalState(new Runnable() { public void run() {
+ mDatabase.setTransactionSuccessful();
+ }});
+ // We should get an error if we begin a transaction after marking the parent as clean.
+ assertThrowsIllegalState(new Runnable() { public void run() {
+ mDatabase.beginTransaction();
+ }});
+ mDatabase.endTransaction();
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+
+ // Test a two-level transaction.
+ setNum(0);
+ mDatabase.beginTransaction();
+ mDatabase.beginTransaction();
+ setNum(1);
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ checkNum(1);
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+
+ // Test rolling back an inner transaction.
+ setNum(0);
+ mDatabase.beginTransaction();
+ mDatabase.beginTransaction();
+ setNum(1);
+ mDatabase.endTransaction();
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ checkNum(0);
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+
+ // Test rolling back an outer transaction.
+ setNum(0);
+ mDatabase.beginTransaction();
+ mDatabase.beginTransaction();
+ setNum(1);
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ mDatabase.endTransaction();
+ checkNum(0);
+ Assert.assertFalse(mDatabase.isDbLockedByCurrentThread());
+ }
+
+ private void setNum(int num) {
+ mDatabase.execSQL("UPDATE test SET num = " + num);
+ }
+
+ private void checkNum(int num) {
+ Assert.assertEquals(
+ num, DatabaseUtils.longForQuery(mDatabase, "SELECT num FROM test", null));
+ }
+
+ private void assertThrowsIllegalState(Runnable r) {
+ boolean ok = false;
+ try {
+ r.run();
+ } catch (IllegalStateException e) {
+ ok = true;
+ }
+ Assert.assertTrue(ok);
+ }
+
+ // Disable these until we can explicitly mark them as stress tests
+ public void xxtestMem1() throws Exception {
+ populateDefaultTable();
+
+ for (int i = 0; i < 50000; i++) {
+ Cursor cursor = mDatabase.query("test", null, null, null, null, null, null);
+ cursor.moveToFirst();
+ cursor.close();
+// Log.i("~~~~", "Finished round " + i);
+ }
+ }
+
+ // Disable these until we can explicitly mark them as stress tests
+ public void xxtestMem2() throws Exception {
+ populateDefaultTable();
+
+ for (int i = 0; i < 50000; i++) {
+ Cursor cursor = mDatabase.query("test", null, null, null, null, null, null);
+ cursor.close();
+// Log.i("~~~~", "Finished round " + i);
+ }
+ }
+
+ // Disable these until we can explicitly mark them as stress tests
+ public void xxtestMem3() throws Exception {
+ populateDefaultTable();
+
+ for (int i = 0; i < 50000; i++) {
+ Cursor cursor = mDatabase.query("test", null, null, null, null, null, null);
+ cursor.deactivate();
+// Log.i("~~~~", "Finished round " + i);
+ }
+ }
+
+ @MediumTest
+ public void testContentValues() throws Exception {
+ ContentValues values = new ContentValues();
+ values.put("string", "value");
+ assertEquals("value", values.getAsString("string"));
+ byte[] bytes = new byte[42];
+ Arrays.fill(bytes, (byte) 0x28);
+ values.put("byteArray", bytes);
+ assertTrue(Arrays.equals(bytes, values.getAsByteArray("byteArray")));
+
+ // Write the ContentValues to a Parcel and then read them out
+ Parcel p = Parcel.obtain();
+ values.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ values = ContentValues.CREATOR.createFromParcel(p);
+
+ // Read the values out again and make sure they're the same
+ assertTrue(Arrays.equals(bytes, values.getAsByteArray("byteArray")));
+ assertEquals("value", values.get("string"));
+ }
+
+ @MediumTest
+ public void testTableInfoPragma() throws Exception {
+ mDatabase.execSQL("CREATE TABLE pragma_test (" +
+ "i INTEGER DEFAULT 1234, " +
+ "j INTEGER, " +
+ "s TEXT DEFAULT 'hello', " +
+ "t TEXT, " +
+ "'select' TEXT DEFAULT \"hello\")");
+ try {
+ Cursor cur = mDatabase.rawQuery("PRAGMA table_info(pragma_test)", null);
+ Assert.assertEquals(5, cur.getCount());
+
+ Assert.assertTrue(cur.moveToNext());
+ Assert.assertEquals("i",
+ cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX));
+ Assert.assertEquals("1234",
+ cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX));
+
+ Assert.assertTrue(cur.moveToNext());
+ Assert.assertEquals("j",
+ cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX));
+ Assert.assertNull(cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX));
+
+ Assert.assertTrue(cur.moveToNext());
+ Assert.assertEquals("s",
+ cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX));
+ Assert.assertEquals("'hello'",
+ cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX));
+
+ Assert.assertTrue(cur.moveToNext());
+ Assert.assertEquals("t",
+ cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX));
+ Assert.assertNull(cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX));
+
+ Assert.assertTrue(cur.moveToNext());
+ Assert.assertEquals("select",
+ cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX));
+ Assert.assertEquals("\"hello\"",
+ cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX));
+
+ cur.close();
+ } catch (Throwable t) {
+ throw new RuntimeException(
+ "If you see this test fail, it's likely that something about " +
+ "sqlite's PRAGMA table_info(...) command has changed.", t);
+ }
+ }
+
+ @MediumTest
+ public void testInsertHelper() throws Exception {
+ Cursor cur;
+ ContentValues cv;
+ long row;
+
+ mDatabase.execSQL("CREATE TABLE insert_test (" +
+ "_id INTEGER PRIMARY KEY, " +
+ "s TEXT NOT NULL UNIQUE, " +
+ "t TEXT NOT NULL DEFAULT 'hello world', " +
+ "i INTEGER, " +
+ "j INTEGER NOT NULL DEFAULT 1234, " +
+ "'select' TEXT)");
+
+ DatabaseUtils.InsertHelper ih =
+ new DatabaseUtils.InsertHelper(mDatabase, "insert_test");
+
+ cv = new ContentValues();
+ cv.put("s", "one");
+ row = ih.insert(cv);
+ cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null);
+ Assert.assertTrue(cur.moveToFirst());
+ Assert.assertEquals("one", cur.getString(1));
+ Assert.assertEquals("hello world", cur.getString(2));
+ Assert.assertNull(cur.getString(3));
+ Assert.assertEquals(1234, cur.getLong(4));
+ Assert.assertNull(cur.getString(5));
+ cur.close();
+
+ cv = new ContentValues();
+ cv.put("s", "two");
+ cv.put("t", "goodbye world");
+ row = ih.insert(cv);
+ cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null);
+ Assert.assertTrue(cur.moveToFirst());
+ Assert.assertEquals("two", cur.getString(1));
+ Assert.assertEquals("goodbye world", cur.getString(2));
+ Assert.assertNull(cur.getString(3));
+ Assert.assertEquals(1234, cur.getLong(4));
+ Assert.assertNull(cur.getString(5));
+ cur.close();
+
+ cv = new ContentValues();
+ cv.put("t", "goodbye world");
+ row = ih.insert(cv);
+ Assert.assertEquals(-1, row);
+
+ cv = new ContentValues();
+ cv.put("s", "three");
+ cv.put("i", 2345);
+ cv.put("j", 3456);
+ cv.put("select", "tricky");
+ row = ih.insert(cv);
+ cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null);
+ Assert.assertTrue(cur.moveToFirst());
+ Assert.assertEquals("three", cur.getString(1));
+ Assert.assertEquals("hello world", cur.getString(2));
+ Assert.assertEquals(2345, cur.getLong(3));
+ Assert.assertEquals(3456, cur.getLong(4));
+ Assert.assertEquals("tricky", cur.getString(5));
+ cur.close();
+
+ cv = new ContentValues();
+ cv.put("s", "three");
+ cv.put("i", 6789);
+ row = ih.insert(cv);
+ Assert.assertEquals(-1, row);
+ row = ih.replace(cv);
+ cur = mDatabase.rawQuery("SELECT * FROM insert_test WHERE _id == " + row, null);
+ Assert.assertTrue(cur.moveToFirst());
+ Assert.assertEquals("three", cur.getString(1));
+ Assert.assertEquals("hello world", cur.getString(2));
+ Assert.assertEquals(6789, cur.getLong(3));
+ cur.close();
+
+ ih.close();
+ }
+
+ @MediumTest
+ public void testDbCloseReleasingAllCachedSql() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
+ "num1 INTEGER, num2 INTEGER, image BLOB);");
+ final String statement = "DELETE FROM test WHERE _id=?;";
+ SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
+ assertTrue(statementDoNotClose.getUniqueId() > 0);
+ int nStatement = statementDoNotClose.getUniqueId();
+ assertTrue(statementDoNotClose.getUniqueId() == nStatement);
+ /* do not close statementDoNotClose object.
+ * That should leave it in SQLiteDatabase.mPrograms.
+ * mDatabase.close() in tearDown() should release it.
+ */
+ }
+
+ @MediumTest
+ public void testSemicolonsInStatements() throws Exception {
+ mDatabase.execSQL("CREATE TABLE pragma_test (" +
+ "i INTEGER DEFAULT 1234, " +
+ "j INTEGER, " +
+ "s TEXT DEFAULT 'hello', " +
+ "t TEXT, " +
+ "'select' TEXT DEFAULT \"hello\")");
+ try {
+ // ending the sql statement with semicolons shouldn't be a problem.
+ Cursor cur = mDatabase.rawQuery("PRAGMA database_list;", null);
+ cur.close();
+ // two semicolons in the statement shouldn't be a problem.
+ cur = mDatabase.rawQuery("PRAGMA database_list;;", null);
+ cur.close();
+ } catch (Throwable t) {
+ fail("unexpected, of course");
+ }
+ }
+
+ /**
+ * This test is available only when the platform has a locale with the language "ja".
+ * It finishes without failure when it is not available.
+ */
+ @MediumTest
+ public void testCollateLocalizedForJapanese() throws Exception {
+ final String testName = "DatabaseGeneralTest#testCollateLocalizedForJapanese()";
+ final Locale[] localeArray = Locale.getAvailableLocales();
+ final String japanese = Locale.JAPANESE.getLanguage();
+ final String english = Locale.ENGLISH.getLanguage();
+ Locale japaneseLocale = null;
+ Locale englishLocale = null;
+ for (Locale locale : localeArray) {
+ if (locale != null) {
+ final String language = locale.getLanguage();
+ if (language == null) {
+ continue;
+ } else if (language.equals(japanese)) {
+ japaneseLocale = locale;
+ } else if (language.equals(english)) {
+ englishLocale = locale;
+ }
+ }
+
+ if (japaneseLocale != null && englishLocale != null) {
+ break;
+ }
+ }
+
+ if (japaneseLocale == null || englishLocale == null) {
+ Log.d(TAG, testName + "n is silently skipped since " +
+ (englishLocale == null ?
+ (japaneseLocale == null ?
+ "Both English and Japanese locales do not exist." :
+ "English locale does not exist.") :
+ (japaneseLocale == null ?
+ "Japanese locale does not exist." :
+ "...why?")));
+ return;
+ }
+
+ Locale originalLocale = Locale.getDefault();
+ try {
+
+ final String dbName = "collate_localized_test";
+ mDatabase.execSQL("CREATE TABLE " + dbName + " (" +
+ "_id INTEGER PRIMARY KEY, " +
+ "s TEXT COLLATE LOCALIZED) ");
+ DatabaseUtils.InsertHelper ih =
+ new DatabaseUtils.InsertHelper(mDatabase, dbName);
+ ContentValues cv = new ContentValues();
+
+ cv = new ContentValues(); //
+ cv.put("s", "\uFF75\uFF77\uFF85\uFF9C"); // O-ki-na-wa in half-width Katakana
+ ih.insert(cv);
+
+ cv = new ContentValues(); //
+ cv.put("s", "\u306B\u307B\u3093"); // Ni-ho-n in Hiragana
+ ih.insert(cv);
+
+ cv = new ContentValues(); //
+ cv.put("s", "\u30A2\u30E1\u30EA\u30AB"); // A-me-ri-ca in hull-width Katakana
+ ih.insert(cv);
+
+ // Assume setLocale() does REINDEX and an English locale does not consider
+ // Japanese-specific LOCALIZED order.
+ Locale.setDefault(englishLocale);
+ Locale.setDefault(japaneseLocale);
+
+ Cursor cur = mDatabase.rawQuery(
+ "SELECT * FROM " + dbName + " ORDER BY s", null);
+ assertTrue(cur.moveToFirst());
+ assertEquals("\u30A2\u30E1\u30EA\u30AB", cur.getString(1));
+ assertTrue(cur.moveToNext());
+ assertEquals("\uFF75\uFF77\uFF85\uFF9C", cur.getString(1));
+ assertTrue(cur.moveToNext());
+ assertEquals("\u306B\u307B\u3093", cur.getString(1));
+ } finally {
+ if (originalLocale != null) {
+ try {
+ Locale.setDefault(originalLocale);
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseLocaleTest.java b/core/tests/coretests/src/android/database/DatabaseLocaleTest.java
new file mode 100644
index 0000000..b3282941
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseLocaleTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.database.sqlite.SQLiteDatabase;
+import android.database.Cursor;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.test.MoreAsserts;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+public class DatabaseLocaleTest extends TestCase {
+
+ private SQLiteDatabase mDatabase;
+
+ private static final String[] STRINGS = {
+ "c\u00f4t\u00e9",
+ "cote",
+ "c\u00f4te",
+ "cot\u00e9",
+ "boy",
+ "dog",
+ "COTE",
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mDatabase = SQLiteDatabase.create(null);
+ mDatabase.execSQL(
+ "CREATE TABLE test (id INTEGER PRIMARY KEY, data TEXT COLLATE LOCALIZED);");
+ }
+
+ private void insertStrings() {
+ for (String s : STRINGS) {
+ mDatabase.execSQL("INSERT INTO test (data) VALUES('" + s + "');");
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ super.tearDown();
+ }
+
+ private String[] query(String sql) {
+ Log.i("LocaleTest", "Querying: " + sql);
+ Cursor c = mDatabase.rawQuery(sql, null);
+ assertNotNull(c);
+ ArrayList<String> items = new ArrayList<String>();
+ while (c.moveToNext()) {
+ items.add(c.getString(0));
+ Log.i("LocaleTest", "...." + c.getString(0));
+ }
+ String[] result = items.toArray(new String[items.size()]);
+ assertEquals(STRINGS.length, result.length);
+ c.close();
+ return result;
+ }
+
+ @MediumTest
+ public void testLocaleInsertOrder() throws Exception {
+ insertStrings();
+ String[] results = query("SELECT data FROM test");
+ MoreAsserts.assertEquals(STRINGS, results);
+ }
+
+ @MediumTest
+ public void testLocaleenUS() throws Exception {
+ insertStrings();
+ Log.i("LocaleTest", "about to call setLocale en_US");
+ mDatabase.setLocale(new Locale("en", "US"));
+ String[] results;
+ results = query("SELECT data FROM test ORDER BY data COLLATE LOCALIZED ASC");
+
+ // The database code currently uses PRIMARY collation strength,
+ // meaning that all versions of a character compare equal (regardless
+ // of case or accents), leaving the "cote" flavors in database order.
+ MoreAsserts.assertEquals(results, new String[] {
+ STRINGS[4], // "boy"
+ STRINGS[0], // sundry forms of "cote"
+ STRINGS[1],
+ STRINGS[2],
+ STRINGS[3],
+ STRINGS[6], // "COTE"
+ STRINGS[5], // "dog"
+ });
+ }
+
+ @SmallTest
+ public void testHoge() throws Exception {
+ Cursor cursor = null;
+ try {
+ String expectedString = new String(new int[] {0xFE000}, 0, 1);
+ mDatabase.execSQL("INSERT INTO test(id, data) VALUES(1, '" + expectedString + "')");
+ cursor = mDatabase.rawQuery("SELECT data FROM test WHERE id = 1", null);
+
+ assertNotNull(cursor);
+ assertTrue(cursor.moveToFirst());
+ String actualString = cursor.getString(0);
+ assertEquals(expectedString.length(), actualString.length());
+ for (int i = 0; i < expectedString.length(); i++) {
+ assertEquals((int)expectedString.charAt(i), (int)actualString.charAt(i));
+ }
+ assertEquals(expectedString, actualString);
+ } finally {
+ if (cursor != null) cursor.close();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseLockTest.java b/core/tests/coretests/src/android/database/DatabaseLockTest.java
new file mode 100644
index 0000000..f7a9f8a
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseLockTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.database;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import android.test.AndroidTestCase;
+
+import junit.framework.TestCase;
+
+/*
+ * This is a series of unit tests for database locks.
+ *
+ * Suppress these tests for now, since they have has inconsistent results.
+ * This should be turned into a performance tracking test.
+ */
+@Suppress
+public class DatabaseLockTest extends AndroidTestCase {
+
+ private static final int NUM_ITERATIONS = 100;
+ private static final int SLEEP_TIME = 30;
+ private static final int MAX_ALLOWED_LATENCY_TIME = 30;
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+ private AtomicInteger mCounter = new AtomicInteger();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ File parentDir = getContext().getFilesDir();
+ mDatabaseFile = new File(parentDir, "database_test.db");
+
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ /*
+ * testLockFairness() tests the fairness of prioritizing multiple threads
+ * attempting to access a database concurrently.
+ * This test is intended to verify that, when two threads are accessing the
+ * same database at the same time with the same prioritization, neither thread
+ * is locked out and prevented from accessing the database.
+ */
+ @Suppress
+ public void testLockFairness() {
+ startDatabaseFairnessThread();
+ int previous = 0;
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ mDatabase.beginTransaction();
+ int val = mCounter.get();
+ if (i == 0) {
+ previous = val - i;
+ }
+ assertTrue(previous == (val - i));
+ try {
+ Thread.currentThread().sleep(SLEEP_TIME);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ mDatabase.endTransaction();
+ }
+ }
+
+ /*
+ * This function is to create the second thread for testLockFairness() test.
+ */
+ private void startDatabaseFairnessThread() {
+ Thread thread = new DatabaseFairnessThread();
+ thread.start();
+ }
+
+ private class DatabaseFairnessThread extends Thread {
+ @Override
+ public void run() {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ mDatabase.beginTransaction();
+ int val = mCounter.incrementAndGet();
+ try {
+ Thread.currentThread().sleep(SLEEP_TIME);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ mDatabase.endTransaction();
+ }
+ }
+ }
+
+ /*
+ * testLockLatency() tests the latency of database locks.
+ * This test is intended to verify that, even when two threads are accessing
+ * the same database, the locking/unlocking of the database is done within an
+ * appropriate amount of time (MAX_ALLOWED_LATENCY_TIME).
+ */
+ @Suppress
+ public void testLockLatency() {
+ startDatabaseLatencyThread();
+ int previous = 0;
+ long sumTime = 0;
+ long maxTime = 0;
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ long startTime = System.currentTimeMillis();
+ mDatabase.beginTransaction();
+ long endTime = System.currentTimeMillis();
+ long elapsedTime = endTime - startTime;
+ if (maxTime < elapsedTime) {
+ maxTime = elapsedTime;
+ }
+ sumTime += elapsedTime;
+ try {
+ Thread.currentThread().sleep(SLEEP_TIME);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ mDatabase.endTransaction();
+ }
+ long averageTime = sumTime/NUM_ITERATIONS;
+ Log.i("DatabaseLockLatency", "AverageTime: " + averageTime);
+ Log.i("DatabaseLockLatency", "MaxTime: " + maxTime);
+ assertTrue( (averageTime - SLEEP_TIME) <= MAX_ALLOWED_LATENCY_TIME);
+ }
+
+ /*
+ * This function is to create the second thread for testLockLatency() test.
+ */
+ private void startDatabaseLatencyThread() {
+ Thread thread = new DatabaseLatencyThread();
+ thread.start();
+ }
+
+ private class DatabaseLatencyThread extends Thread {
+ @Override
+ public void run() {
+ for (int i = 0; i < NUM_ITERATIONS; i++)
+ {
+ mDatabase.beginTransaction();
+ try {
+ Thread.currentThread().sleep(SLEEP_TIME);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ mDatabase.endTransaction();
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabasePerformanceTests.java b/core/tests/coretests/src/android/database/DatabasePerformanceTests.java
new file mode 100644
index 0000000..b8ebcc4
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabasePerformanceTests.java
@@ -0,0 +1,1353 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import junit.framework.Assert;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.Contacts;
+import android.provider.Contacts.People;
+import android.test.PerformanceTestCase;
+import android.test.TestCase;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * Database Performance Tests
+ *
+ */
+
+public class DatabasePerformanceTests {
+
+ public static String[] children() {
+ return new String[] {
+ ContactReadingTest1.class.getName(),
+ Perf1Test.class.getName(),
+ Perf2Test.class.getName(),
+ Perf3Test.class.getName(),
+ Perf4Test.class.getName(),
+ Perf5Test.class.getName(),
+ Perf6Test.class.getName(),
+ Perf7Test.class.getName(),
+ Perf8Test.class.getName(),
+ Perf9Test.class.getName(),
+ Perf10Test.class.getName(),
+ Perf11Test.class.getName(),
+ Perf12Test.class.getName(),
+ Perf13Test.class.getName(),
+ Perf14Test.class.getName(),
+ Perf15Test.class.getName(),
+ Perf16Test.class.getName(),
+ Perf17Test.class.getName(),
+ Perf18Test.class.getName(),
+ Perf19Test.class.getName(),
+ Perf20Test.class.getName(),
+ Perf21Test.class.getName(),
+ Perf22Test.class.getName(),
+ Perf23Test.class.getName(),
+ Perf24Test.class.getName(),
+ Perf25Test.class.getName(),
+ Perf26Test.class.getName(),
+ Perf27Test.class.getName(),
+ Perf28Test.class.getName(),
+ Perf29Test.class.getName(),
+ Perf30Test.class.getName(),
+ Perf31Test.class.getName(),
+ };
+ }
+
+ public static abstract class PerformanceBase implements TestCase,
+ PerformanceTestCase {
+ protected static final int CURRENT_DATABASE_VERSION = 42;
+ protected SQLiteDatabase mDatabase;
+ protected File mDatabaseFile;
+ protected Context mContext;
+
+ public void setUp(Context c) {
+ mContext = c;
+ mDatabaseFile = new File("/tmp", "perf_database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ Assert.assertTrue(mDatabase != null);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ public void tearDown() {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ }
+
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 0;
+ }
+
+ public void run() {
+ }
+
+ public String numberName(int number) {
+ String result = "";
+
+ if (number >= 1000) {
+ result += numberName((number / 1000)) + " thousand";
+ number = (number % 1000);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number >= 100) {
+ result += ONES[(number / 100)] + " hundred";
+ number = (number % 100);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number >= 20) {
+ result += TENS[(number / 10)];
+ number = (number % 10);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number > 0) {
+ result += ONES[number];
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Test reading all contact data.
+ */
+ public static class ContactReadingTest1 implements TestCase, PerformanceTestCase {
+ private static final String[] PEOPLE_PROJECTION = new String[] {
+ Contacts.People._ID, // 0
+ Contacts.People.PRIMARY_PHONE_ID, // 1
+ Contacts.People.TYPE, // 2
+ Contacts.People.NUMBER, // 3
+ Contacts.People.LABEL, // 4
+ Contacts.People.NAME, // 5
+ Contacts.People.PRESENCE_STATUS, // 6
+ };
+
+ private Cursor mCursor;
+
+ public void setUp(Context c) {
+ mCursor = c.getContentResolver().query(People.CONTENT_URI, PEOPLE_PROJECTION, null,
+ null, People.DEFAULT_SORT_ORDER);
+ }
+
+ public void tearDown() {
+ mCursor.close();
+ }
+
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ public int startPerformance(Intermediates intermediates) {
+ // This test can only be run once.
+ return 0;
+ }
+
+ public void run() {
+ while (mCursor.moveToNext()) {
+ // Read out all of the data
+ mCursor.getLong(0);
+ mCursor.getLong(1);
+ mCursor.getLong(2);
+ mCursor.getString(3);
+ mCursor.getString(4);
+ mCursor.getString(5);
+ mCursor.getLong(6);
+ }
+ }
+ }
+
+ /**
+ * Test 1000 inserts
+ */
+
+ public static class Perf1Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+
+ private String[] statements = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ statements[i] =
+ "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')";
+ }
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.execSQL(statements[i]);
+ }
+ }
+ }
+
+ /**
+ * Test 1000 inserts into and indexed table
+ */
+
+ public static class Perf2Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+
+ private String[] statements = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ statements[i] =
+ "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')";
+ }
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.execSQL(statements[i]);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs without an index
+ */
+
+ public static class Perf3Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on a string comparison
+ */
+
+ public static class Perf4Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "c LIKE '" + numberName(i) + "'";
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs with an index
+ */
+
+ public static class Perf5Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * INNER JOIN without an index
+ */
+
+ public static class Perf6Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ @Override
+ public void run() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * INNER JOIN without an index on one side
+ */
+
+ public static class Perf7Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ @Override
+ public void run() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * INNER JOIN without an index on one side
+ */
+
+ public static class Perf8Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ @Override
+ public void run() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * 100 SELECTs with subqueries. Subquery is using an index
+ */
+
+ public static class Perf9Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i2b ON t2(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] =
+ "t1.b IN (SELECT t2.b FROM t2 WHERE t2.b >= " + lower
+ + " AND t2.b < " + upper + ")";
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on string comparison with Index
+ */
+
+ public static class Perf10Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "c LIKE '" + numberName(i) + "'";
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on integer
+ */
+
+ public static class Perf11Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"b"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String
+ */
+
+ public static class Perf12Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"c"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on integer with index
+ */
+
+ public static class Perf13Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"b"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b on t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String with index
+ */
+
+ public static class Perf14Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String with starts with
+ */
+
+ public static class Perf15Test extends PerformanceBase {
+ private static final int SIZE = 100;
+ private static final String[] COLUMNS = {"c"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 Deletes on an indexed table
+ */
+
+ public static class Perf16Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 Deletes
+ */
+
+ public static class Perf17Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 DELETE's without an index with where clause
+ */
+
+ public static class Perf18Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 DELETE's with an index with where clause
+ */
+
+ public static class Perf19Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 update's with an index with where clause
+ */
+
+ public static class Perf20Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private String[] where = new String[SIZE];
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ ContentValues b = new ContentValues(1);
+ b.put("b", upper);
+ mValues[i] = b;
+
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.update("t1", mValues[i], where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 update's without an index with where clause
+ */
+
+ public static class Perf21Test extends PerformanceBase {
+ private static final int SIZE = 1000;
+ private String[] where = new String[SIZE];
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ ContentValues b = new ContentValues(1);
+ b.put("b", upper);
+ mValues[i] = b;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.update("t1", mValues[i], where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for an integer
+ */
+
+ public static class Perf22Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", r);
+ mValues[i] = b;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for an integer -indexed table
+ */
+
+ public static class Perf23Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER)");
+ mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", r);
+ mValues[i] = b;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for a String
+ */
+
+ public static class Perf24Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", numberName(r));
+ mValues[i] = b;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for a String - indexed table
+ */
+
+ public static class Perf25Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", numberName(r));
+ mValues[i] = b;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+
+ /**
+ * 10000 selects for a String -starts with
+ */
+
+ public static class Perf26Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for a String - indexed table -starts with
+ */
+
+ public static class Perf27Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for an integer -
+ */
+
+ public static class Perf28Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t4.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t4(a INTEGER)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "a >= " + lower + " AND a < " + upper;
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for an integer -indexed table
+ */
+
+ public static class Perf29Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t4.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t4(a INTEGER)");
+ mDatabase.execSQL("CREATE INDEX i4a ON t4(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "a >= " + lower + " AND a < " + upper;
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+
+ /**
+ * 10000 selects for a String - contains 'e'
+ */
+
+ public static class Perf30Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "a LIKE '*e*'";
+
+ }
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for a String - contains 'e'-indexed table
+ */
+
+ public static class Perf31Test extends PerformanceBase {
+ private static final int SIZE = 10000;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp(Context c) {
+ super.setUp(c);
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "a LIKE '*e*'";
+
+ }
+
+ }
+
+ @Override
+ public void run() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ public static final String[] ONES =
+ {"zero", "one", "two", "three", "four", "five", "six", "seven",
+ "eight", "nine", "ten", "eleven", "twelve", "thirteen",
+ "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
+ "nineteen"};
+
+ public static final String[] TENS =
+ {"", "ten", "twenty", "thirty", "forty", "fifty", "sixty",
+ "seventy", "eighty", "ninety"};
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseStatementTest.java b/core/tests/coretests/src/android/database/DatabaseStatementTest.java
new file mode 100644
index 0000000..71dc3ae
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseStatementTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDoneException;
+import android.database.sqlite.SQLiteStatement;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class DatabaseStatementTest extends AndroidTestCase implements PerformanceTestCase {
+
+ private static final String sString1 = "this is a test";
+ private static final String sString2 = "and yet another test";
+ private static final String sString3 = "this string is a little longer, but still a test";
+
+ private static final int CURRENT_DATABASE_VERSION = 42;
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ // These test can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 1;
+ }
+
+ private void populateDefaultTable() {
+ mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString1 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString2 + "');");
+ mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
+ }
+
+ @MediumTest
+ public void testExecuteStatement() throws Exception {
+ populateDefaultTable();
+ SQLiteStatement statement = mDatabase.compileStatement("DELETE FROM test");
+ statement.execute();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ assertEquals(0, c.getCount());
+ c.deactivate();
+ statement.close();
+ }
+
+ @MediumTest
+ public void testSimpleQuery() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
+ mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
+ SQLiteStatement statement1 =
+ mDatabase.compileStatement("SELECT num FROM test WHERE str = ?");
+ SQLiteStatement statement2 =
+ mDatabase.compileStatement("SELECT str FROM test WHERE num = ?");
+
+ try {
+ statement1.bindString(1, "hello");
+ long value = statement1.simpleQueryForLong();
+ assertEquals(1234, value);
+
+ statement1.bindString(1, "world");
+ statement1.simpleQueryForLong();
+ fail("shouldn't get here");
+ } catch (SQLiteDoneException e) {
+ // expected
+ }
+
+ try {
+ statement2.bindLong(1, 1234);
+ String value = statement1.simpleQueryForString();
+ assertEquals("hello", value);
+
+ statement2.bindLong(1, 5678);
+ statement1.simpleQueryForString();
+ fail("shouldn't get here");
+ } catch (SQLiteDoneException e) {
+ // expected
+ }
+
+ statement1.close();
+ statement2.close();
+ }
+
+ @MediumTest
+ public void testStatementLongBinding() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
+
+ for (int i = 0; i < 10; i++) {
+ statement.bindLong(1, i);
+ statement.execute();
+ }
+ statement.close();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ int numCol = c.getColumnIndexOrThrow("num");
+ c.moveToFirst();
+ for (long i = 0; i < 10; i++) {
+ long num = c.getLong(numCol);
+ assertEquals(i, num);
+ c.moveToNext();
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testStatementStringBinding() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num TEXT);");
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
+
+ for (long i = 0; i < 10; i++) {
+ statement.bindString(1, Long.toHexString(i));
+ statement.execute();
+ }
+ statement.close();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ int numCol = c.getColumnIndexOrThrow("num");
+ c.moveToFirst();
+ for (long i = 0; i < 10; i++) {
+ String num = c.getString(numCol);
+ assertEquals(Long.toHexString(i), num);
+ c.moveToNext();
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testStatementClearBindings() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
+
+ for (long i = 0; i < 10; i++) {
+ statement.bindLong(1, i);
+ statement.clearBindings();
+ statement.execute();
+ }
+ statement.close();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
+ int numCol = c.getColumnIndexOrThrow("num");
+ assertTrue(c.moveToFirst());
+ for (long i = 0; i < 10; i++) {
+ assertTrue(c.isNull(numCol));
+ c.moveToNext();
+ }
+ c.close();
+ }
+
+ @MediumTest
+ public void testSimpleStringBinding() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num TEXT, value TEXT);");
+ String statement = "INSERT INTO test (num, value) VALUES (?,?)";
+
+ String[] args = new String[2];
+ for (int i = 0; i < 2; i++) {
+ args[i] = Integer.toHexString(i);
+ }
+
+ mDatabase.execSQL(statement, args);
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ int numCol = c.getColumnIndexOrThrow("num");
+ int valCol = c.getColumnIndexOrThrow("value");
+ c.moveToFirst();
+ String num = c.getString(numCol);
+ assertEquals(Integer.toHexString(0), num);
+
+ String val = c.getString(valCol);
+ assertEquals(Integer.toHexString(1), val);
+ c.close();
+ }
+
+ @MediumTest
+ public void testStatementMultipleBindings() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
+ SQLiteStatement statement =
+ mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)");
+
+ for (long i = 0; i < 10; i++) {
+ statement.bindLong(1, i);
+ statement.bindString(2, Long.toHexString(i));
+ statement.execute();
+ }
+ statement.close();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
+ int numCol = c.getColumnIndexOrThrow("num");
+ int strCol = c.getColumnIndexOrThrow("str");
+ assertTrue(c.moveToFirst());
+ for (long i = 0; i < 10; i++) {
+ long num = c.getLong(numCol);
+ String str = c.getString(strCol);
+ assertEquals(i, num);
+ assertEquals(Long.toHexString(i), str);
+ c.moveToNext();
+ }
+ c.close();
+ }
+
+ private static class StatementTestThread extends Thread {
+ private SQLiteDatabase mDatabase;
+ private SQLiteStatement mStatement;
+
+ public StatementTestThread(SQLiteDatabase db, SQLiteStatement statement) {
+ super();
+ mDatabase = db;
+ mStatement = statement;
+ }
+
+ @Override
+ public void run() {
+ mDatabase.beginTransaction();
+ for (long i = 0; i < 10; i++) {
+ mStatement.bindLong(1, i);
+ mStatement.bindString(2, Long.toHexString(i));
+ mStatement.execute();
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, "ROWID");
+ int numCol = c.getColumnIndexOrThrow("num");
+ int strCol = c.getColumnIndexOrThrow("str");
+ assertTrue(c.moveToFirst());
+ for (long i = 0; i < 10; i++) {
+ long num = c.getLong(numCol);
+ String str = c.getString(strCol);
+ assertEquals(i, num);
+ assertEquals(Long.toHexString(i), str);
+ c.moveToNext();
+ }
+ c.close();
+ }
+ }
+
+ @MediumTest
+ public void testStatementMultiThreaded() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
+ SQLiteStatement statement =
+ mDatabase.compileStatement("INSERT INTO test (num, str) VALUES (?, ?)");
+
+ StatementTestThread thread = new StatementTestThread(mDatabase, statement);
+ thread.start();
+ try {
+ thread.join();
+ } finally {
+ statement.close();
+ }
+ }
+
+ @MediumTest
+ public void testStatementConstraint() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL);");
+ SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
+
+ // Try to insert NULL, which violates the constraint
+ try {
+ statement.clearBindings();
+ statement.execute();
+ fail("expected exception not thrown");
+ } catch (SQLiteConstraintException e) {
+ // expected
+ }
+
+ // Make sure the statement can still be used
+ statement.bindLong(1, 1);
+ statement.execute();
+ statement.close();
+
+ Cursor c = mDatabase.query("test", null, null, null, null, null, null);
+ int numCol = c.getColumnIndexOrThrow("num");
+ c.moveToFirst();
+ long num = c.getLong(numCol);
+ assertEquals(1, num);
+ c.close();
+ }
+}
diff --git a/core/tests/coretests/src/android/database/DatabaseStressTest.java b/core/tests/coretests/src/android/database/DatabaseStressTest.java
new file mode 100644
index 0000000..30e46e7
--- /dev/null
+++ b/core/tests/coretests/src/android/database/DatabaseStressTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.content.Context;
+import android.database.sqlite.*;
+import android.util.Log;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.io.File;
+
+// This test suite is too desctructive and takes too long to be included in the
+// automated suite.
+@Suppress
+public class DatabaseStressTest extends AndroidTestCase {
+ private static final String TAG = "DatabaseStressTest";
+ private static final int CURRENT_DATABASE_VERSION = 1;
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Context c = getContext();
+
+ mDatabaseFile = c.getDatabasePath("database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+
+ mDatabase = c.openOrCreateDatabase("database_test.db", 0, null);
+
+ assertNotNull(mDatabase);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+
+ mDatabase.execSQL("CREATE TABLE IF NOT EXISTS test (_id INTEGER PRIMARY KEY, data TEXT);");
+
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ public void testSingleThreadInsertDelete() {
+ int i = 0;
+ char[] ch = new char[100000];
+ String str = new String(ch);
+ String[] strArr = new String[1];
+ strArr[0] = str;
+ for (; i < 10000; ++i) {
+ try {
+ mDatabase.execSQL("INSERT INTO test (data) VALUES (?)", strArr);
+ mDatabase.execSQL("delete from test;");
+ } catch (Exception e) {
+ Log.e(TAG, "exception " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * use fillup -p 90 before run the test
+ * and when disk run out
+ * start delete some fillup files
+ * and see if db recover
+ */
+ public void testOutOfSpace() {
+ int i = 0;
+ char[] ch = new char[100000];
+ String str = new String(ch);
+ String[] strArr = new String[1];
+ strArr[0] = str;
+ for (; i < 10000; ++i) {
+ try {
+ mDatabase.execSQL("INSERT INTO test (data) VALUES (?)", strArr);
+ } catch (Exception e) {
+ Log.e(TAG, "exception " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/MatrixCursorTest.java b/core/tests/coretests/src/android/database/MatrixCursorTest.java
new file mode 100644
index 0000000..cddc6c4
--- /dev/null
+++ b/core/tests/coretests/src/android/database/MatrixCursorTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import junit.framework.TestCase;
+
+import java.util.*;
+
+public class MatrixCursorTest extends TestCase {
+
+ public void testEmptyCursor() {
+ Cursor cursor = new MatrixCursor(new String[] { "a" });
+ assertEquals(0, cursor.getCount());
+ }
+
+ public void testNullValue() {
+ MatrixCursor cursor = new MatrixCursor(new String[] { "a" });
+ cursor.newRow().add(null);
+ cursor.moveToNext();
+ assertTrue(cursor.isNull(0));
+ assertNull(cursor.getString(0));
+ assertEquals(0, cursor.getShort(0));
+ assertEquals(0, cursor.getInt(0));
+ assertEquals(0L, cursor.getLong(0));
+ assertEquals(0.0f, cursor.getFloat(0));
+ assertEquals(0.0d, cursor.getDouble(0));
+ }
+
+ public void testMatrixCursor() {
+ MatrixCursor cursor = newMatrixCursor();
+
+ cursor.newRow()
+ .add("a")
+ .add(1)
+ .add(2)
+ .add(3)
+ .add(4)
+ .add(5);
+
+ cursor.moveToNext();
+
+ checkValues(cursor);
+
+ cursor.newRow()
+ .add("a")
+ .add("1")
+ .add("2")
+ .add("3")
+ .add("4")
+ .add("5");
+
+ cursor.moveToNext();
+ checkValues(cursor);
+
+ cursor.moveToPrevious();
+ checkValues(cursor);
+ }
+
+ public void testAddArray() {
+ MatrixCursor cursor = newMatrixCursor();
+
+ cursor.addRow(new Object[] { "a", 1, 2, 3, 4, 5 });
+ cursor.moveToNext();
+ checkValues(cursor);
+
+ try {
+ cursor.addRow(new Object[0]);
+ fail();
+ } catch (IllegalArgumentException e) { /* expected */ }
+ }
+
+ public void testAddIterable() {
+ MatrixCursor cursor = newMatrixCursor();
+
+ cursor.addRow(Arrays.asList("a", 1, 2, 3, 4, 5));
+ cursor.moveToNext();
+ checkValues(cursor);
+
+ try {
+ cursor.addRow(Collections.emptyList());
+ fail();
+ } catch (IllegalArgumentException e) { /* expected */ }
+
+ try {
+ cursor.addRow(Arrays.asList("a", 1, 2, 3, 4, 5, "Too many!"));
+ fail();
+ } catch (IllegalArgumentException e) { /* expected */ }
+ }
+
+ public void testAddArrayList() {
+ MatrixCursor cursor = newMatrixCursor();
+
+ cursor.addRow(new NonIterableArrayList<Object>(
+ Arrays.asList("a", 1, 2, 3, 4, 5)));
+ cursor.moveToNext();
+ checkValues(cursor);
+
+ try {
+ cursor.addRow(new NonIterableArrayList<Object>());
+ fail();
+ } catch (IllegalArgumentException e) { /* expected */ }
+
+ try {
+ cursor.addRow(new NonIterableArrayList<Object>(
+ Arrays.asList("a", 1, 2, 3, 4, 5, "Too many!")));
+ fail();
+ } catch (IllegalArgumentException e) { /* expected */ }
+ }
+
+ static class NonIterableArrayList<T> extends ArrayList<T> {
+
+ NonIterableArrayList() {}
+
+ NonIterableArrayList(Collection<? extends T> ts) {
+ super(ts);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ throw new AssertionError();
+ }
+ }
+
+ private MatrixCursor newMatrixCursor() {
+ return new MatrixCursor(new String[] {
+ "string", "short", "int", "long", "float", "double" });
+ }
+
+ private void checkValues(MatrixCursor cursor) {
+ assertEquals("a", cursor.getString(0));
+ assertEquals(1, cursor.getShort(1));
+ assertEquals(2, cursor.getInt(2));
+ assertEquals(3, cursor.getLong(3));
+ assertEquals(4.0f, cursor.getFloat(4));
+ assertEquals(5.0D, cursor.getDouble(5));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
new file mode 100644
index 0000000..31cf515
--- /dev/null
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTestSuite.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import junit.framework.TestSuite;
+
+public class NewDatabasePerformanceTestSuite extends TestSuite {
+ public static TestSuite suite() {
+ TestSuite suite =
+ new TestSuite(NewDatabasePerformanceTestSuite.class.getName());
+
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ Insert1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InsertIndexed1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ Select100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringComparison100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InnerJoin100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InnerJoinOneSide100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InnerJoinNoIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectSubQIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectIndexStringComparison100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectInteger100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectString100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectIntegerIndex100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectIndexString100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringStartsWith100.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ DeleteIndexed1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ Delete1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ DeleteWhere1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ DeleteIndexWhere1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ UpdateIndexWhere1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ UpdateWhere1000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InsertInteger10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InsertIntegerIndex10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InsertString10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ InsertStringIndexed10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringStartsWith10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringIndexedStartsWith10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectInteger10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectIntegerIndexed10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringContains10000.class);
+ suite.addTestSuite(NewDatabasePerformanceTests.
+ SelectStringIndexedContains10000.class);
+
+ return suite;
+ }
+}
diff --git a/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
new file mode 100644
index 0000000..0dca90b
--- /dev/null
+++ b/core/tests/coretests/src/android/database/NewDatabasePerformanceTests.java
@@ -0,0 +1,1234 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database;
+
+import android.content.ContentValues;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.PerformanceTestCase;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * Database Performance Tests
+ *
+ */
+
+public class NewDatabasePerformanceTests {
+
+ // Edit this to change the test run times. The original is 100.
+ final static int kMultiplier = 1;
+
+ public static class PerformanceBase extends TestCase
+ implements PerformanceTestCase {
+ protected static final int CURRENT_DATABASE_VERSION = 42;
+ protected SQLiteDatabase mDatabase;
+ protected File mDatabaseFile;
+
+ public void setUp() {
+ mDatabaseFile = new File("/sdcard", "perf_database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase =
+ SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(),
+ null);
+ assertTrue(mDatabase != null);
+ mDatabase.setVersion(CURRENT_DATABASE_VERSION);
+ }
+
+ public void tearDown() {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ }
+
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ // These tests can only be run once.
+ public int startPerformance(Intermediates intermediates) {
+ return 0;
+ }
+
+ public String numberName(int number) {
+ String result = "";
+
+ if (number >= 1000) {
+ result += numberName((number / 1000)) + " thousand";
+ number = (number % 1000);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number >= 100) {
+ result += ONES[(number / 100)] + " hundred";
+ number = (number % 100);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number >= 20) {
+ result += TENS[(number / 10)];
+ number = (number % 10);
+
+ if (number > 0) result += " ";
+ }
+
+ if (number > 0) {
+ result += ONES[number];
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Test 1000 inserts.
+ */
+
+ public static class Insert1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+
+ private String[] statements = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ statements[i] =
+ "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')";
+ }
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.execSQL(statements[i]);
+ }
+ }
+ }
+
+ /**
+ * Test 1000 inserts into an indexed table.
+ */
+
+ public static class InsertIndexed1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+
+ private String[] statements = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ statements[i] =
+ "INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')";
+ }
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.execSQL(statements[i]);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs without an index
+ */
+
+ public static class Select100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on a string comparison
+ */
+
+ public static class SelectStringComparison100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "c LIKE '" + numberName(i) + "'";
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs with an index
+ */
+
+ public static class SelectIndex100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * INNER JOIN without an index
+ */
+
+ public static class InnerJoin100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ public void testRun() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * INNER JOIN without an index on one side
+ */
+
+ public static class InnerJoinOneSide100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ public void testRun() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.b = t2.b", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * INNER JOIN without an index on one side
+ */
+
+ public static class InnerJoinNoIndex100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+ }
+
+ public void testRun() {
+ mDatabase.query("t1 INNER JOIN t2 ON t1.c = t2.c", COLUMNS, null,
+ null, null, null, null);
+ }
+ }
+
+ /**
+ * 100 SELECTs with subqueries. Subquery is using an index
+ */
+
+ public static class SelectSubQIndex100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"t1.a"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase
+ .execSQL("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ mDatabase.execSQL("CREATE INDEX i2b ON t2(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t2 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] =
+ "t1.b IN (SELECT t2.b FROM t2 WHERE t2.b >= " + lower
+ + " AND t2.b < " + upper + ")";
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on string comparison with Index
+ */
+
+ public static class SelectIndexStringComparison100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"count(*)", "avg(b)"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "c LIKE '" + numberName(i) + "'";
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on integer
+ */
+
+ public static class SelectInteger100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"b"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String
+ */
+
+ public static class SelectString100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"c"};
+
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on integer with index
+ */
+
+ public static class SelectIntegerIndex100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"b"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b on t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String with index
+ */
+
+ public static class SelectIndexString100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t1", COLUMNS, null, null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 100 SELECTs on String with starts with
+ */
+
+ public static class SelectStringStartsWith100 extends PerformanceBase {
+ private static final int SIZE = 1 * kMultiplier;
+ private static final String[] COLUMNS = {"c"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "c LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase
+ .query("t1", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 Deletes on an indexed table
+ */
+
+ public static class DeleteIndexed1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3c ON t1(c)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 Deletes
+ */
+
+ public static class Delete1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private static final String[] COLUMNS = {"c"};
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", null, null);
+ }
+ }
+ }
+
+ /**
+ * 1000 DELETE's without an index with where clause
+ */
+
+ public static class DeleteWhere1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 DELETE's with an index with where clause
+ */
+
+ public static class DeleteIndexWhere1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.delete("t1", where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 update's with an index with where clause
+ */
+
+ public static class UpdateIndexWhere1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private String[] where = new String[SIZE];
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1b ON t1(b)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ ContentValues b = new ContentValues(1);
+ b.put("b", upper);
+ mValues[i] = b;
+
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.update("t1", mValues[i], where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 1000 update's without an index with where clause
+ */
+
+ public static class UpdateWhere1000 extends PerformanceBase {
+ private static final int SIZE = 10 * kMultiplier;
+ private String[] where = new String[SIZE];
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t1 VALUES(" + i + "," + r + ",'"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "b >= " + lower + " AND b < " + upper;
+ ContentValues b = new ContentValues(1);
+ b.put("b", upper);
+ mValues[i] = b;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.update("t1", mValues[i], where[i], null);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for an integer
+ */
+
+ public static class InsertInteger10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", r);
+ mValues[i] = b;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for an integer -indexed table
+ */
+
+ public static class InsertIntegerIndex10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a INTEGER)");
+ mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", r);
+ mValues[i] = b;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for a String
+ */
+
+ public static class InsertString10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", numberName(r));
+ mValues[i] = b;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+ /**
+ * 10000 inserts for a String - indexed table
+ */
+
+ public static class InsertStringIndexed10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ ContentValues[] mValues = new ContentValues[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t1(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i1a ON t1(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ ContentValues b = new ContentValues(1);
+ b.put("a", numberName(r));
+ mValues[i] = b;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.insert("t1", null, mValues[i]);
+ }
+ }
+ }
+
+
+ /**
+ * 10000 selects for a String -starts with
+ */
+
+ public static class SelectStringStartsWith10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for a String - indexed table -starts with
+ */
+
+ public static class SelectStringIndexedStartsWith10000 extends
+ PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ where[i] = "a LIKE '" + numberName(r).substring(0, 1) + "*'";
+
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for an integer -
+ */
+
+ public static class SelectInteger10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t4.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t4(a INTEGER)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "a >= " + lower + " AND a < " + upper;
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for an integer -indexed table
+ */
+
+ public static class SelectIntegerIndexed10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t4.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t4(a INTEGER)");
+ mDatabase.execSQL("CREATE INDEX i4a ON t4(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t4 VALUES(" + r + ")");
+
+ int lower = i * 100;
+ int upper = (i + 10) * 100;
+ where[i] = "a >= " + lower + " AND a < " + upper;
+ }
+
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t4", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+
+ /**
+ * 10000 selects for a String - contains 'e'
+ */
+
+ public static class SelectStringContains10000 extends PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "a LIKE '*e*'";
+
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ /**
+ * 10000 selects for a String - contains 'e'-indexed table
+ */
+
+ public static class SelectStringIndexedContains10000 extends
+ PerformanceBase {
+ private static final int SIZE = 100 * kMultiplier;
+ private static final String[] COLUMNS = {"t3.a"};
+ private String[] where = new String[SIZE];
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ Random random = new Random(42);
+
+ mDatabase
+ .execSQL("CREATE TABLE t3(a VARCHAR(100))");
+ mDatabase.execSQL("CREATE INDEX i3a ON t3(a)");
+
+ for (int i = 0; i < SIZE; i++) {
+ int r = random.nextInt(100000);
+ mDatabase.execSQL("INSERT INTO t3 VALUES('"
+ + numberName(r) + "')");
+ }
+
+ for (int i = 0; i < SIZE; i++) {
+ where[i] = "a LIKE '*e*'";
+
+ }
+ }
+
+ public void testRun() {
+ for (int i = 0; i < SIZE; i++) {
+ mDatabase.query("t3", COLUMNS, where[i], null, null, null, null);
+ }
+ }
+ }
+
+ public static final String[] ONES =
+ {"zero", "one", "two", "three", "four", "five", "six", "seven",
+ "eight", "nine", "ten", "eleven", "twelve", "thirteen",
+ "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
+ "nineteen"};
+
+ public static final String[] TENS =
+ {"", "ten", "twenty", "thirty", "forty", "fifty", "sixty",
+ "seventy", "eighty", "ninety"};
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java
new file mode 100644
index 0000000..19c7bcb
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * Tests for the most commonly used methods of sql like creating a connection,
+ * inserting, selecting, updating.
+ */
+public abstract class AbstractJDBCDriverTest extends TestCase {
+
+ @MediumTest
+ public void testJDBCDriver() throws Exception {
+ Connection firstConnection = null;
+ Connection secondConnection = null;
+ File dbFile = getDbFile();
+ String connectionURL = getConnectionURL();
+ Statement firstStmt = null;
+ Statement secondStmt = null;
+ try {
+ Class.forName(getJDBCDriverClassName());
+ firstConnection = DriverManager.getConnection(connectionURL);
+ secondConnection = DriverManager.getConnection(connectionURL);
+
+ String[] ones = {"hello!", "goodbye"};
+ short[] twos = {10, 20};
+ String[] onesUpdated = new String[ones.length];
+ for (int i = 0; i < ones.length; i++) {
+ onesUpdated[i] = ones[i] + twos[i];
+ }
+ firstStmt = firstConnection.createStatement();
+ firstStmt.execute("create table tbl1(one varchar(10), two smallint)");
+ secondStmt = secondConnection.createStatement();
+
+ autoCommitInsertSelectTest(firstStmt, ones, twos);
+ updateSelectCommitSelectTest(firstStmt, secondStmt, ones, onesUpdated, twos);
+ updateSelectRollbackSelectTest(firstStmt, secondStmt, onesUpdated, ones, twos);
+ } finally {
+ closeConnections(firstConnection, secondConnection, dbFile, firstStmt, secondStmt);
+ }
+ }
+
+ protected abstract String getJDBCDriverClassName();
+ protected abstract String getConnectionURL();
+ protected abstract File getDbFile();
+
+ private void closeConnections(Connection firstConnection, Connection secondConnection,
+ File dbFile, Statement firstStmt, Statement secondStmt) {
+ String failText = null;
+ try {
+ if (firstStmt != null) {
+ firstStmt.execute("drop table tbl1");
+ }
+ } catch (SQLException e) {
+ failText = e.getLocalizedMessage();
+ }
+ try {
+ if (firstStmt != null) {
+ firstStmt.close();
+ }
+ } catch (SQLException e) {
+ failText = e.getLocalizedMessage();
+ }
+ try {
+ if (firstConnection != null) {
+ firstConnection.close();
+ }
+ } catch (SQLException e) {
+ failText = e.getLocalizedMessage();
+ }
+ try {
+ if (secondStmt != null) {
+ secondStmt.close();
+ }
+ } catch (SQLException e) {
+ failText = e.getLocalizedMessage();
+ }
+ try {
+ if (secondConnection != null) {
+ secondConnection.close();
+ }
+ } catch (SQLException e) {
+ failText = e.getLocalizedMessage();
+ }
+ dbFile.delete();
+ assertNull(failText, failText);
+ }
+
+ /**
+ * Inserts the values from 'ones' with the values from 'twos' into 'tbl1'
+ * @param stmt the statement to use for the inserts.
+ * @param ones the string values to insert into tbl1.
+ * @param twos the corresponding numerical values to insert into tbl1.
+ * @throws SQLException in case of a problem during insert.
+ */
+ private void autoCommitInsertSelectTest(Statement stmt, String[] ones,
+ short[] twos) throws SQLException {
+ for (int i = 0; i < ones.length; i++) {
+ stmt.execute("insert into tbl1 values('" + ones[i] + "'," + twos[i]
+ + ")");
+ }
+ assertAllFromTbl1(stmt, ones, twos);
+ }
+
+ /**
+ * Asserts that all values that where added to tbl1 are actually in tbl1.
+ * @param stmt the statement to use for the select.
+ * @param ones the string values that where added.
+ * @param twos the numerical values that where added.
+ * @throws SQLException in case of a problem during select.
+ */
+ private void assertAllFromTbl1(Statement stmt, String[] ones, short[] twos)
+ throws SQLException {
+ ResultSet rs = stmt.executeQuery("select * from tbl1");
+ int i = 0;
+ for (; rs.next(); i++) {
+ assertTrue(i < ones.length);
+ assertEquals(ones[i], rs.getString("one"));
+ assertEquals(twos[i], rs.getShort("two"));
+ }
+ assertEquals(i, ones.length);
+ }
+
+ /**
+ * Tests the results of an update followed bz a select on a diffrent statement.
+ * After that the first statement commits its update. and now the second
+ * statement should also be able to see the changed values in a select.
+ * @param firstStmt the statement to use for the update and commit.
+ * @param secondStmt the statement that should be used to check if the commit works
+ * @param ones the original string values.
+ * @param onesUpdated the updated string values.
+ * @param twos the numerical values.
+ * @throws SQLException in case of a problem during any of the executed commands.
+ */
+ private void updateSelectCommitSelectTest(Statement firstStmt,
+ Statement secondStmt, String[] ones, String[] onesUpdated,
+ short[] twos) throws SQLException {
+ firstStmt.getConnection().setAutoCommit(false);
+ try {
+ updateOnes(firstStmt, onesUpdated, twos);
+ assertAllFromTbl1(secondStmt, ones, twos);
+ firstStmt.getConnection().commit();
+ assertAllFromTbl1(secondStmt, onesUpdated, twos);
+ } finally {
+ firstStmt.getConnection().setAutoCommit(true);
+ }
+ }
+
+ /**
+ * Tests if an update followed by a select works. After that a rollback will
+ * be made and again a select should show that the rollback worked.
+ * @param firstStmt the statement to use for the update and the rollback
+ * @param secondStmt the statement to use for checking if the rollback worked as intended.
+ * @param ones the original string values.
+ * @param onesUpdated the updated string values.
+ * @param twos the nomerical values.
+ * @throws SQLException in case of a problem during any command.
+ */
+ private void updateSelectRollbackSelectTest(Statement firstStmt,
+ Statement secondStmt, String[] ones, String[] onesUpdated,
+ short[] twos) throws SQLException {
+ firstStmt.getConnection().setAutoCommit(false);
+ try {
+ updateOnes(firstStmt, onesUpdated, twos);
+ assertAllFromTbl1(secondStmt, ones, twos);
+ firstStmt.getConnection().rollback();
+ assertAllFromTbl1(secondStmt, ones, twos);
+ } finally {
+ firstStmt.getConnection().setAutoCommit(true);
+ }
+ }
+
+ /**
+ * updates the sring values. the original values are stored in 'ones'
+ * and the updated values in 'ones_updated'
+ * @param stmt the statement to use for the update.
+ * @param onesUpdated the new string values.
+ * @param twos the numerical values.
+ * @throws SQLException in case of a problem during update.
+ */
+ private void updateOnes(Statement stmt, String[] onesUpdated, short[] twos)
+ throws SQLException {
+ for (int i = 0; i < onesUpdated.length; i++) {
+ stmt.execute("UPDATE tbl1 SET one = '" + onesUpdated[i]
+ + "' WHERE two = " + twos[i]);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
new file mode 100644
index 0000000..af7ccce
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteGeneralTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.database.sqlite;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+
+public class SQLiteGeneralTest extends AndroidTestCase {
+
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+ Boolean exceptionRecvd = false;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ exceptionRecvd = false;
+ File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mDatabase.close();
+ mDatabaseFile.delete();
+ super.tearDown();
+ }
+
+ @LargeTest
+ public void testUseOfSameSqlStatementBy2Threads() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+
+ // thread 1 creates a prepared statement
+ final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+
+ // start 2 threads to do repeatedly execute "stmt"
+ // since these 2 threads are executing the same sql, they each should get
+ // their own copy and
+ // there SHOULD NOT be an error from sqlite: "prepared statement is busy"
+ class RunStmtThread extends Thread {
+ private static final int N = 1000;
+ @Override public void run() {
+ int i = 0;
+ try {
+ // execute many times
+ for (i = 0; i < N; i++) {
+ SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+ s1.bindLong(1, i);
+ s1.execute();
+ s1.close();
+ }
+ } catch (SQLiteException e) {
+ fail("SQLiteException: " + e.getMessage());
+ return;
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("random unexpected exception: " + e.getMessage());
+ return;
+ }
+ }
+ }
+ RunStmtThread t1 = new RunStmtThread();
+ t1.start();
+ RunStmtThread t2 = new RunStmtThread();
+ t2.start();
+ while (t1.isAlive() || t2.isAlive()) {
+ Thread.sleep(1000);
+ }
+ }
+
+ @FlakyTest
+ public void testUseOfSamePreparedStatementBy2Threads() throws Exception {
+ mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
+
+ // thread 1 creates a prepared statement
+ final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
+ final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
+
+ // start 2 threads to do repeatedly execute "stmt"
+ // since these 2 threads are executing the same prepared statement,
+ // should see an error from sqlite: "prepared statement is busy"
+ class RunStmtThread extends Thread {
+ private static final int N = 1000;
+ @Override public void run() {
+ int i = 0;
+ try {
+ // execute many times
+ for (i = 0; i < N; i++) {
+ s1.bindLong(1, i);
+ s1.execute();
+ }
+ } catch (SQLiteException e) {
+ // expect it
+ assertTrue(e.getMessage().contains("library routine called out of sequence:"));
+ exceptionRecvd = true;
+ return;
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("random unexpected exception: " + e.getMessage());
+ return;
+ }
+ }
+ }
+ RunStmtThread t1 = new RunStmtThread();
+ t1.start();
+ RunStmtThread t2 = new RunStmtThread();
+ t2.start();
+ while (t1.isAlive() || t2.isAlive()) {
+ Thread.sleep(1000);
+ }
+ assertTrue(exceptionRecvd);
+ }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java
new file mode 100644
index 0000000..8e677a5
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import java.io.File;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * Minimal test for JDBC driver
+ */
+public class SQLiteJDBCDriverTest extends AbstractJDBCDriverTest {
+
+ private File dbFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ dbFile = File.createTempFile("sqliteTestDB", null);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if(dbFile != null) {
+ dbFile.delete();
+ }
+ super.tearDown();
+ }
+
+ @Override
+ protected String getConnectionURL() {
+ return "jdbc:sqlite:/" + dbFile;
+ }
+
+ @Override
+ protected File getDbFile() {
+ return dbFile;
+ }
+
+ @Override
+ protected String getJDBCDriverClassName() {
+ return "SQLite.JDBCDriver";
+ }
+
+ // Regression test for (Noser) #255: PreparedStatement.executeUpdate results
+ // in VM crashing with SIGABRT.
+ @MediumTest
+ public void test_connection3() throws Exception {
+ PreparedStatement prst = null;
+ Statement st = null;
+ Connection conn = null;
+ try {
+ Class.forName("SQLite.JDBCDriver").newInstance();
+ if (dbFile.exists()) {
+ dbFile.delete();
+ }
+ conn = DriverManager.getConnection("jdbc:sqlite:/"
+ + dbFile.getPath());
+ assertNotNull(conn);
+
+ // create table
+ st = conn.createStatement();
+ String sql = "CREATE TABLE zoo (ZID INTEGER NOT NULL, family VARCHAR (20) NOT NULL, name VARCHAR (20) NOT NULL, PRIMARY KEY(ZID) )";
+ st.executeUpdate(sql);
+
+ String update = "update zoo set family = ? where name = ?;";
+ prst = conn.prepareStatement(update);
+ prst.setString(1, "cat");
+ prst.setString(2, "Yasha");
+ // st = conn.createStatement();
+ // st.execute("select * from zoo where family = 'cat'");
+ // ResultSet rs = st.getResultSet();
+ // assertEquals(0, getCount(rs));
+ prst.executeUpdate();
+ // st.execute("select * from zoo where family = 'cat'");
+ // ResultSet rs1 = st.getResultSet();
+ // assertEquals(1, getCount(rs1));
+ try {
+ prst = conn.prepareStatement("");
+ prst.execute();
+ fail("SQLException is not thrown");
+ } catch (SQLException e) {
+ // expected
+ }
+
+ try {
+ conn.prepareStatement(null);
+ fail("NPE is not thrown");
+ } catch (Exception e) {
+ // expected
+ }
+ try {
+ st = conn.createStatement();
+ st.execute("drop table if exists zoo");
+
+ } catch (SQLException e) {
+ fail("Couldn't drop table: " + e.getMessage());
+ } finally {
+ try {
+ st.close();
+ conn.close();
+ } catch (SQLException ee) {
+ }
+ }
+ } finally {
+ try {
+ if (prst != null) {
+ prst.close();
+ }
+ if (st != null) {
+ st.close();
+ }
+ } catch (SQLException ee) {
+ }
+ }
+
+ }
+
+}
diff --git a/core/tests/coretests/src/android/net/LocalSocketTest.java b/core/tests/coretests/src/android/net/LocalSocketTest.java
new file mode 100644
index 0000000..1349844
--- /dev/null
+++ b/core/tests/coretests/src/android/net/LocalSocketTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class LocalSocketTest extends TestCase {
+
+ @SmallTest
+ public void testBasic() throws Exception {
+ LocalServerSocket ss;
+ LocalSocket ls;
+ LocalSocket ls1;
+
+ ss = new LocalServerSocket("android.net.LocalSocketTest");
+
+ ls = new LocalSocket();
+
+ ls.connect(new LocalSocketAddress("android.net.LocalSocketTest"));
+
+ ls1 = ss.accept();
+
+ // Test trivial read and write
+ ls.getOutputStream().write(42);
+
+ assertEquals(42, ls1.getInputStream().read());
+
+ // Test getting credentials
+ Credentials c = ls1.getPeerCredentials();
+
+ MoreAsserts.assertNotEqual(0, c.getPid());
+
+ // Test sending and receiving file descriptors
+ ls.setFileDescriptorsForSend(
+ new FileDescriptor[]{FileDescriptor.in});
+
+ ls.getOutputStream().write(42);
+
+ assertEquals(42, ls1.getInputStream().read());
+
+ FileDescriptor[] out = ls1.getAncillaryFileDescriptors();
+
+ assertEquals(1, out.length);
+
+ // Test multible byte write and available()
+ ls1.getOutputStream().write(new byte[]{0, 1, 2, 3, 4, 5}, 1, 5);
+
+ assertEquals(1, ls.getInputStream().read());
+ assertEquals(4, ls.getInputStream().available());
+
+ byte[] buffer = new byte[16];
+ int countRead;
+
+ countRead = ls.getInputStream().read(buffer, 1, 15);
+
+ assertEquals(4, countRead);
+ assertEquals(2, buffer[1]);
+ assertEquals(3, buffer[2]);
+ assertEquals(4, buffer[3]);
+ assertEquals(5, buffer[4]);
+
+ // Try various array-out-of-bound cases
+ try {
+ ls.getInputStream().read(buffer, 1, 16);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ try {
+ ls.getOutputStream().write(buffer, 1, 16);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ try {
+ ls.getOutputStream().write(buffer, -1, 15);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ try {
+ ls.getOutputStream().write(buffer, 0, -1);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ try {
+ ls.getInputStream().read(buffer, -1, 15);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ try {
+ ls.getInputStream().read(buffer, 0, -1);
+ fail("expected exception");
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // excpected
+ }
+
+ // Try read of length 0
+ ls.getOutputStream().write(42);
+ countRead = ls1.getInputStream().read(buffer, 0, 0);
+ assertEquals(0, countRead);
+ assertEquals(42, ls1.getInputStream().read());
+
+ ss.close();
+
+ ls.close();
+
+ // Try write on closed socket
+
+ try {
+ ls.getOutputStream().write(42);
+ fail("expected exception");
+ } catch (IOException ex) {
+ // Expected
+ }
+
+ // Try read on closed socket
+
+ try {
+ ls.getInputStream().read();
+ fail("expected exception");
+ } catch (IOException ex) {
+ // Expected
+ }
+
+ // Try write on socket whose peer has closed
+
+ try {
+ ls1.getOutputStream().write(42);
+ fail("expected exception");
+ } catch (IOException ex) {
+ // Expected
+ }
+
+ // Try read on socket whose peer has closed
+
+ assertEquals(-1, ls1.getInputStream().read());
+
+ ls1.close();
+ }
+}
diff --git a/core/tests/coretests/src/android/net/SSLTest.java b/core/tests/coretests/src/android/net/SSLTest.java
new file mode 100644
index 0000000..810ed0d
--- /dev/null
+++ b/core/tests/coretests/src/android/net/SSLTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.net;
+
+import android.net.SSLCertificateSocketFactory;
+import android.test.suitebuilder.annotation.Suppress;
+import junit.framework.TestCase;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+//This test relies on network resources.
+@Suppress
+public class SSLTest extends TestCase {
+ public void testCertificate() throws Exception {
+ // test www.fortify.net/sslcheck.html
+ Socket ssl = SSLCertificateSocketFactory.getDefault().createSocket("www.fortify.net",443);
+ assertNotNull(ssl);
+
+ OutputStream out = ssl.getOutputStream();
+ assertNotNull(out);
+
+ InputStream in = ssl.getInputStream();
+ assertNotNull(in);
+
+ String get = "GET /sslcheck.html HTTP/1.1\r\nHost: 68.178.217.222\r\n\r\n";
+
+ // System.out.println("going for write...");
+ out.write(get.getBytes());
+
+ byte[] b = new byte[1024];
+ // System.out.println("going for read...");
+ int ret = in.read(b);
+
+ // System.out.println(new String(b));
+ }
+}
diff --git a/core/tests/coretests/src/android/net/UriMatcherTest.java b/core/tests/coretests/src/android/net/UriMatcherTest.java
new file mode 100644
index 0000000..2872144
--- /dev/null
+++ b/core/tests/coretests/src/android/net/UriMatcherTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+public class UriMatcherTest extends TestCase
+{
+ static final int ROOT = 0;
+ static final int PEOPLE = 1;
+ static final int PEOPLE_ID = 2;
+ static final int PEOPLE_PHONES = 3;
+ static final int PEOPLE_PHONES_ID = 4;
+ static final int PEOPLE_ADDRESSES = 5;
+ static final int PEOPLE_ADDRESSES_ID = 6;
+ static final int PEOPLE_CONTACTMETH = 7;
+ static final int PEOPLE_CONTACTMETH_ID = 8;
+ static final int CALLS = 9;
+ static final int CALLS_ID = 10;
+ static final int CALLERID = 11;
+ static final int CALLERID_TEXT = 12;
+ static final int FILTERRECENT = 13;
+
+ @SmallTest
+ public void testContentUris() {
+ check("content://asdf", UriMatcher.NO_MATCH);
+ check("content://people", PEOPLE);
+ check("content://people/1", PEOPLE_ID);
+ check("content://people/asdf", UriMatcher.NO_MATCH);
+ check("content://people/2/phones", PEOPLE_PHONES);
+ check("content://people/2/phones/3", PEOPLE_PHONES_ID);
+ check("content://people/2/phones/asdf", UriMatcher.NO_MATCH);
+ check("content://people/2/addresses", PEOPLE_ADDRESSES);
+ check("content://people/2/addresses/3", PEOPLE_ADDRESSES_ID);
+ check("content://people/2/addresses/asdf", UriMatcher.NO_MATCH);
+ check("content://people/2/contact-methods", PEOPLE_CONTACTMETH);
+ check("content://people/2/contact-methods/3", PEOPLE_CONTACTMETH_ID);
+ check("content://people/2/contact-methods/asdf", UriMatcher.NO_MATCH);
+ check("content://calls", CALLS);
+ check("content://calls/1", CALLS_ID);
+ check("content://calls/asdf", UriMatcher.NO_MATCH);
+ check("content://caller-id", CALLERID);
+ check("content://caller-id/asdf", CALLERID_TEXT);
+ check("content://caller-id/1", CALLERID_TEXT);
+ check("content://filter-recent", FILTERRECENT);
+ }
+
+ private static final UriMatcher mURLMatcher = new UriMatcher(ROOT);
+
+ static
+ {
+ mURLMatcher.addURI("people", null, PEOPLE);
+ mURLMatcher.addURI("people", "#", PEOPLE_ID);
+ mURLMatcher.addURI("people", "#/phones", PEOPLE_PHONES);
+ mURLMatcher.addURI("people", "#/phones/blah", PEOPLE_PHONES_ID);
+ mURLMatcher.addURI("people", "#/phones/#", PEOPLE_PHONES_ID);
+ mURLMatcher.addURI("people", "#/addresses", PEOPLE_ADDRESSES);
+ mURLMatcher.addURI("people", "#/addresses/#", PEOPLE_ADDRESSES_ID);
+ mURLMatcher.addURI("people", "#/contact-methods", PEOPLE_CONTACTMETH);
+ mURLMatcher.addURI("people", "#/contact-methods/#", PEOPLE_CONTACTMETH_ID);
+ mURLMatcher.addURI("calls", null, CALLS);
+ mURLMatcher.addURI("calls", "#", CALLS_ID);
+ mURLMatcher.addURI("caller-id", null, CALLERID);
+ mURLMatcher.addURI("caller-id", "*", CALLERID_TEXT);
+ mURLMatcher.addURI("filter-recent", null, FILTERRECENT);
+ }
+
+ void check(String uri, int expected)
+ {
+ int result = mURLMatcher.match(Uri.parse(uri));
+ if (result != expected) {
+ String msg = "failed on " + uri;
+ msg += " expected " + expected + " got " + result;
+ throw new RuntimeException(msg);
+ }
+ }
+}
+
diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java
new file mode 100644
index 0000000..a5fda20
--- /dev/null
+++ b/core/tests/coretests/src/android/net/UriTest.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.Uri;
+import android.content.ContentUris;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class UriTest extends TestCase {
+
+ @SmallTest
+ public void testToStringWithPathOnly() {
+ Uri.Builder builder = new Uri.Builder();
+
+ // Not a valid path, but this came from a user's test case.
+ builder.path("//foo");
+ Uri uri = builder.build();
+ assertEquals("//foo", uri.toString());
+ }
+
+ @SmallTest
+ public void testParcelling() {
+ parcelAndUnparcel(Uri.parse("foo:bob%20lee"));
+ parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment"));
+ parcelAndUnparcel(new Uri.Builder()
+ .scheme("http")
+ .authority("crazybob.org")
+ .path("/rss/")
+ .encodedQuery("a=b")
+ .fragment("foo")
+ .build());
+ }
+
+ private void parcelAndUnparcel(Uri u) {
+ Parcel p = Parcel.obtain();
+ try {
+ Uri.writeToParcel(p, u);
+ p.setDataPosition(0);
+ assertEquals(u, Uri.CREATOR.createFromParcel(p));
+
+ p.setDataPosition(0);
+ u = u.buildUpon().build();
+ Uri.writeToParcel(p, u);
+ p.setDataPosition(0);
+ assertEquals(u, Uri.CREATOR.createFromParcel(p));
+ }
+ finally {
+ p.recycle();
+ }
+ }
+
+ @SmallTest
+ public void testBuildUponOpaqueStringUri() {
+ Uri u = Uri.parse("bob:lee").buildUpon().scheme("robert").build();
+ assertEquals("robert", u.getScheme());
+ assertEquals("lee", u.getEncodedSchemeSpecificPart());
+ assertEquals("lee", u.getSchemeSpecificPart());
+ assertNull(u.getQuery());
+ assertNull(u.getPath());
+ assertNull(u.getAuthority());
+ assertNull(u.getHost());
+ }
+
+ @SmallTest
+ public void testStringUri() {
+ assertEquals("bob lee",
+ Uri.parse("foo:bob%20lee").getSchemeSpecificPart());
+ assertEquals("bob%20lee",
+ Uri.parse("foo:bob%20lee").getEncodedSchemeSpecificPart());
+ assertEquals("/bob%20lee",
+ Uri.parse("foo:/bob%20lee").getEncodedPath());
+ assertNull(Uri.parse("foo:bob%20lee").getPath());
+ assertEquals("bob%20lee",
+ Uri.parse("foo:?bob%20lee").getEncodedQuery());
+ assertNull(Uri.parse("foo:bar#?bob%20lee").getQuery());
+ assertEquals("bob%20lee",
+ Uri.parse("foo:#bob%20lee").getEncodedFragment());
+ }
+
+ @SmallTest
+ public void testStringUriIsHierarchical() {
+ assertTrue(Uri.parse("bob").isHierarchical());
+ assertFalse(Uri.parse("bob:").isHierarchical());
+ }
+
+ @SmallTest
+ public void testNullUriString() {
+ try {
+ Uri.parse(null);
+ fail();
+ } catch (NullPointerException e) {}
+ }
+
+ @SmallTest
+ public void testNullFile() {
+ try {
+ Uri.fromFile(null);
+ fail();
+ } catch (NullPointerException e) {}
+ }
+
+ @SmallTest
+ public void testCompareTo() {
+ Uri a = Uri.parse("foo:a");
+ Uri b = Uri.parse("foo:b");
+ Uri b2 = Uri.parse("foo:b");
+
+ assertTrue(a.compareTo(b) < 0);
+ assertTrue(b.compareTo(a) > 0);
+ assertEquals(0, b.compareTo(b2));
+ }
+
+ @SmallTest
+ public void testEqualsAndHashCode() {
+
+ Uri a = Uri.parse("http://crazybob.org/test/?foo=bar#tee");
+
+ Uri b = new Uri.Builder()
+ .scheme("http")
+ .authority("crazybob.org")
+ .path("/test/")
+ .encodedQuery("foo=bar")
+ .fragment("tee")
+ .build();
+
+ // Try alternate builder methods.
+ Uri c = new Uri.Builder()
+ .scheme("http")
+ .encodedAuthority("crazybob.org")
+ .encodedPath("/test/")
+ .encodedQuery("foo=bar")
+ .encodedFragment("tee")
+ .build();
+
+ assertFalse(Uri.EMPTY.equals(null));
+
+ assertEquals(a, b);
+ assertEquals(b, c);
+ assertEquals(c, a);
+
+ assertEquals(a.hashCode(), b.hashCode());
+ assertEquals(b.hashCode(), c.hashCode());
+ }
+
+ @SmallTest
+ public void testAuthorityParsing() {
+ Uri uri = Uri.parse("http://localhost:42");
+ assertEquals("localhost", uri.getHost());
+ assertEquals(42, uri.getPort());
+
+ uri = Uri.parse("http://bob@localhost:42");
+ assertEquals("bob", uri.getUserInfo());
+ assertEquals("localhost", uri.getHost());
+ assertEquals(42, uri.getPort());
+
+ uri = Uri.parse("http://bob%20lee@localhost:42");
+ assertEquals("bob lee", uri.getUserInfo());
+ assertEquals("bob%20lee", uri.getEncodedUserInfo());
+
+ uri = Uri.parse("http://bob%40lee%3ajr@local%68ost:4%32");
+ assertEquals("bob@lee:jr", uri.getUserInfo());
+ assertEquals("localhost", uri.getHost());
+ assertEquals(42, uri.getPort());
+
+ uri = Uri.parse("http://localhost");
+ assertEquals("localhost", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ }
+
+ @SmallTest
+ public void testBuildUponOpaqueUri() {
+ Uri a = Uri.fromParts("foo", "bar", "tee");
+ Uri b = a.buildUpon().fragment("new").build();
+ assertEquals("new", b.getFragment());
+ assertEquals("bar", b.getSchemeSpecificPart());
+ assertEquals("foo", b.getScheme());
+ }
+
+ @SmallTest
+ public void testBuildUponEncodedOpaqueUri() {
+ Uri a = new Uri.Builder()
+ .scheme("foo")
+ .encodedOpaquePart("bar")
+ .fragment("tee")
+ .build();
+ Uri b = a.buildUpon().fragment("new").build();
+ assertEquals("new", b.getFragment());
+ assertEquals("bar", b.getSchemeSpecificPart());
+ assertEquals("foo", b.getScheme());
+ }
+
+ @SmallTest
+ public void testPathSegmentDecoding() {
+ Uri uri = Uri.parse("foo://bar/a%20a/b%20b");
+ assertEquals("a a", uri.getPathSegments().get(0));
+ assertEquals("b b", uri.getPathSegments().get(1));
+ }
+
+ @SmallTest
+ public void testSms() {
+ Uri base = Uri.parse("content://sms");
+ Uri appended = base.buildUpon()
+ .appendEncodedPath("conversations/addr=555-1212")
+ .build();
+ assertEquals("content://sms/conversations/addr=555-1212",
+ appended.toString());
+ assertEquals(2, appended.getPathSegments().size());
+ assertEquals("conversations", appended.getPathSegments().get(0));
+ assertEquals("addr=555-1212", appended.getPathSegments().get(1));
+ }
+
+ @SmallTest
+ public void testEncodeWithAllowedChars() {
+ String encoded = Uri.encode("Bob:/", "/");
+ assertEquals(-1, encoded.indexOf(':'));
+ assertTrue(encoded.indexOf('/') > -1);
+ }
+
+ @SmallTest
+ public void testEncodeDecode() {
+ code(null);
+ code("");
+ code("Bob");
+ code(":Bob");
+ code("::Bob");
+ code("Bob::Lee");
+ code("Bob:Lee");
+ code("Bob::");
+ code("Bob:");
+ code("::Bob::");
+ }
+
+ private void code(String s) {
+ assertEquals(s, Uri.decode(Uri.encode(s, null)));
+ }
+
+ @SmallTest
+ public void testFile() {
+ File f = new File("/tmp/bob");
+
+ Uri uri = Uri.fromFile(f);
+
+ assertEquals("file:///tmp/bob", uri.toString());
+ }
+
+ @SmallTest
+ public void testQueryParameters() {
+ Uri uri = Uri.parse("content://user");
+
+ assertEquals(null, uri.getQueryParameter("a"));
+
+ uri = uri.buildUpon().appendQueryParameter("a", "b").build();
+
+ assertEquals("b", uri.getQueryParameter("a"));
+
+ uri = uri.buildUpon().appendQueryParameter("a", "b2").build();
+
+ assertEquals(Arrays.asList("b", "b2"), uri.getQueryParameters("a"));
+
+ uri = uri.buildUpon().appendQueryParameter("c", "d").build();
+
+ assertEquals(Arrays.asList("b", "b2"), uri.getQueryParameters("a"));
+ assertEquals("d", uri.getQueryParameter("c"));
+ }
+
+ @SmallTest
+ public void testSchemeOnly() {
+ Uri uri = Uri.parse("empty:");
+ assertEquals("empty", uri.getScheme());
+ assertTrue(uri.isAbsolute());
+ assertNull(uri.getPath());
+ }
+
+ @SmallTest
+ public void testEmptyPath() {
+ Uri uri = Uri.parse("content://user");
+ assertEquals(0, uri.getPathSegments().size());
+ }
+
+ @SmallTest
+ public void testPathOperations() {
+ Uri uri = Uri.parse("content://user/a/b");
+
+ assertEquals(2, uri.getPathSegments().size());
+ assertEquals("b", uri.getLastPathSegment());
+
+ Uri first = uri;
+ uri = uri.buildUpon().appendPath("c").build();
+
+ assertEquals(3, uri.getPathSegments().size());
+ assertEquals("c", uri.getLastPathSegment());
+ assertEquals("content://user/a/b/c", uri.toString());
+
+ uri = ContentUris.withAppendedId(uri, 100);
+
+ assertEquals(4, uri.getPathSegments().size());
+ assertEquals("100", uri.getLastPathSegment());
+ assertEquals(100, ContentUris.parseId(uri));
+ assertEquals("content://user/a/b/c/100", uri.toString());
+
+ // Make sure the original URI is still intact.
+ assertEquals(2, first.getPathSegments().size());
+ assertEquals("b", first.getLastPathSegment());
+
+ try {
+ first.getPathSegments().get(2);
+ fail();
+ } catch (IndexOutOfBoundsException e) {}
+
+ assertEquals(null, Uri.EMPTY.getLastPathSegment());
+
+ Uri withC = Uri.parse("foo:/a/b/").buildUpon().appendPath("c").build();
+ assertEquals("/a/b/c", withC.getPath());
+ }
+
+ @SmallTest
+ public void testOpaqueUri() {
+ Uri uri = Uri.parse("mailto:nobody");
+ testOpaqueUri(uri);
+
+ uri = uri.buildUpon().build();
+ testOpaqueUri(uri);
+
+ uri = Uri.fromParts("mailto", "nobody", null);
+ testOpaqueUri(uri);
+
+ uri = uri.buildUpon().build();
+ testOpaqueUri(uri);
+
+ uri = new Uri.Builder()
+ .scheme("mailto")
+ .opaquePart("nobody")
+ .build();
+ testOpaqueUri(uri);
+
+ uri = uri.buildUpon().build();
+ testOpaqueUri(uri);
+ }
+
+ private void testOpaqueUri(Uri uri) {
+ assertEquals("mailto", uri.getScheme());
+ assertEquals("nobody", uri.getSchemeSpecificPart());
+ assertEquals("nobody", uri.getEncodedSchemeSpecificPart());
+
+ assertNull(uri.getFragment());
+ assertTrue(uri.isAbsolute());
+ assertTrue(uri.isOpaque());
+ assertFalse(uri.isRelative());
+ assertFalse(uri.isHierarchical());
+
+ assertNull(uri.getAuthority());
+ assertNull(uri.getEncodedAuthority());
+ assertNull(uri.getPath());
+ assertNull(uri.getEncodedPath());
+ assertNull(uri.getUserInfo());
+ assertNull(uri.getEncodedUserInfo());
+ assertNull(uri.getQuery());
+ assertNull(uri.getEncodedQuery());
+ assertNull(uri.getHost());
+ assertEquals(-1, uri.getPort());
+
+ assertTrue(uri.getPathSegments().isEmpty());
+ assertNull(uri.getLastPathSegment());
+
+ assertEquals("mailto:nobody", uri.toString());
+
+ Uri withFragment = uri.buildUpon().fragment("top").build();
+ assertEquals("mailto:nobody#top", withFragment.toString());
+ }
+
+ @SmallTest
+ public void testHierarchicalUris() {
+ testHierarchical("http", "google.com", "/p1/p2", "query", "fragment");
+ testHierarchical("file", null, "/p1/p2", null, null);
+ testHierarchical("content", "contact", "/p1/p2", null, null);
+ testHierarchical("http", "google.com", "/p1/p2", null, "fragment");
+ testHierarchical("http", "google.com", "", null, "fragment");
+ testHierarchical("http", "google.com", "", "query", "fragment");
+ testHierarchical("http", "google.com", "", "query", null);
+ testHierarchical("http", null, "/", "query", null);
+ }
+
+ private static void testHierarchical(String scheme, String authority,
+ String path, String query, String fragment) {
+ StringBuilder sb = new StringBuilder();
+
+ if (authority != null) {
+ sb.append("//").append(authority);
+ }
+ if (path != null) {
+ sb.append(path);
+ }
+ if (query != null) {
+ sb.append('?').append(query);
+ }
+
+ String ssp = sb.toString();
+
+ if (scheme != null) {
+ sb.insert(0, scheme + ":");
+ }
+ if (fragment != null) {
+ sb.append('#').append(fragment);
+ }
+
+ String uriString = sb.toString();
+
+ Uri uri = Uri.parse(uriString);
+
+ // Run these twice to test caching.
+ compareHierarchical(
+ uriString, ssp, uri, scheme, authority, path, query, fragment);
+ compareHierarchical(
+ uriString, ssp, uri, scheme, authority, path, query, fragment);
+
+ // Test rebuilt version.
+ uri = uri.buildUpon().build();
+
+ // Run these twice to test caching.
+ compareHierarchical(
+ uriString, ssp, uri, scheme, authority, path, query, fragment);
+ compareHierarchical(
+ uriString, ssp, uri, scheme, authority, path, query, fragment);
+
+ // The decoded and encoded versions of the inputs are all the same.
+ // We'll test the actual encoding decoding separately.
+
+ // Test building with encoded versions.
+ Uri built = new Uri.Builder()
+ .scheme(scheme)
+ .encodedAuthority(authority)
+ .encodedPath(path)
+ .encodedQuery(query)
+ .encodedFragment(fragment)
+ .build();
+
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+
+ // Test building with decoded versions.
+ built = new Uri.Builder()
+ .scheme(scheme)
+ .authority(authority)
+ .path(path)
+ .query(query)
+ .fragment(fragment)
+ .build();
+
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+
+ // Rebuild.
+ built = built.buildUpon().build();
+
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+ compareHierarchical(
+ uriString, ssp, built, scheme, authority, path, query, fragment);
+ }
+
+ private static void compareHierarchical(String uriString, String ssp,
+ Uri uri,
+ String scheme, String authority, String path, String query,
+ String fragment) {
+ assertEquals(scheme, uri.getScheme());
+ assertEquals(authority, uri.getAuthority());
+ assertEquals(authority, uri.getEncodedAuthority());
+ assertEquals(path, uri.getPath());
+ assertEquals(path, uri.getEncodedPath());
+ assertEquals(query, uri.getQuery());
+ assertEquals(query, uri.getEncodedQuery());
+ assertEquals(fragment, uri.getFragment());
+ assertEquals(fragment, uri.getEncodedFragment());
+ assertEquals(ssp, uri.getSchemeSpecificPart());
+
+ if (scheme != null) {
+ assertTrue(uri.isAbsolute());
+ assertFalse(uri.isRelative());
+ } else {
+ assertFalse(uri.isAbsolute());
+ assertTrue(uri.isRelative());
+ }
+
+ assertFalse(uri.isOpaque());
+ assertTrue(uri.isHierarchical());
+
+ assertEquals(uriString, uri.toString());
+ }
+
+ public void testEmptyToStringNotNull() {
+ assertNotNull(Uri.EMPTY.toString());
+ }
+
+ @SmallTest
+ public void testParcellingWithoutFragment() {
+ parcelAndUnparcel(Uri.parse("foo:bob%20lee"));
+ parcelAndUnparcel(Uri.fromParts("foo", "bob lee", "fragment"));
+ parcelAndUnparcel(new Uri.Builder()
+ .scheme("http")
+ .authority("crazybob.org")
+ .path("/rss/")
+ .encodedQuery("a=b")
+ .build());
+ }
+
+ public void testGetQueryParameter() {
+ String nestedUrl = "http://crazybob.org/?a=1&b=2";
+ Uri uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("foo", "bar")
+ .appendQueryParameter("nested", nestedUrl).build();
+ assertEquals(nestedUrl, uri.getQueryParameter("nested"));
+ assertEquals(nestedUrl, uri.getQueryParameters("nested").get(0));
+ }
+
+ public void testGetQueryParameterWorkaround() {
+ // This was a workaround for a bug where getQueryParameter called
+ // getQuery() instead of getEncodedQuery().
+ String nestedUrl = "http://crazybob.org/?a=1&b=2";
+ Uri uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("foo", "bar")
+ .appendQueryParameter("nested", Uri.encode(nestedUrl)).build();
+ assertEquals(nestedUrl, Uri.decode(uri.getQueryParameter("nested")));
+ assertEquals(nestedUrl,
+ Uri.decode(uri.getQueryParameters("nested").get(0)));
+ }
+
+ public void testGetQueryParameterEdgeCases() {
+ Uri uri;
+
+ // key at beginning of URL
+ uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("key", "a b")
+ .appendQueryParameter("keya", "c d")
+ .appendQueryParameter("bkey", "e f")
+ .build();
+ assertEquals("a b", uri.getQueryParameter("key"));
+
+ // key in middle of URL
+ uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("akeyb", "a b")
+ .appendQueryParameter("keya", "c d")
+ .appendQueryParameter("key", "e f")
+ .appendQueryParameter("bkey", "g h")
+ .build();
+ assertEquals("e f", uri.getQueryParameter("key"));
+
+ // key at end of URL
+ uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("akeyb", "a b")
+ .appendQueryParameter("keya", "c d")
+ .appendQueryParameter("key", "y z")
+ .build();
+ assertEquals("y z", uri.getQueryParameter("key"));
+
+ // key is a substring of parameters, but not present
+ uri = Uri.parse("http://test/").buildUpon()
+ .appendQueryParameter("akeyb", "a b")
+ .appendQueryParameter("keya", "c d")
+ .appendQueryParameter("bkey", "e f")
+ .build();
+ assertNull(uri.getQueryParameter("key"));
+
+ // key is a prefix or suffix of the query
+ uri = Uri.parse("http://test/?qq=foo");
+ assertNull(uri.getQueryParameter("q"));
+ assertNull(uri.getQueryParameter("oo"));
+
+ // escaped keys
+ uri = Uri.parse("http://www.google.com/?a%20b=foo&c%20d=");
+ assertEquals("foo", uri.getQueryParameter("a b"));
+ assertEquals("", uri.getQueryParameter("c d"));
+ assertNull(uri.getQueryParameter("e f"));
+ assertNull(uri.getQueryParameter("b"));
+ assertNull(uri.getQueryParameter("c"));
+ assertNull(uri.getQueryParameter(" d"));
+
+ // empty values
+ uri = Uri.parse("http://www.google.com/?a=&b=&&c=");
+ assertEquals("", uri.getQueryParameter("a"));
+ assertEquals("", uri.getQueryParameter("b"));
+ assertEquals("", uri.getQueryParameter("c"));
+ }
+}
diff --git a/core/tests/coretests/src/android/net/http/SslCertificateTest.java b/core/tests/coretests/src/android/net/http/SslCertificateTest.java
new file mode 100644
index 0000000..147816b
--- /dev/null
+++ b/core/tests/coretests/src/android/net/http/SslCertificateTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.net.http.SslCertificate;
+import android.test.suitebuilder.annotation.LargeTest;
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import junit.framework.TestCase;
+
+public class SslCertificateTest extends TestCase {
+
+ /**
+ * Problematic certificate from Issue 1597
+ * http://code.google.com/p/android/issues/detail?id=1597
+ */
+ private static final String Issue1597Certificate =
+ "-----BEGIN CERTIFICATE-----\n"+
+ "MIIBnjCCAQegAwIBAgIFAKvN774wDQYJKoZIhvcNAQEFBQAwADAeFw0yNzA5MjQw\n"+
+ "MDAwMDFaFw0zNzA5MjQwMDAwMDFaMAAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ\n"+
+ "AoGBAMNAaUSKw3stg6UHx6bWHNn0T5WR39UB43EZqdhhM0hnfpzwAzNs1T3jOAzF\n"+
+ "OtgcX/XVt2Exc1vnwwuiJfvtPtBtQVsNu7wfk45cTUF45axBr4v8oFq7DOHCvs2C\n"+
+ "pBDnw/v9PoOihuBamOjzRPL+oVhVfzEqEOILnZD1qEeVJn4RAgMBAAGjJDAiMCAG\n"+
+ "A1UdEQQZMBeGD2h0dHBzOi8vMS4xLjEuMYcEAQEBATANBgkqhkiG9w0BAQUFAAOB\n"+
+ "gQA7CMJylEjCR9CjztZUMLOutLe64RNhMq9iKgbDfJwYrcgvUNOxjrCdFW66lE9N\n"+
+ "TDscc4zS2kpV41vcVYiGwabCNUPi2P6zfFSpYmGqwwu1NoEayqGPdDMrgCnMXVYV\n"+
+ "X7HoVif4IdGvjFQrYcyU2VWSWBq6IGMVCR6RkC2YWnnNhw==\n"+
+ "-----END CERTIFICATE-----\n";
+
+ @LargeTest
+ public void testSslCertificateWithEmptyIssuer() throws Exception {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate x509Certificate = (X509Certificate)
+ certificateFactory.generateCertificate(new ByteArrayInputStream(Issue1597Certificate.getBytes()));
+ assertEquals(x509Certificate.getIssuerDN().getName(), "");
+ SslCertificate sslCertificate = new SslCertificate(x509Certificate);
+ assertEquals(sslCertificate.getIssuedBy().getDName(), "");
+ }
+}
diff --git a/core/tests/coretests/src/android/os/AidlTest.aidl b/core/tests/coretests/src/android/os/AidlTest.aidl
new file mode 100644
index 0000000..6004f4b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/AidlTest.aidl
@@ -0,0 +1,20 @@
+/* //device/apps/AndroidTests/src/com.android.unit_tests/AidlTest.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable AidlTest.TestParcelable;
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
new file mode 100644
index 0000000..bf11d56
--- /dev/null
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.google.android.collect.Lists;
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class AidlTest extends TestCase {
+
+ private IAidlTest mRemote;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ AidlObject mLocal = new AidlObject();
+ mRemote = IAidlTest.Stub.asInterface(mLocal);
+ }
+
+ private static boolean check(TestParcelable p, int n, String s) {
+ return p.mAnInt == n &&
+ ((s == null && p.mAString == null) || s.equals(p.mAString));
+ }
+
+ public static class TestParcelable implements Parcelable {
+ public int mAnInt;
+ public String mAString;
+
+ public TestParcelable() {
+ }
+
+ public TestParcelable(int i, String s) {
+ mAnInt = i;
+ mAString = s;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mAnInt);
+ parcel.writeString(mAString);
+ }
+
+ public void readFromParcel(Parcel parcel) {
+ mAnInt = parcel.readInt();
+ mAString = parcel.readString();
+ }
+
+ public static final Parcelable.Creator<TestParcelable> CREATOR
+ = new Parcelable.Creator<TestParcelable>() {
+ public TestParcelable createFromParcel(Parcel parcel) {
+ return new TestParcelable(parcel.readInt(),
+ parcel.readString());
+ }
+
+ public TestParcelable[] newArray(int size) {
+ return new TestParcelable[size];
+ }
+ };
+
+ public String toString() {
+ return super.toString() + " {" + mAnInt + "/" + mAString + "}";
+ }
+ }
+
+ private static class AidlObject extends IAidlTest.Stub {
+ public IInterface queryLocalInterface(String descriptor) {
+ // overriding this to return null makes asInterface always
+ // generate a proxy
+ return null;
+ }
+
+ public int intMethod(int a) {
+ return a;
+ }
+
+ public TestParcelable parcelableIn(TestParcelable p) {
+ p.mAnInt++;
+ return p;
+ }
+
+ public TestParcelable parcelableOut(TestParcelable p) {
+ p.mAnInt = 44;
+ return p;
+ }
+
+ public TestParcelable parcelableInOut(TestParcelable p) {
+ p.mAnInt++;
+ return p;
+ }
+
+ public TestParcelable listParcelableLonger(List<TestParcelable> list, int index) {
+ list.add(list.get(index));
+ return list.get(index);
+ }
+
+ public int listParcelableShorter(List<TestParcelable> list, int index) {
+ list.remove(index);
+ return list.size();
+ }
+
+ public boolean[] booleanArray(boolean[] a0, boolean[] a1, boolean[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public char[] charArray(char[] a0, char[] a1, char[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public int[] intArray(int[] a0, int[] a1, int[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public long[] longArray(long[] a0, long[] a1, long[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public float[] floatArray(float[] a0, float[] a1, float[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public double[] doubleArray(double[] a0, double[] a1, double[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public String[] stringArray(String[] a0, String[] a1, String[] a2) {
+ for (int i = 0; i < a0.length && i < a2.length; i++) {
+ a2[i] = a0[i];
+ }
+ for (int i = 0; i < a0.length && i < a1.length; i++) {
+ a1[i] = a0[i];
+ }
+ return a0;
+ }
+
+ public TestParcelable[] parcelableArray(TestParcelable[] a0,
+ TestParcelable[] a1, TestParcelable[] a2) {
+ return null;
+ }
+
+ public void voidSecurityException() {
+ throw new SecurityException("gotcha!");
+ }
+
+ public int intSecurityException() {
+ throw new SecurityException("gotcha!");
+ }
+ }
+
+ @SmallTest
+ public void testInt() throws Exception {
+ int result = mRemote.intMethod(42);
+ assertEquals(42, result);
+ }
+
+ @SmallTest
+ public void testParcelableIn() throws Exception {
+ TestParcelable arg = new TestParcelable(43, "hi");
+ TestParcelable result = mRemote.parcelableIn(arg);
+ assertNotSame(arg, result);
+
+ assertEquals(43, arg.mAnInt);
+ assertEquals(44, result.mAnInt);
+ }
+
+ @SmallTest
+ public void testParcelableOut() throws Exception {
+ TestParcelable arg = new TestParcelable(43, "hi");
+ TestParcelable result = mRemote.parcelableOut(arg);
+ assertNotSame(arg, result);
+ assertEquals(44, arg.mAnInt);
+ }
+
+ @SmallTest
+ public void testParcelableInOut() throws Exception {
+ TestParcelable arg = new TestParcelable(43, "hi");
+ TestParcelable result = mRemote.parcelableInOut(arg);
+ assertNotSame(arg, result);
+ assertEquals(44, arg.mAnInt);
+ }
+
+ @SmallTest
+ public void testListParcelableLonger() throws Exception {
+ List<TestParcelable> list = Lists.newArrayList();
+ list.add(new TestParcelable(33, "asdf"));
+ list.add(new TestParcelable(34, "jkl;"));
+
+ TestParcelable result = mRemote.listParcelableLonger(list, 1);
+
+// System.out.println("result=" + result);
+// for (TestParcelable p : list) {
+// System.out.println("longer: " + p);
+// }
+
+ assertEquals("jkl;", result.mAString);
+ assertEquals(34, result.mAnInt);
+
+ assertEquals(3, list.size());
+ assertTrue("out parameter 0: " + list.get(0), check(list.get(0), 33, "asdf"));
+ assertTrue("out parameter 1: " + list.get(1), check(list.get(1), 34, "jkl;"));
+ assertTrue("out parameter 2: " + list.get(2), check(list.get(2), 34, "jkl;"));
+
+ assertNotSame(list.get(1), list.get(2));
+ }
+
+ @SmallTest
+ public void testListParcelableShorter() throws Exception {
+ List<TestParcelable> list = Lists.newArrayList();
+ list.add(new TestParcelable(33, "asdf"));
+ list.add(new TestParcelable(34, "jkl;"));
+ list.add(new TestParcelable(35, "qwerty"));
+
+ int result = mRemote.listParcelableShorter(list, 2);
+
+// System.out.println("result=" + result);
+// for (TestParcelable p : list) {
+// System.out.println("shorter: " + p);
+// }
+
+ assertEquals(2, result);
+ assertEquals(2, list.size());
+ assertTrue("out parameter 0: " + list.get(0), check(list.get(0), 33, "asdf"));
+ assertTrue("out parameter 1: " + list.get(1), check(list.get(1), 34, "jkl;"));
+
+ assertNotSame(list.get(0), list.get(1));
+ }
+
+ @SmallTest
+ public void testArrays() throws Exception {
+ // boolean
+ boolean[] b0 = new boolean[]{true};
+ boolean[] b1 = new boolean[]{false, true};
+ boolean[] b2 = new boolean[]{true, false, true};
+ boolean[] br = mRemote.booleanArray(b0, b1, b2);
+
+ assertEquals(1, br.length);
+ assertTrue(br[0]);
+
+ assertTrue(b1[0]);
+ assertFalse(b1[1]);
+
+ assertTrue(b2[0]);
+ assertFalse(b2[1]);
+ assertTrue(b2[2]);
+
+ // char
+ char[] c0 = new char[]{'a'};
+ char[] c1 = new char[]{'b', 'c'};
+ char[] c2 = new char[]{'d', 'e', 'f'};
+ char[] cr = mRemote.charArray(c0, c1, c2);
+
+ assertEquals(1, cr.length);
+ assertEquals('a', cr[0]);
+
+ assertEquals('a', c1[0]);
+ assertEquals('\0', c1[1]);
+
+ assertEquals('a', c2[0]);
+ assertEquals('e', c2[1]);
+ assertEquals('f', c2[2]);
+
+ // int
+ int[] i0 = new int[]{34};
+ int[] i1 = new int[]{38, 39};
+ int[] i2 = new int[]{42, 43, 44};
+ int[] ir = mRemote.intArray(i0, i1, i2);
+
+ assertEquals(1, ir.length);
+ assertEquals(34, ir[0]);
+
+ assertEquals(34, i1[0]);
+ assertEquals(0, i1[1]);
+
+ assertEquals(34, i2[0]);
+ assertEquals(43, i2[1]);
+ assertEquals(44, i2[2]);
+
+ // long
+ long[] l0 = new long[]{50};
+ long[] l1 = new long[]{51, 52};
+ long[] l2 = new long[]{53, 54, 55};
+ long[] lr = mRemote.longArray(l0, l1, l2);
+
+ assertEquals(1, lr.length);
+ assertEquals(50, lr[0]);
+
+ assertEquals(50, l1[0]);
+ assertEquals(0, l1[1]);
+
+ assertEquals(50, l2[0]);
+ assertEquals(54, l2[1]);
+ assertEquals(55, l2[2]);
+
+ // float
+ float[] f0 = new float[]{90.1f};
+ float[] f1 = new float[]{90.2f, 90.3f};
+ float[] f2 = new float[]{90.4f, 90.5f, 90.6f};
+ float[] fr = mRemote.floatArray(f0, f1, f2);
+
+ assertEquals(1, fr.length);
+ assertEquals(90.1f, fr[0]);
+
+ assertEquals(90.1f, f1[0]);
+ assertEquals(0f, f1[1], 0.0f);
+
+ assertEquals(90.1f, f2[0]);
+ assertEquals(90.5f, f2[1]);
+ assertEquals(90.6f, f2[2]);
+
+ // double
+ double[] d0 = new double[]{100.1};
+ double[] d1 = new double[]{100.2, 100.3};
+ double[] d2 = new double[]{100.4, 100.5, 100.6};
+ double[] dr = mRemote.doubleArray(d0, d1, d2);
+
+ assertEquals(1, dr.length);
+ assertEquals(100.1, dr[0]);
+
+ assertEquals(100.1, d1[0]);
+ assertEquals(0, d1[1], 0.0);
+
+ assertEquals(100.1, d2[0]);
+ assertEquals(100.5, d2[1]);
+ assertEquals(100.6, d2[2]);
+
+ // String
+ String[] s0 = new String[]{"s0[0]"};
+ String[] s1 = new String[]{"s1[0]", "s1[1]"};
+ String[] s2 = new String[]{"s2[0]", "s2[1]", "s2[2]"};
+ String[] sr = mRemote.stringArray(s0, s1, s2);
+
+ assertEquals(1, sr.length);
+ assertEquals("s0[0]", sr[0]);
+
+ assertEquals("s0[0]", s1[0]);
+ assertNull(s1[1]);
+
+ assertEquals("s0[0]", s2[0]);
+ assertEquals("s2[1]", s2[1]);
+ assertEquals("s2[2]", s2[2]);
+ }
+
+ @SmallTest
+ public void testVoidSecurityException() throws Exception {
+ boolean good = false;
+ try {
+ mRemote.voidSecurityException();
+ } catch (SecurityException e) {
+ good = true;
+ }
+ assertEquals(good, true);
+ }
+
+ @SmallTest
+ public void testIntSecurityException() throws Exception {
+ boolean good = false;
+ try {
+ mRemote.intSecurityException();
+ } catch (SecurityException e) {
+ good = true;
+ }
+ assertEquals(good, true);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityService.java b/core/tests/coretests/src/android/os/BinderThreadPriorityService.java
new file mode 100644
index 0000000..47a4483
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityService.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Service used by {@link BinderThreadPriorityTest} to verify
+ * the conveyance of thread priorities over Binder.
+ */
+public class BinderThreadPriorityService extends Service {
+ private static final String TAG = "BinderThreadPriorityService";
+
+ private final IBinderThreadPriorityService.Stub mBinder =
+ new IBinderThreadPriorityService.Stub() {
+ public int getThreadPriority() {
+ return Process.getThreadPriority(Process.myTid());
+ }
+
+ public String getThreadSchedulerGroup() {
+ return BinderThreadPriorityTest.getSchedulerGroup();
+ }
+
+ public void callBack(IBinderThreadPriorityService recurse) {
+ try {
+ recurse.callBack(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Binder callback failed", e);
+ }
+ }
+
+ public void setPriorityAndCallBack(int priority, IBinderThreadPriorityService recurse) {
+ Process.setThreadPriority(priority);
+ try {
+ recurse.callBack(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Binder callback failed", e);
+ }
+ }
+ };
+
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+}
diff --git a/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
new file mode 100644
index 0000000..7a4980a
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderThreadPriorityTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Test whether Binder calls inherit thread priorities correctly.
+ */
+public class BinderThreadPriorityTest extends AndroidTestCase {
+ private static final String TAG = "BinderThreadPriorityTest";
+ private IBinderThreadPriorityService mService;
+ private int mSavedPriority;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (BinderThreadPriorityTest.this) {
+ mService = IBinderThreadPriorityService.Stub.asInterface(service);
+ BinderThreadPriorityTest.this.notifyAll();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+ };
+
+ private static class ServiceStub extends IBinderThreadPriorityService.Stub {
+ public int getThreadPriority() { fail(); return -999; }
+ public String getThreadSchedulerGroup() { fail(); return null; }
+ public void setPriorityAndCallBack(int p, IBinderThreadPriorityService cb) { fail(); }
+ public void callBack(IBinderThreadPriorityService cb) { fail(); }
+ private static void fail() { throw new RuntimeException("unimplemented"); }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ getContext().bindService(
+ new Intent(getContext(), BinderThreadPriorityService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
+
+ synchronized (this) {
+ if (mService == null) {
+ try {
+ wait(30000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ assertNotNull("Gave up waiting for BinderThreadPriorityService", mService);
+ }
+ }
+
+ mSavedPriority = Process.getThreadPriority(Process.myTid());
+ Process.setThreadPriority(mSavedPriority); // To realign priority & cgroup, if needed
+ assertEquals(expectedSchedulerGroup(mSavedPriority), getSchedulerGroup());
+ Log.i(TAG, "Saved priority: " + mSavedPriority);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // HACK -- see bug 2665914 -- setThreadPriority() doesn't always set the
+ // scheduler group reliably unless we start out with background priority.
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ Process.setThreadPriority(mSavedPriority);
+ assertEquals(mSavedPriority, Process.getThreadPriority(Process.myTid()));
+ assertEquals(expectedSchedulerGroup(mSavedPriority), getSchedulerGroup());
+
+ getContext().unbindService(mConnection);
+ super.tearDown();
+ }
+
+ public static String getSchedulerGroup() {
+ String fn = "/proc/" + Process.myPid() + "/task/" + Process.myTid() + "/cgroup";
+ try {
+ String cgroup = FileUtils.readTextFile(new File(fn), 1024, null);
+ for (String line : cgroup.split("\n")) {
+ String fields[] = line.trim().split(":");
+ if (fields.length == 3 && fields[1].equals("cpu")) return fields[2];
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can't read: " + fn, e);
+ }
+ return null; // Unknown
+ }
+
+ public static String expectedSchedulerGroup(int prio) {
+ return prio < Process.THREAD_PRIORITY_BACKGROUND ? "/" : "/bg_non_interactive";
+ }
+
+ public void testPassPriorityToService() throws Exception {
+ for (int prio = 19; prio >= -20; prio--) {
+ Process.setThreadPriority(prio);
+
+ // Local
+ assertEquals(prio, Process.getThreadPriority(Process.myTid()));
+ assertEquals(expectedSchedulerGroup(prio), getSchedulerGroup());
+
+ // Remote
+ assertEquals(prio, mService.getThreadPriority());
+ assertEquals(expectedSchedulerGroup(prio), mService.getThreadSchedulerGroup());
+ }
+ }
+
+ public void testCallBackFromServiceWithPriority() throws Exception {
+ for (int prio = -20; prio <= 19; prio++) {
+ final int expected = prio;
+ mService.setPriorityAndCallBack(prio, new ServiceStub() {
+ public void callBack(IBinderThreadPriorityService cb) {
+ assertEquals(expected, Process.getThreadPriority(Process.myTid()));
+ assertEquals(expectedSchedulerGroup(expected), getSchedulerGroup());
+ }
+ });
+
+ assertEquals(mSavedPriority, Process.getThreadPriority(Process.myTid()));
+
+ // BROKEN -- see bug 2665954 -- scheduler group doesn't get reset
+ // properly after a back-call with a different priority.
+ // assertEquals(expectedSchedulerGroup(mSavedPriority), getSchedulerGroup());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/BrightnessLimit.java b/core/tests/coretests/src/android/os/BrightnessLimit.java
new file mode 100644
index 0000000..5e9b906
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BrightnessLimit.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IPowerManager;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Tries to set the brightness to 0. Should be silently thwarted by the framework.
+ */
+public class BrightnessLimit extends Activity implements OnClickListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.brightness_limit);
+
+ Button b = (Button) findViewById(R.id.go);
+ b.setOnClickListener(this);
+ }
+
+ public void onClick(View v) {
+ IPowerManager power = IPowerManager.Stub.asInterface(
+ ServiceManager.getService("power"));
+ if (power != null) {
+ try {
+ power.setBacklightBrightness(0);
+ } catch (RemoteException darn) {
+
+ }
+ }
+ Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 0);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java
new file mode 100644
index 0000000..551ea8d
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BroadcasterTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Broadcaster;
+import android.os.Handler;
+import android.os.Message;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import junit.framework.TestCase;
+
+public class BroadcasterTest extends TestCase {
+ private static final int MESSAGE_A = 23234;
+ private static final int MESSAGE_B = 3;
+ private static final int MESSAGE_C = 14;
+ private static final int MESSAGE_D = 95;
+
+ @MediumTest
+ public void test1() throws Exception {
+ /*
+ * One handler requestes one message, with a translation
+ */
+ HandlerTester tester = new HandlerTester() {
+ Handler h;
+
+ public void go() {
+ Broadcaster b = new Broadcaster();
+ h = new H();
+
+ b.request(MESSAGE_A, h, MESSAGE_B);
+
+ Message msg = new Message();
+ msg.what = MESSAGE_A;
+
+ b.broadcast(msg);
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_B) {
+ success();
+ } else {
+ failure();
+ }
+ }
+ };
+ tester.doTest(1000);
+ }
+
+ private static class Tests2and3 extends HandlerTester {
+ Tests2and3(int n) {
+ N = n;
+ }
+
+ int N;
+ Handler mHandlers[];
+ boolean mSuccess[];
+
+ public void go() {
+ Broadcaster b = new Broadcaster();
+ mHandlers = new Handler[N];
+ mSuccess = new boolean[N];
+ for (int i = 0; i < N; i++) {
+ mHandlers[i] = new H();
+ mSuccess[i] = false;
+ b.request(MESSAGE_A, mHandlers[i], MESSAGE_B + i);
+ }
+
+ Message msg = new Message();
+ msg.what = MESSAGE_A;
+
+ b.broadcast(msg);
+ }
+
+ public void handleMessage(Message msg) {
+ int index = msg.what - MESSAGE_B;
+ if (index < 0 || index >= N) {
+ failure();
+ } else {
+ if (msg.getTarget() == mHandlers[index]) {
+ mSuccess[index] = true;
+ }
+ }
+ boolean winner = true;
+ for (int i = 0; i < N; i++) {
+ if (!mSuccess[i]) {
+ winner = false;
+ }
+ }
+ if (winner) {
+ success();
+ }
+ }
+ }
+
+ @MediumTest
+ public void test2() throws Exception {
+ /*
+ * 2 handlers request the same message, with different translations
+ */
+ HandlerTester tester = new Tests2and3(2);
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void test3() throws Exception {
+ /*
+ * 1000 handlers request the same message, with different translations
+ */
+ HandlerTester tester = new Tests2and3(10);
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void test4() throws Exception {
+ /*
+ * Two handlers request different messages, with translations, sending
+ * only one. The other one should never get sent.
+ */
+ HandlerTester tester = new HandlerTester() {
+ Handler h1;
+ Handler h2;
+
+ public void go() {
+ Broadcaster b = new Broadcaster();
+ h1 = new H();
+ h2 = new H();
+
+ b.request(MESSAGE_A, h1, MESSAGE_C);
+ b.request(MESSAGE_B, h2, MESSAGE_D);
+
+ Message msg = new Message();
+ msg.what = MESSAGE_A;
+
+ b.broadcast(msg);
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_C && msg.getTarget() == h1) {
+ success();
+ } else {
+ failure();
+ }
+ }
+ };
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void test5() throws Exception {
+ /*
+ * Two handlers request different messages, with translations, sending
+ * only one. The other one should never get sent.
+ */
+ HandlerTester tester = new HandlerTester() {
+ Handler h1;
+ Handler h2;
+
+ public void go() {
+ Broadcaster b = new Broadcaster();
+ h1 = new H();
+ h2 = new H();
+
+ b.request(MESSAGE_A, h1, MESSAGE_C);
+ b.request(MESSAGE_B, h2, MESSAGE_D);
+
+ Message msg = new Message();
+ msg.what = MESSAGE_B;
+
+ b.broadcast(msg);
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_D && msg.getTarget() == h2) {
+ success();
+ } else {
+ failure();
+ }
+ }
+ };
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void test6() throws Exception {
+ /*
+ * Two handlers request same message. Cancel the request for the
+ * 2nd handler, make sure the first still works.
+ */
+ HandlerTester tester = new HandlerTester() {
+ Handler h1;
+ Handler h2;
+
+ public void go() {
+ Broadcaster b = new Broadcaster();
+ h1 = new H();
+ h2 = new H();
+
+ b.request(MESSAGE_A, h1, MESSAGE_C);
+ b.request(MESSAGE_A, h2, MESSAGE_D);
+ b.cancelRequest(MESSAGE_A, h2, MESSAGE_D);
+
+ Message msg = new Message();
+ msg.what = MESSAGE_A;
+
+ b.broadcast(msg);
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == MESSAGE_C && msg.getTarget() == h1) {
+ success();
+ } else {
+ failure();
+ }
+ }
+ };
+ tester.doTest(1000);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
new file mode 100644
index 0000000..3758627
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Build;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+/**
+ * Provides test cases for android.os.Build and, in turn, many of the
+ * system properties set by the build system.
+ */
+public class BuildTest extends TestCase {
+
+ private static final String TAG = "BuildTest";
+
+ /**
+ * Asserts that a String is non-null and non-empty. If it is not,
+ * an AssertionFailedError is thrown with the given message.
+ */
+ private static void assertNotEmpty(String message, String string) {
+ //Log.i(TAG, "" + message + ": " + string);
+ assertNotNull(message, string);
+ assertFalse(message, string.equals(""));
+ }
+
+ /**
+ * Asserts that a String is non-null and non-empty. If it is not,
+ * an AssertionFailedError is thrown.
+ */
+ private static void assertNotEmpty(String string) {
+ assertNotEmpty(null, string);
+ }
+
+ /**
+ * Asserts that all android.os.Build fields are non-empty and/or in a valid range.
+ */
+ @SmallTest
+ public void testBuildFields() throws Exception {
+ assertNotEmpty("ID", Build.ID);
+ assertNotEmpty("DISPLAY", Build.DISPLAY);
+ assertNotEmpty("PRODUCT", Build.PRODUCT);
+ assertNotEmpty("DEVICE", Build.DEVICE);
+ assertNotEmpty("BOARD", Build.BOARD);
+ assertNotEmpty("BRAND", Build.BRAND);
+ assertNotEmpty("MODEL", Build.MODEL);
+ assertNotEmpty("VERSION.INCREMENTAL", Build.VERSION.INCREMENTAL);
+ assertNotEmpty("VERSION.RELEASE", Build.VERSION.RELEASE);
+ assertNotEmpty("TYPE", Build.TYPE);
+ Assert.assertNotNull("TAGS", Build.TAGS); // TAGS is allowed to be empty.
+ assertNotEmpty("FINGERPRINT", Build.FINGERPRINT);
+ Assert.assertTrue("TIME", Build.TIME > 0);
+ assertNotEmpty("USER", Build.USER);
+ assertNotEmpty("HOST", Build.HOST);
+
+ // TODO: if any of the android.os.Build fields have additional constraints
+ // (e.g., must be a C identifier, must be a valid filename, must not contain any spaces)
+ // add tests for them.
+ }
+}
diff --git a/core/tests/coretests/src/android/os/FileObserverTest.java b/core/tests/coretests/src/android/os/FileObserverTest.java
new file mode 100644
index 0000000..ca4e0d6
--- /dev/null
+++ b/core/tests/coretests/src/android/os/FileObserverTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.os;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.os.FileObserver;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+public class FileObserverTest extends AndroidTestCase {
+ private Observer mObserver;
+ private File mTestFile;
+
+ private static class Observer extends FileObserver {
+ public List<Map> events = Lists.newArrayList();
+ public int totalEvents = 0;
+
+ public Observer(String path) {
+ super(path);
+ }
+
+ public void onEvent(int event, String path) {
+ synchronized (this) {
+ totalEvents++;
+ Map<String, Object> map = Maps.newHashMap();
+
+ map.put("event", event);
+ map.put("path", path);
+
+ events.add(map);
+
+ this.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mTestFile = File.createTempFile(".file_observer_test", ".txt");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mTestFile != null && mTestFile.exists()) {
+ mTestFile.delete();
+ }
+ }
+
+ @LargeTest
+ public void testRun() throws Exception {
+ // make file changes and wait for them
+ assertTrue(mTestFile.exists());
+ assertNotNull(mTestFile.getParent());
+
+ mObserver = new Observer(mTestFile.getParent());
+ mObserver.startWatching();
+
+ FileOutputStream out = new FileOutputStream(mTestFile);
+ try {
+ out.write(0x20);
+ waitForEvent(); // open
+ waitForEvent(); // modify
+
+ mTestFile.delete();
+ waitForEvent(); // modify
+ waitForEvent(); // delete
+
+ mObserver.stopWatching();
+
+ // Ensure that we have seen at least 3 events.
+ assertTrue(mObserver.totalEvents > 3);
+ } finally {
+ out.close();
+ }
+ }
+
+ private void waitForEvent() {
+ synchronized (mObserver) {
+ boolean done = false;
+ while (!done) {
+ try {
+ mObserver.wait(2000);
+ done = true;
+ } catch (InterruptedException e) {
+ }
+ }
+
+ Iterator<Map> it = mObserver.events.iterator();
+
+ while (it.hasNext()) {
+ Map map = it.next();
+ Log.i("FileObserverTest", "event: " + getEventString((Integer)map.get("event")) + " path: " + map.get("path"));
+ }
+
+ mObserver.events.clear();
+ }
+ }
+
+ private String getEventString(int event) {
+ switch (event) {
+ case FileObserver.ACCESS:
+ return "ACCESS";
+ case FileObserver.MODIFY:
+ return "MODIFY";
+ case FileObserver.ATTRIB:
+ return "ATTRIB";
+ case FileObserver.CLOSE_WRITE:
+ return "CLOSE_WRITE";
+ case FileObserver.CLOSE_NOWRITE:
+ return "CLOSE_NOWRITE";
+ case FileObserver.OPEN:
+ return "OPEN";
+ case FileObserver.MOVED_FROM:
+ return "MOVED_FROM";
+ case FileObserver.MOVED_TO:
+ return "MOVED_TO";
+ case FileObserver.CREATE:
+ return "CREATE";
+ case FileObserver.DELETE:
+ return "DELETE";
+ case FileObserver.DELETE_SELF:
+ return "DELETE_SELF";
+ case FileObserver.MOVE_SELF:
+ return "MOVE_SELF";
+ default:
+ return "UNKNOWN";
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
new file mode 100644
index 0000000..f12cbe1
--- /dev/null
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.FileUtils.FileStatus;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import junit.framework.Assert;
+
+public class FileUtilsTest extends AndroidTestCase {
+ private static final String TEST_DATA =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ private File mTestFile;
+ private File mCopyFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ File testDir = getContext().getDir("testing", Context.MODE_PRIVATE);
+ mTestFile = new File(testDir, "test.file");
+ mCopyFile = new File(testDir, "copy.file");
+ FileWriter writer = new FileWriter(mTestFile);
+ try {
+ writer.write(TEST_DATA, 0, TEST_DATA.length());
+ } finally {
+ writer.close();
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mTestFile.exists()) mTestFile.delete();
+ if (mCopyFile.exists()) mCopyFile.delete();
+ }
+
+ @LargeTest
+ public void testGetFileStatus() {
+ final byte[] MAGIC = { 0xB, 0xE, 0x0, 0x5 };
+
+ try {
+ // truncate test file and write MAGIC (4 bytes) to it.
+ FileOutputStream os = new FileOutputStream(mTestFile, false);
+ os.write(MAGIC, 0, 4);
+ os.flush();
+ os.close();
+ } catch (FileNotFoundException e) {
+ Assert.fail("File was removed durning test" + e);
+ } catch (IOException e) {
+ Assert.fail("Unexpected IOException: " + e);
+ }
+
+ Assert.assertTrue(mTestFile.exists());
+ Assert.assertTrue(FileUtils.getFileStatus(mTestFile.getPath(), null));
+
+ FileStatus status1 = new FileStatus();
+ FileUtils.getFileStatus(mTestFile.getPath(), status1);
+
+ Assert.assertEquals(4, status1.size);
+
+ // Sleep for at least one second so that the modification time will be different.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+
+ try {
+ // append so we don't change the creation time.
+ FileOutputStream os = new FileOutputStream(mTestFile, true);
+ os.write(MAGIC, 0, 4);
+ os.flush();
+ os.close();
+ } catch (FileNotFoundException e) {
+ Assert.fail("File was removed durning test" + e);
+ } catch (IOException e) {
+ Assert.fail("Unexpected IOException: " + e);
+ }
+
+ FileStatus status2 = new FileStatus();
+ FileUtils.getFileStatus(mTestFile.getPath(), status2);
+
+ Assert.assertEquals(8, status2.size);
+ Assert.assertTrue(status2.mtime > status1.mtime);
+
+ mTestFile.delete();
+
+ Assert.assertFalse(mTestFile.exists());
+ Assert.assertFalse(FileUtils.getFileStatus(mTestFile.getPath(), null));
+ }
+
+ // TODO: test setPermissions(), getPermissions()
+
+ @MediumTest
+ public void testCopyFile() throws Exception {
+ assertFalse(mCopyFile.exists());
+ FileUtils.copyFile(mTestFile, mCopyFile);
+ assertTrue(mCopyFile.exists());
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
+ }
+
+ @MediumTest
+ public void testCopyToFile() throws Exception {
+ final String s = "Foo Bar";
+ assertFalse(mCopyFile.exists());
+ FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile); assertTrue(mCopyFile.exists());
+ assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
+ }
+
+ @MediumTest
+ public void testIsFilenameSafe() throws Exception {
+ assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
+ assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
+ assertFalse(FileUtils.isFilenameSafe(new File("foo*bar")));
+ assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
+ }
+
+ @MediumTest
+ public void testReadTextFile() throws Exception {
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null));
+
+ assertEquals("ABCDE", FileUtils.readTextFile(mTestFile, 5, null));
+ assertEquals("ABCDE<>", FileUtils.readTextFile(mTestFile, 5, "<>"));
+ assertEquals(TEST_DATA.substring(0, 51) + "<>",
+ FileUtils.readTextFile(mTestFile, 51, "<>"));
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 52, "<>"));
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 100, "<>"));
+
+ assertEquals("vwxyz", FileUtils.readTextFile(mTestFile, -5, null));
+ assertEquals("<>vwxyz", FileUtils.readTextFile(mTestFile, -5, "<>"));
+ assertEquals("<>" + TEST_DATA.substring(1, 52),
+ FileUtils.readTextFile(mTestFile, -51, "<>"));
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -52, "<>"));
+ assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
+ }
+
+ @MediumTest
+ public void testReadTextFileWithZeroLengthFile() throws Exception {
+ new FileOutputStream(mTestFile).close(); // Zero out the file
+ assertEquals("", FileUtils.readTextFile(mTestFile, 0, null));
+ assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>"));
+ assertEquals("", FileUtils.readTextFile(mTestFile, 10, "<>"));
+ assertEquals("", FileUtils.readTextFile(mTestFile, -1, "<>"));
+ assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
+ }
+}
diff --git a/core/tests/coretests/src/android/os/HandlerTester.java b/core/tests/coretests/src/android/os/HandlerTester.java
new file mode 100644
index 0000000..a216a0b
--- /dev/null
+++ b/core/tests/coretests/src/android/os/HandlerTester.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+public abstract class HandlerTester extends Thread {
+ public abstract void go();
+ public abstract void handleMessage(Message msg);
+
+ public HandlerTester() {
+ }
+
+ public void doTest(long timeout) {
+ start();
+
+ synchronized (this) {
+ try {
+ wait(timeout);
+ quit();
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+ if (!mDone) {
+ throw new RuntimeException("test timed out");
+ }
+ if (!mSuccess) {
+ throw new RuntimeException("test failed");
+ }
+ }
+
+ public void success() {
+ mDone = true;
+ mSuccess = true;
+ }
+
+ public void failure() {
+ mDone = true;
+ mSuccess = false;
+ }
+
+ public void run() {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ go();
+ Looper.loop();
+ }
+
+ protected class H extends Handler {
+ public void handleMessage(Message msg) {
+ synchronized (HandlerTester.this) {
+ // Call into them with our monitor locked, so they don't have
+ // to deal with other races.
+ HandlerTester.this.handleMessage(msg);
+ if (mDone) {
+ HandlerTester.this.notify();
+ quit();
+ }
+ }
+ }
+ }
+
+ private void quit() {
+ mLooper.quit();
+ }
+
+ private boolean mDone = false;
+ private boolean mSuccess = false;
+ private Looper mLooper;
+}
+
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
new file mode 100644
index 0000000..9772aa4
--- /dev/null
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.os;
+
+import junit.framework.TestCase;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Process;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class HandlerThreadTest extends TestCase {
+ private static final int TEST_WHAT = 1;
+
+ private boolean mGotMessage = false;
+ private int mGotMessageWhat = -1;
+ private volatile boolean mDidSetup = false;
+ private volatile int mLooperTid = -1;
+
+ @MediumTest
+ public void testHandlerThread() throws Exception {
+ HandlerThread th1 = new HandlerThread("HandlerThreadTest") {
+ protected void onLooperPrepared() {
+ synchronized (HandlerThreadTest.this) {
+ mDidSetup = true;
+ mLooperTid = Process.myTid();
+ HandlerThreadTest.this.notify();
+ }
+ }
+ };
+
+ assertFalse(th1.isAlive());
+ assertNull(th1.getLooper());
+
+ th1.start();
+
+ assertTrue(th1.isAlive());
+ assertNotNull(th1.getLooper());
+
+ // The call to getLooper() internally blocks until the looper is
+ // available, but will call onLooperPrepared() after that. So we
+ // need to block here to wait for our onLooperPrepared() to complete
+ // and fill in the values we expect.
+ synchronized (this) {
+ while (!mDidSetup) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+
+ // Make sure that the process was set.
+ assertNotSame(-1, mLooperTid);
+ // Make sure that the onLooperPrepared() was called on a different thread.
+ assertNotSame(Process.myTid(), mLooperTid);
+
+ final Handler h1 = new Handler(th1.getLooper()) {
+ public void handleMessage(Message msg) {
+ assertEquals(TEST_WHAT, msg.what);
+ // Ensure that we are running on the same thread in which the looper was setup on.
+ assertEquals(mLooperTid, Process.myTid());
+
+ mGotMessageWhat = msg.what;
+ mGotMessage = true;
+ synchronized(this) {
+ notifyAll();
+ }
+ }
+ };
+
+ Message msg = h1.obtainMessage(TEST_WHAT);
+
+ synchronized (h1) {
+ // wait until we have the lock before sending the message.
+ h1.sendMessage(msg);
+ try {
+ // wait for the message to be handled
+ h1.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ assertTrue(mGotMessage);
+ assertEquals(TEST_WHAT, mGotMessageWhat);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java
new file mode 100644
index 0000000..89b3fb6
--- /dev/null
+++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java
@@ -0,0 +1,1640 @@
+/**
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import junit.framework.TestCase;
+
+import android.os.Debug;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.util.Log;
+
+import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.ProcessedMessages;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Test for HierarchicalStateMachine.
+ *
+ * @author wink@google.com (Wink Saville)
+ */
+public class HierarchicalStateMachineTest extends TestCase {
+ private static final int TEST_CMD_1 = 1;
+ private static final int TEST_CMD_2 = 2;
+ private static final int TEST_CMD_3 = 3;
+ private static final int TEST_CMD_4 = 4;
+ private static final int TEST_CMD_5 = 5;
+ private static final int TEST_CMD_6 = 6;
+
+ private static final boolean DBG = true;
+ private static final boolean WAIT_FOR_DEBUGGER = true;
+ private static final String TAG = "HierarchicalStateMachineTest";
+
+ /**
+ * Tests that we can quit the state machine.
+ */
+ class StateMachineQuitTest extends HierarchicalStateMachine {
+ private int mQuitCount = 0;
+
+ StateMachineQuitTest(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (isQuit(message)) {
+ mQuitCount += 1;
+ if (mQuitCount > 2) {
+ // Returning false to actually quit
+ return false;
+ } else {
+ // Do NOT quit
+ return true;
+ }
+ } else {
+ // All other message are handled
+ return true;
+ }
+ }
+ }
+
+ @Override
+ protected void quitting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachineQuitTest mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ @SmallTest
+ public void testStateMachineQuitTest() throws Exception {
+ //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest");
+ smQuitTest.start();
+ if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest E");
+
+ synchronized (smQuitTest) {
+ // Send 6 messages
+ for (int i = 1; i <= 6; i++) {
+ smQuitTest.sendMessage(i);
+ }
+
+ // First two are ignored
+ smQuitTest.quit();
+ smQuitTest.quit();
+
+ // Now we will quit
+ smQuitTest.quit();
+
+ try {
+ // wait for the messages to be handled
+ smQuitTest.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachineQuitTest: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertTrue(smQuitTest.getProcessedMessagesCount() == 9);
+
+ ProcessedMessages.Info pmi;
+
+ // The first two message didn't quit and were handled by mS1
+ pmi = smQuitTest.getProcessedMessage(6);
+ assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(smQuitTest.mS1, pmi.getState());
+ assertEquals(smQuitTest.mS1, pmi.getOriginalState());
+
+ pmi = smQuitTest.getProcessedMessage(7);
+ assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(smQuitTest.mS1, pmi.getState());
+ assertEquals(smQuitTest.mS1, pmi.getOriginalState());
+
+ // The last message was never handled so the states are null
+ pmi = smQuitTest.getProcessedMessage(8);
+ assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(null, pmi.getState());
+ assertEquals(null, pmi.getOriginalState());
+
+ if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest X");
+ }
+
+ /**
+ * Test enter/exit can use transitionTo
+ */
+ class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
+ StateMachineEnterExitTransitionToTest(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+ addState(mS2);
+ addState(mS3);
+ addState(mS4);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected void enter() {
+ // Test that a transition in enter and the initial state works
+ mS1EnterCount += 1;
+ transitionTo(mS2);
+ Log.d(TAG, "S1.enter");
+ }
+ @Override protected void exit() {
+ mS1ExitCount += 1;
+ Log.d(TAG, "S1.exit");
+ }
+ }
+
+ class S2 extends HierarchicalState {
+ @Override protected void enter() {
+ mS2EnterCount += 1;
+ Log.d(TAG, "S2.enter");
+ }
+ @Override protected void exit() {
+ // Test transition in exit work
+ mS2ExitCount += 1;
+ transitionTo(mS4);
+ Log.d(TAG, "S2.exit");
+ }
+ @Override protected boolean processMessage(Message message) {
+ // Start a transition to S3 but it will be
+ // changed to a transition to S4
+ transitionTo(mS3);
+ Log.d(TAG, "S2.processMessage");
+ return true;
+ }
+ }
+
+ class S3 extends HierarchicalState {
+ @Override protected void enter() {
+ // Test that we can do halting in an enter/exit
+ transitionToHaltingState();
+ mS3EnterCount += 1;
+ Log.d(TAG, "S3.enter");
+ }
+ @Override protected void exit() {
+ mS3ExitCount += 1;
+ Log.d(TAG, "S3.exit");
+ }
+ }
+
+
+ class S4 extends HierarchicalState {
+ @Override protected void enter() {
+ // Test that we can do halting in an enter/exit
+ transitionToHaltingState();
+ mS4EnterCount += 1;
+ Log.d(TAG, "S4.enter");
+ }
+ @Override protected void exit() {
+ mS4ExitCount += 1;
+ Log.d(TAG, "S4.exit");
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachineEnterExitTransitionToTest mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+ private S3 mS3 = new S3();
+ private S4 mS4 = new S4();
+ private int mS1EnterCount = 0;
+ private int mS1ExitCount = 0;
+ private int mS2EnterCount = 0;
+ private int mS2ExitCount = 0;
+ private int mS3EnterCount = 0;
+ private int mS3ExitCount = 0;
+ private int mS4EnterCount = 0;
+ private int mS4ExitCount = 0;
+ }
+
+ @SmallTest
+ public void testStateMachineEnterExitTransitionToTest() throws Exception {
+ //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachineEnterExitTransitionToTest smEnterExitTranstionToTest =
+ new StateMachineEnterExitTransitionToTest("smEnterExitTranstionToTest");
+ smEnterExitTranstionToTest.start();
+ if (smEnterExitTranstionToTest.isDbg()) {
+ Log.d(TAG, "testStateMachineEnterExitTransitionToTest E");
+ }
+
+ synchronized (smEnterExitTranstionToTest) {
+ smEnterExitTranstionToTest.sendMessage(1);
+
+ try {
+ // wait for the messages to be handled
+ smEnterExitTranstionToTest.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachineEnterExitTransitionToTest: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ assertTrue(smEnterExitTranstionToTest.getProcessedMessagesCount() == 1);
+
+ ProcessedMessages.Info pmi;
+
+ // Message should be handled by mS2.
+ pmi = smEnterExitTranstionToTest.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(smEnterExitTranstionToTest.mS2, pmi.getState());
+ assertEquals(smEnterExitTranstionToTest.mS2, pmi.getOriginalState());
+
+ assertEquals(smEnterExitTranstionToTest.mS1EnterCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS1ExitCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS2EnterCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS2ExitCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS3EnterCount, 1);
+ assertEquals(smEnterExitTranstionToTest.mS3ExitCount, 1);
+
+ if (smEnterExitTranstionToTest.isDbg()) {
+ Log.d(TAG, "testStateMachineEnterExitTransitionToTest X");
+ }
+ }
+
+ /**
+ * Tests that ProcessedMessage works as a circular buffer.
+ */
+ class StateMachine0 extends HierarchicalStateMachine {
+ StateMachine0(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+ setProcessedMessagesSize(3);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_6) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine0 mThisSm;
+ private S1 mS1 = new S1();
+ }
+
+ @SmallTest
+ public void testStateMachine0() throws Exception {
+ //if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+ StateMachine0 sm0 = new StateMachine0("sm0");
+ sm0.start();
+ if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 E");
+
+ synchronized (sm0) {
+ // Send 6 messages
+ for (int i = 1; i <= 6; i++) {
+ sm0.sendMessage(sm0.obtainMessage(i));
+ }
+
+ try {
+ // wait for the messages to be handled
+ sm0.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine0: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertTrue(sm0.getProcessedMessagesCount() == 6);
+ assertTrue(sm0.getProcessedMessagesSize() == 3);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm0.getProcessedMessage(0);
+ assertEquals(TEST_CMD_4, pmi.getWhat());
+ assertEquals(sm0.mS1, pmi.getState());
+ assertEquals(sm0.mS1, pmi.getOriginalState());
+
+ pmi = sm0.getProcessedMessage(1);
+ assertEquals(TEST_CMD_5, pmi.getWhat());
+ assertEquals(sm0.mS1, pmi.getState());
+ assertEquals(sm0.mS1, pmi.getOriginalState());
+
+ pmi = sm0.getProcessedMessage(2);
+ assertEquals(TEST_CMD_6, pmi.getWhat());
+ assertEquals(sm0.mS1, pmi.getState());
+ assertEquals(sm0.mS1, pmi.getOriginalState());
+
+ if (sm0.isDbg()) Log.d(TAG, "testStateMachine0 X");
+ }
+
+ /**
+ * This tests enter/exit and transitions to the same state.
+ * The state machine has one state, it receives two messages
+ * in state mS1. With the first message it transitions to
+ * itself which causes it to be exited and reentered.
+ */
+ class StateMachine1 extends HierarchicalStateMachine {
+ StateMachine1(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) Log.d(TAG, "StateMachine1: ctor X");
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected void enter() {
+ mEnterCount++;
+ }
+
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_1) {
+ assertEquals(1, mEnterCount);
+ assertEquals(0, mExitCount);
+ transitionTo(mS1);
+ } else if (message.what == TEST_CMD_2) {
+ assertEquals(2, mEnterCount);
+ assertEquals(1, mExitCount);
+ transitionToHaltingState();
+ }
+ return true;
+ }
+
+ @Override protected void exit() {
+ mExitCount++;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine1 mThisSm;
+ private S1 mS1 = new S1();
+
+ private int mEnterCount;
+ private int mExitCount;
+ }
+
+ @SmallTest
+ public void testStateMachine1() throws Exception {
+ StateMachine1 sm1 = new StateMachine1("sm1");
+ sm1.start();
+ if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 E");
+
+ synchronized (sm1) {
+ // Send two messages
+ sm1.sendMessage(TEST_CMD_1);
+ sm1.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm1.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine1: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertEquals(2, sm1.mEnterCount);
+ assertEquals(2, sm1.mExitCount);
+
+ assertTrue(sm1.getProcessedMessagesSize() == 2);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm1.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm1.mS1, pmi.getState());
+ assertEquals(sm1.mS1, pmi.getOriginalState());
+
+ pmi = sm1.getProcessedMessage(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm1.mS1, pmi.getState());
+ assertEquals(sm1.mS1, pmi.getOriginalState());
+
+ assertEquals(2, sm1.mEnterCount);
+ assertEquals(2, sm1.mExitCount);
+
+ if (sm1.isDbg()) Log.d(TAG, "testStateMachine1 X");
+ }
+
+ /**
+ * Test deferring messages and states with no parents. The state machine
+ * has two states, it receives two messages in state mS1 deferring them
+ * until what == TEST_CMD_2 and then transitions to state mS2. State
+ * mS2 then receives both of the deferred messages first TEST_CMD_1 and
+ * then TEST_CMD_2.
+ */
+ class StateMachine2 extends HierarchicalStateMachine {
+ StateMachine2(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup the hierarchy
+ addState(mS1);
+ addState(mS2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) Log.d(TAG, "StateMachine2: ctor X");
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected void enter() {
+ mDidEnter = true;
+ }
+
+ @Override protected boolean processMessage(Message message) {
+ deferMessage(message);
+ if (message.what == TEST_CMD_2) {
+ transitionTo(mS2);
+ }
+ return true;
+ }
+
+ @Override protected void exit() {
+ mDidExit = true;
+ }
+ }
+
+ class S2 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine2 mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+
+ private boolean mDidEnter = false;
+ private boolean mDidExit = false;
+ }
+
+ @SmallTest
+ public void testStateMachine2() throws Exception {
+ StateMachine2 sm2 = new StateMachine2("sm2");
+ sm2.start();
+ if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 E");
+
+ synchronized (sm2) {
+ // Send two messages
+ sm2.sendMessage(TEST_CMD_1);
+ sm2.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm2.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine2: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertTrue(sm2.getProcessedMessagesSize() == 4);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm2.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm2.mS1, pmi.getState());
+
+ pmi = sm2.getProcessedMessage(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm2.mS1, pmi.getState());
+
+ pmi = sm2.getProcessedMessage(2);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm2.mS2, pmi.getState());
+
+ pmi = sm2.getProcessedMessage(3);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm2.mS2, pmi.getState());
+
+ assertTrue(sm2.mDidEnter);
+ assertTrue(sm2.mDidExit);
+
+ if (sm2.isDbg()) Log.d(TAG, "testStateMachine2 X");
+ }
+
+ /**
+ * Test that unhandled messages in a child are handled by the parent.
+ * When TEST_CMD_2 is received.
+ */
+ class StateMachine3 extends HierarchicalStateMachine {
+ StateMachine3(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup the simplest hierarchy of two states
+ // mParentState and mChildState.
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState);
+ addState(mChildState, mParentState);
+
+ // Set the initial state will be the child
+ setInitialState(mChildState);
+ if (DBG) Log.d(TAG, "StateMachine3: ctor X");
+ }
+
+ class ParentState extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+ }
+
+ class ChildState extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ return false;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine3 mThisSm;
+ private ParentState mParentState = new ParentState();
+ private ChildState mChildState = new ChildState();
+ }
+
+ @SmallTest
+ public void testStateMachine3() throws Exception {
+ StateMachine3 sm3 = new StateMachine3("sm3");
+ sm3.start();
+ if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 E");
+
+ synchronized (sm3) {
+ // Send two messages
+ sm3.sendMessage(TEST_CMD_1);
+ sm3.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm3.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine3: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertTrue(sm3.getProcessedMessagesSize() == 2);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm3.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm3.mParentState, pmi.getState());
+ assertEquals(sm3.mChildState, pmi.getOriginalState());
+
+ pmi = sm3.getProcessedMessage(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm3.mParentState, pmi.getState());
+ assertEquals(sm3.mChildState, pmi.getOriginalState());
+
+ if (sm3.isDbg()) Log.d(TAG, "testStateMachine3 X");
+ }
+
+ /**
+ * Test a hierarchy of 3 states a parent and two children
+ * with transition from child 1 to child 2 and child 2
+ * lets the parent handle the messages.
+ */
+ class StateMachine4 extends HierarchicalStateMachine {
+ StateMachine4(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup a hierarchy of three states
+ // mParentState, mChildState1 & mChildState2
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState);
+ addState(mChildState1, mParentState);
+ addState(mChildState2, mParentState);
+
+ // Set the initial state will be child 1
+ setInitialState(mChildState1);
+ if (DBG) Log.d(TAG, "StateMachine4: ctor X");
+ }
+
+ class ParentState extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+ }
+
+ class ChildState1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ transitionTo(mChildState2);
+ return true;
+ }
+ }
+
+ class ChildState2 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ return false;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine4 mThisSm;
+ private ParentState mParentState = new ParentState();
+ private ChildState1 mChildState1 = new ChildState1();
+ private ChildState2 mChildState2 = new ChildState2();
+ }
+
+ @SmallTest
+ public void testStateMachine4() throws Exception {
+ StateMachine4 sm4 = new StateMachine4("sm4");
+ sm4.start();
+ if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 E");
+
+ synchronized (sm4) {
+ // Send two messages
+ sm4.sendMessage(TEST_CMD_1);
+ sm4.sendMessage(TEST_CMD_2);
+
+ try {
+ // wait for the messages to be handled
+ sm4.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine4: exception while waiting " + e.getMessage());
+ }
+ }
+
+
+ assertTrue(sm4.getProcessedMessagesSize() == 2);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm4.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm4.mChildState1, pmi.getState());
+ assertEquals(sm4.mChildState1, pmi.getOriginalState());
+
+ pmi = sm4.getProcessedMessage(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm4.mParentState, pmi.getState());
+ assertEquals(sm4.mChildState2, pmi.getOriginalState());
+
+ if (sm4.isDbg()) Log.d(TAG, "testStateMachine4 X");
+ }
+
+ /**
+ * Test transition from one child to another of a "complex"
+ * hierarchy with two parents and multiple children.
+ */
+ class StateMachine5 extends HierarchicalStateMachine {
+ StateMachine5(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup a hierarchy with two parents and some children.
+ // (Use indentation to help visualize hierarchy)
+ addState(mParentState1);
+ addState(mChildState1, mParentState1);
+ addState(mChildState2, mParentState1);
+
+ addState(mParentState2);
+ addState(mChildState3, mParentState2);
+ addState(mChildState4, mParentState2);
+ addState(mChildState5, mChildState4);
+
+ // Set the initial state will be the child
+ setInitialState(mChildState1);
+ if (DBG) Log.d(TAG, "StateMachine5: ctor X");
+ }
+
+ class ParentState1 extends HierarchicalState {
+ @Override protected void enter() {
+ mParentState1EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ return true;
+ }
+ @Override protected void exit() {
+ mParentState1ExitCount += 1;
+ }
+ }
+
+ class ChildState1 extends HierarchicalState {
+ @Override protected void enter() {
+ mChildState1EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(0, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(0, mChildState1ExitCount);
+ assertEquals(0, mChildState2EnterCount);
+ assertEquals(0, mChildState2ExitCount);
+ assertEquals(0, mParentState2EnterCount);
+ assertEquals(0, mParentState2ExitCount);
+ assertEquals(0, mChildState3EnterCount);
+ assertEquals(0, mChildState3ExitCount);
+ assertEquals(0, mChildState4EnterCount);
+ assertEquals(0, mChildState4ExitCount);
+ assertEquals(0, mChildState5EnterCount);
+ assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState2);
+ return true;
+ }
+ @Override protected void exit() {
+ mChildState1ExitCount += 1;
+ }
+ }
+
+ class ChildState2 extends HierarchicalState {
+ @Override protected void enter() {
+ mChildState2EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(0, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(1, mChildState1ExitCount);
+ assertEquals(1, mChildState2EnterCount);
+ assertEquals(0, mChildState2ExitCount);
+ assertEquals(0, mParentState2EnterCount);
+ assertEquals(0, mParentState2ExitCount);
+ assertEquals(0, mChildState3EnterCount);
+ assertEquals(0, mChildState3ExitCount);
+ assertEquals(0, mChildState4EnterCount);
+ assertEquals(0, mChildState4ExitCount);
+ assertEquals(0, mChildState5EnterCount);
+ assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState5);
+ return true;
+ }
+ @Override protected void exit() {
+ mChildState2ExitCount += 1;
+ }
+ }
+
+ class ParentState2 extends HierarchicalState {
+ @Override protected void enter() {
+ mParentState2EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(1, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(1, mChildState1ExitCount);
+ assertEquals(1, mChildState2EnterCount);
+ assertEquals(1, mChildState2ExitCount);
+ assertEquals(2, mParentState2EnterCount);
+ assertEquals(1, mParentState2ExitCount);
+ assertEquals(1, mChildState3EnterCount);
+ assertEquals(1, mChildState3ExitCount);
+ assertEquals(2, mChildState4EnterCount);
+ assertEquals(2, mChildState4ExitCount);
+ assertEquals(1, mChildState5EnterCount);
+ assertEquals(1, mChildState5ExitCount);
+
+ transitionToHaltingState();
+ return true;
+ }
+ @Override protected void exit() {
+ mParentState2ExitCount += 1;
+ }
+ }
+
+ class ChildState3 extends HierarchicalState {
+ @Override protected void enter() {
+ mChildState3EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(1, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(1, mChildState1ExitCount);
+ assertEquals(1, mChildState2EnterCount);
+ assertEquals(1, mChildState2ExitCount);
+ assertEquals(1, mParentState2EnterCount);
+ assertEquals(0, mParentState2ExitCount);
+ assertEquals(1, mChildState3EnterCount);
+ assertEquals(0, mChildState3ExitCount);
+ assertEquals(1, mChildState4EnterCount);
+ assertEquals(1, mChildState4ExitCount);
+ assertEquals(1, mChildState5EnterCount);
+ assertEquals(1, mChildState5ExitCount);
+
+ transitionTo(mChildState4);
+ return true;
+ }
+ @Override protected void exit() {
+ mChildState3ExitCount += 1;
+ }
+ }
+
+ class ChildState4 extends HierarchicalState {
+ @Override protected void enter() {
+ mChildState4EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(1, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(1, mChildState1ExitCount);
+ assertEquals(1, mChildState2EnterCount);
+ assertEquals(1, mChildState2ExitCount);
+ assertEquals(1, mParentState2EnterCount);
+ assertEquals(0, mParentState2ExitCount);
+ assertEquals(1, mChildState3EnterCount);
+ assertEquals(1, mChildState3ExitCount);
+ assertEquals(2, mChildState4EnterCount);
+ assertEquals(1, mChildState4ExitCount);
+ assertEquals(1, mChildState5EnterCount);
+ assertEquals(1, mChildState5ExitCount);
+
+ transitionTo(mParentState2);
+ return true;
+ }
+ @Override protected void exit() {
+ mChildState4ExitCount += 1;
+ }
+ }
+
+ class ChildState5 extends HierarchicalState {
+ @Override protected void enter() {
+ mChildState5EnterCount += 1;
+ }
+ @Override protected boolean processMessage(Message message) {
+ assertEquals(1, mParentState1EnterCount);
+ assertEquals(1, mParentState1ExitCount);
+ assertEquals(1, mChildState1EnterCount);
+ assertEquals(1, mChildState1ExitCount);
+ assertEquals(1, mChildState2EnterCount);
+ assertEquals(1, mChildState2ExitCount);
+ assertEquals(1, mParentState2EnterCount);
+ assertEquals(0, mParentState2ExitCount);
+ assertEquals(0, mChildState3EnterCount);
+ assertEquals(0, mChildState3ExitCount);
+ assertEquals(1, mChildState4EnterCount);
+ assertEquals(0, mChildState4ExitCount);
+ assertEquals(1, mChildState5EnterCount);
+ assertEquals(0, mChildState5ExitCount);
+
+ transitionTo(mChildState3);
+ return true;
+ }
+ @Override protected void exit() {
+ mChildState5ExitCount += 1;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine5 mThisSm;
+ private ParentState1 mParentState1 = new ParentState1();
+ private ChildState1 mChildState1 = new ChildState1();
+ private ChildState2 mChildState2 = new ChildState2();
+ private ParentState2 mParentState2 = new ParentState2();
+ private ChildState3 mChildState3 = new ChildState3();
+ private ChildState4 mChildState4 = new ChildState4();
+ private ChildState5 mChildState5 = new ChildState5();
+
+ private int mParentState1EnterCount = 0;
+ private int mParentState1ExitCount = 0;
+ private int mChildState1EnterCount = 0;
+ private int mChildState1ExitCount = 0;
+ private int mChildState2EnterCount = 0;
+ private int mChildState2ExitCount = 0;
+ private int mParentState2EnterCount = 0;
+ private int mParentState2ExitCount = 0;
+ private int mChildState3EnterCount = 0;
+ private int mChildState3ExitCount = 0;
+ private int mChildState4EnterCount = 0;
+ private int mChildState4ExitCount = 0;
+ private int mChildState5EnterCount = 0;
+ private int mChildState5ExitCount = 0;
+ }
+
+ @SmallTest
+ public void testStateMachine5() throws Exception {
+ StateMachine5 sm5 = new StateMachine5("sm5");
+ sm5.start();
+ if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 E");
+
+ synchronized (sm5) {
+ // Send 6 messages
+ sm5.sendMessage(TEST_CMD_1);
+ sm5.sendMessage(TEST_CMD_2);
+ sm5.sendMessage(TEST_CMD_3);
+ sm5.sendMessage(TEST_CMD_4);
+ sm5.sendMessage(TEST_CMD_5);
+ sm5.sendMessage(TEST_CMD_6);
+
+ try {
+ // wait for the messages to be handled
+ sm5.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine5: exception while waiting " + e.getMessage());
+ }
+ }
+
+
+ assertTrue(sm5.getProcessedMessagesSize() == 6);
+
+ assertEquals(1, sm5.mParentState1EnterCount);
+ assertEquals(1, sm5.mParentState1ExitCount);
+ assertEquals(1, sm5.mChildState1EnterCount);
+ assertEquals(1, sm5.mChildState1ExitCount);
+ assertEquals(1, sm5.mChildState2EnterCount);
+ assertEquals(1, sm5.mChildState2ExitCount);
+ assertEquals(2, sm5.mParentState2EnterCount);
+ assertEquals(2, sm5.mParentState2ExitCount);
+ assertEquals(1, sm5.mChildState3EnterCount);
+ assertEquals(1, sm5.mChildState3ExitCount);
+ assertEquals(2, sm5.mChildState4EnterCount);
+ assertEquals(2, sm5.mChildState4ExitCount);
+ assertEquals(1, sm5.mChildState5EnterCount);
+ assertEquals(1, sm5.mChildState5ExitCount);
+
+ ProcessedMessages.Info pmi;
+ pmi = sm5.getProcessedMessage(0);
+ assertEquals(TEST_CMD_1, pmi.getWhat());
+ assertEquals(sm5.mChildState1, pmi.getState());
+ assertEquals(sm5.mChildState1, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessage(1);
+ assertEquals(TEST_CMD_2, pmi.getWhat());
+ assertEquals(sm5.mChildState2, pmi.getState());
+ assertEquals(sm5.mChildState2, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessage(2);
+ assertEquals(TEST_CMD_3, pmi.getWhat());
+ assertEquals(sm5.mChildState5, pmi.getState());
+ assertEquals(sm5.mChildState5, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessage(3);
+ assertEquals(TEST_CMD_4, pmi.getWhat());
+ assertEquals(sm5.mChildState3, pmi.getState());
+ assertEquals(sm5.mChildState3, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessage(4);
+ assertEquals(TEST_CMD_5, pmi.getWhat());
+ assertEquals(sm5.mChildState4, pmi.getState());
+ assertEquals(sm5.mChildState4, pmi.getOriginalState());
+
+ pmi = sm5.getProcessedMessage(5);
+ assertEquals(TEST_CMD_6, pmi.getWhat());
+ assertEquals(sm5.mParentState2, pmi.getState());
+ assertEquals(sm5.mParentState2, pmi.getOriginalState());
+
+ if (sm5.isDbg()) Log.d(TAG, "testStateMachine5 X");
+ }
+
+ /**
+ * Test that the initial state enter is invoked immediately
+ * after construction and before any other messages arrive and that
+ * sendMessageDelayed works.
+ */
+ class StateMachine6 extends HierarchicalStateMachine {
+ StateMachine6(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) Log.d(TAG, "StateMachine6: ctor X");
+ }
+
+ class S1 extends HierarchicalState {
+
+ @Override protected void enter() {
+ sendMessage(TEST_CMD_1);
+ }
+
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_1) {
+ mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
+ } else if (message.what == TEST_CMD_2) {
+ mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+ transitionToHaltingState();
+ }
+ return true;
+ }
+
+ @Override protected void exit() {
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine6 mThisSm;
+ private S1 mS1 = new S1();
+
+ private long mArrivalTimeMsg1;
+ private long mArrivalTimeMsg2;
+ }
+
+ @SmallTest
+ public void testStateMachine6() throws Exception {
+ long sentTimeMsg2;
+ final int DELAY_TIME = 250;
+ final int DELAY_FUDGE = 20;
+
+ StateMachine6 sm6 = new StateMachine6("sm6");
+ sm6.start();
+ if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 E");
+
+ synchronized (sm6) {
+ // Send a message
+ sentTimeMsg2 = SystemClock.elapsedRealtime();
+ sm6.sendMessageDelayed(TEST_CMD_2, DELAY_TIME);
+
+ try {
+ // wait for the messages to be handled
+ sm6.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine6: exception while waiting " + e.getMessage());
+ }
+ }
+
+ /**
+ * TEST_CMD_1 was sent in enter and must always have been processed
+ * immediately after construction and hence the arrival time difference
+ * should always >= to the DELAY_TIME
+ */
+ long arrivalTimeDiff = sm6.mArrivalTimeMsg2 - sm6.mArrivalTimeMsg1;
+ long expectedDelay = DELAY_TIME - DELAY_FUDGE;
+ if (sm6.isDbg()) Log.d(TAG, "testStateMachine6: expect " + arrivalTimeDiff
+ + " >= " + expectedDelay);
+ assertTrue(arrivalTimeDiff >= expectedDelay);
+
+ if (sm6.isDbg()) Log.d(TAG, "testStateMachine6 X");
+ }
+
+ /**
+ * Test that enter is invoked immediately after exit. This validates
+ * that enter can be used to send a watch dog message for its state.
+ */
+ class StateMachine7 extends HierarchicalStateMachine {
+ private final int SM7_DELAY_TIME = 250;
+
+ StateMachine7(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+ addState(mS2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ if (DBG) Log.d(TAG, "StateMachine7: ctor X");
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ transitionTo(mS2);
+ return true;
+ }
+ @Override protected void exit() {
+ sendMessage(TEST_CMD_2);
+ }
+ }
+
+ class S2 extends HierarchicalState {
+
+ @Override protected void enter() {
+ // Send a delayed message as a watch dog
+ sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
+ }
+
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ mMsgCount += 1;
+ mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
+ } else if (message.what == TEST_CMD_3) {
+ mMsgCount += 1;
+ mArrivalTimeMsg3 = SystemClock.elapsedRealtime();
+ }
+
+ if (mMsgCount == 2) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+
+ @Override protected void exit() {
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachine7 mThisSm;
+ private S1 mS1 = new S1();
+ private S2 mS2 = new S2();
+
+ private int mMsgCount = 0;
+ private long mArrivalTimeMsg2;
+ private long mArrivalTimeMsg3;
+ }
+
+ @SmallTest
+ public void testStateMachine7() throws Exception {
+ long sentTimeMsg2;
+ final int SM7_DELAY_FUDGE = 20;
+
+ StateMachine7 sm7 = new StateMachine7("sm7");
+ sm7.start();
+ if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 E");
+
+ synchronized (sm7) {
+ // Send a message
+ sentTimeMsg2 = SystemClock.elapsedRealtime();
+ sm7.sendMessage(TEST_CMD_1);
+
+ try {
+ // wait for the messages to be handled
+ sm7.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachine7: exception while waiting " + e.getMessage());
+ }
+ }
+
+ /**
+ * TEST_CMD_3 was sent in S2.enter with a delay and must always have been
+ * processed immediately after S1.exit. Since S1.exit sent TEST_CMD_2
+ * without a delay the arrival time difference should always >= to SM7_DELAY_TIME.
+ */
+ long arrivalTimeDiff = sm7.mArrivalTimeMsg3 - sm7.mArrivalTimeMsg2;
+ long expectedDelay = sm7.SM7_DELAY_TIME - SM7_DELAY_FUDGE;
+ if (sm7.isDbg()) Log.d(TAG, "testStateMachine7: expect " + arrivalTimeDiff
+ + " >= " + expectedDelay);
+ assertTrue(arrivalTimeDiff >= expectedDelay);
+
+ if (sm7.isDbg()) Log.d(TAG, "testStateMachine7 X");
+ }
+
+ /**
+ * Test unhandledMessage.
+ */
+ class StateMachineUnhandledMessage extends HierarchicalStateMachine {
+ StateMachineUnhandledMessage(String name) {
+ super(name);
+ mThisSm = this;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ @Override protected void unhandledMessage(Message message) {
+ mUnhandledMessageCount += 1;
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_2) {
+ transitionToHaltingState();
+ }
+ return false;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ synchronized (mThisSm) {
+ mThisSm.notifyAll();
+ }
+ }
+
+ private StateMachineUnhandledMessage mThisSm;
+ private int mUnhandledMessageCount;
+ private S1 mS1 = new S1();
+ }
+
+ @SmallTest
+ public void testStateMachineUnhandledMessage() throws Exception {
+
+ StateMachineUnhandledMessage sm = new StateMachineUnhandledMessage("sm");
+ sm.start();
+ if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage E");
+
+ synchronized (sm) {
+ // Send 2 messages
+ for (int i = 1; i <= 2; i++) {
+ sm.sendMessage(i);
+ }
+
+ try {
+ // wait for the messages to be handled
+ sm.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachineUnhandledMessage: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ assertTrue(sm.getProcessedMessagesCount() == 2);
+ assertEquals(2, sm.mUnhandledMessageCount);
+
+ if (sm.isDbg()) Log.d(TAG, "testStateMachineUnhandledMessage X");
+ }
+
+ /**
+ * Test state machines sharing the same thread/looper. Multiple instances
+ * of the same state machine will be created. They will all share the
+ * same thread and thus each can update <code>sharedCounter</code> which
+ * will be used to notify testStateMachineSharedThread that the test is
+ * complete.
+ */
+ class StateMachineSharedThread extends HierarchicalStateMachine {
+ StateMachineSharedThread(String name, Looper looper, int maxCount) {
+ super(name, looper);
+ mMaxCount = maxCount;
+ setDbg(DBG);
+
+ // Setup state machine with 1 state
+ addState(mS1);
+
+ // Set the initial state
+ setInitialState(mS1);
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected boolean processMessage(Message message) {
+ if (message.what == TEST_CMD_4) {
+ transitionToHaltingState();
+ }
+ return true;
+ }
+ }
+
+ @Override
+ protected void halting() {
+ // Update the shared counter, which is OK since all state
+ // machines are using the same thread.
+ sharedCounter += 1;
+ if (sharedCounter == mMaxCount) {
+ synchronized (waitObject) {
+ waitObject.notifyAll();
+ }
+ }
+ }
+
+ private int mMaxCount;
+ private S1 mS1 = new S1();
+ }
+ private static int sharedCounter = 0;
+ private static Object waitObject = new Object();
+
+ @SmallTest
+ public void testStateMachineSharedThread() throws Exception {
+ if (DBG) Log.d(TAG, "testStateMachineSharedThread E");
+
+ // Create and start the handler thread
+ HandlerThread smThread = new HandlerThread("testStateMachineSharedThread");
+ smThread.start();
+
+ // Create the state machines
+ StateMachineSharedThread sms[] = new StateMachineSharedThread[10];
+ for (int i = 0; i < sms.length; i++) {
+ sms[i] = new StateMachineSharedThread("sm", smThread.getLooper(), sms.length);
+ sms[i].start();
+ }
+
+ synchronized (waitObject) {
+ // Send messages to each of the state machines
+ for (StateMachineSharedThread sm : sms) {
+ for (int i = 1; i <= 4; i++) {
+ sm.sendMessage(i);
+ }
+ }
+
+ // Wait for the last state machine to notify its done
+ try {
+ waitObject.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testStateMachineSharedThread: exception while waiting "
+ + e.getMessage());
+ }
+ }
+
+ for (StateMachineSharedThread sm : sms) {
+ assertTrue(sm.getProcessedMessagesCount() == 4);
+ for (int i = 0; i < sm.getProcessedMessagesCount(); i++) {
+ ProcessedMessages.Info pmi = sm.getProcessedMessage(i);
+ assertEquals(i+1, pmi.getWhat());
+ assertEquals(sm.mS1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+ }
+ }
+
+ if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
+ }
+
+ @SmallTest
+ public void testHsm1() throws Exception {
+ if (DBG) Log.d(TAG, "testHsm1 E");
+
+ Hsm1 sm = Hsm1.makeHsm1();
+
+ // Send messages
+ sm.sendMessage(Hsm1.CMD_1);
+ sm.sendMessage(Hsm1.CMD_2);
+
+ synchronized (sm) {
+ // Wait for the last state machine to notify its done
+ try {
+ sm.wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "testHsm1: exception while waiting " + e.getMessage());
+ }
+ }
+
+ assertEquals(7, sm.getProcessedMessagesCount());
+ ProcessedMessages.Info pmi = sm.getProcessedMessage(0);
+ assertEquals(Hsm1.CMD_1, pmi.getWhat());
+ assertEquals(sm.mS1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(1);
+ assertEquals(Hsm1.CMD_2, pmi.getWhat());
+ assertEquals(sm.mP1, pmi.getState());
+ assertEquals(sm.mS1, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(2);
+ assertEquals(Hsm1.CMD_2, pmi.getWhat());
+ assertEquals(sm.mS2, pmi.getState());
+ assertEquals(sm.mS2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(3);
+ assertEquals(Hsm1.CMD_3, pmi.getWhat());
+ assertEquals(sm.mS2, pmi.getState());
+ assertEquals(sm.mS2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(4);
+ assertEquals(Hsm1.CMD_3, pmi.getWhat());
+ assertEquals(sm.mP2, pmi.getState());
+ assertEquals(sm.mP2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(5);
+ assertEquals(Hsm1.CMD_4, pmi.getWhat());
+ assertEquals(sm.mP2, pmi.getState());
+ assertEquals(sm.mP2, pmi.getOriginalState());
+
+ pmi = sm.getProcessedMessage(6);
+ assertEquals(Hsm1.CMD_5, pmi.getWhat());
+ assertEquals(sm.mP2, pmi.getState());
+ assertEquals(sm.mP2, pmi.getOriginalState());
+
+ if (DBG) Log.d(TAG, "testStateMachineSharedThread X");
+ }
+}
+
+class Hsm1 extends HierarchicalStateMachine {
+ private static final String TAG = "hsm1";
+
+ public static final int CMD_1 = 1;
+ public static final int CMD_2 = 2;
+ public static final int CMD_3 = 3;
+ public static final int CMD_4 = 4;
+ public static final int CMD_5 = 5;
+
+ public static Hsm1 makeHsm1() {
+ Log.d(TAG, "makeHsm1 E");
+ Hsm1 sm = new Hsm1("hsm1");
+ sm.start();
+ Log.d(TAG, "makeHsm1 X");
+ return sm;
+ }
+
+ Hsm1(String name) {
+ super(name);
+ Log.d(TAG, "ctor E");
+
+ // Add states, use indentation to show hierarchy
+ addState(mP1);
+ addState(mS1, mP1);
+ addState(mS2, mP1);
+ addState(mP2);
+
+ // Set the initial state
+ setInitialState(mS1);
+ Log.d(TAG, "ctor X");
+ }
+
+ class P1 extends HierarchicalState {
+ @Override protected void enter() {
+ Log.d(TAG, "P1.enter");
+ }
+ @Override protected boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "P1.processMessage what=" + message.what);
+ switch(message.what) {
+ case CMD_2:
+ // CMD_2 will arrive in mS2 before CMD_3
+ sendMessage(CMD_3);
+ deferMessage(message);
+ transitionTo(mS2);
+ retVal = true;
+ break;
+ default:
+ // Any message we don't understand in this state invokes unhandledMessage
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override protected void exit() {
+ Log.d(TAG, "P1.exit");
+ }
+ }
+
+ class S1 extends HierarchicalState {
+ @Override protected void enter() {
+ Log.d(TAG, "S1.enter");
+ }
+ @Override protected boolean processMessage(Message message) {
+ Log.d(TAG, "S1.processMessage what=" + message.what);
+ if (message.what == CMD_1) {
+ // Transition to ourself to show that enter/exit is called
+ transitionTo(mS1);
+ return true;
+ } else {
+ // Let parent process all other messages
+ return false;
+ }
+ }
+ @Override protected void exit() {
+ Log.d(TAG, "S1.exit");
+ }
+ }
+
+ class S2 extends HierarchicalState {
+ @Override protected void enter() {
+ Log.d(TAG, "S2.enter");
+ }
+ @Override protected boolean processMessage(Message message) {
+ boolean retVal;
+ Log.d(TAG, "S2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_2):
+ sendMessage(CMD_4);
+ retVal = true;
+ break;
+ case(CMD_3):
+ deferMessage(message);
+ transitionTo(mP2);
+ retVal = true;
+ break;
+ default:
+ retVal = false;
+ break;
+ }
+ return retVal;
+ }
+ @Override protected void exit() {
+ Log.d(TAG, "S2.exit");
+ }
+ }
+
+ class P2 extends HierarchicalState {
+ @Override protected void enter() {
+ Log.d(TAG, "P2.enter");
+ sendMessage(CMD_5);
+ }
+ @Override protected boolean processMessage(Message message) {
+ Log.d(TAG, "P2.processMessage what=" + message.what);
+ switch(message.what) {
+ case(CMD_3):
+ break;
+ case(CMD_4):
+ break;
+ case(CMD_5):
+ transitionToHaltingState();
+ break;
+ }
+ return true;
+ }
+ @Override protected void exit() {
+ Log.d(TAG, "P2.exit");
+ }
+ }
+
+ @Override
+ protected void halting() {
+ Log.d(TAG, "halting");
+ synchronized (this) {
+ this.notifyAll();
+ }
+ }
+
+ P1 mP1 = new P1();
+ S1 mS1 = new S1();
+ S2 mS2 = new S2();
+ P2 mP2 = new P2();
+}
diff --git a/core/tests/coretests/src/android/os/IAidlTest.aidl b/core/tests/coretests/src/android/os/IAidlTest.aidl
new file mode 100644
index 0000000..a09022e
--- /dev/null
+++ b/core/tests/coretests/src/android/os/IAidlTest.aidl
@@ -0,0 +1,47 @@
+/* //device/apps/AndroidTests/src/com.android.unit_tests/IAidlTest.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+import android.os.AidlTest;
+
+interface IAidlTest {
+ int intMethod(int a);
+
+ AidlTest.TestParcelable parcelableIn(in AidlTest.TestParcelable p);
+ AidlTest.TestParcelable parcelableOut(out AidlTest.TestParcelable p);
+ AidlTest.TestParcelable parcelableInOut(inout AidlTest.TestParcelable p);
+
+ AidlTest.TestParcelable listParcelableLonger(
+ inout List<AidlTest.TestParcelable> list, int index);
+ int listParcelableShorter(
+ inout List<AidlTest.TestParcelable> list, int index);
+
+ boolean[] booleanArray(in boolean[] a0, out boolean[] a1, inout boolean[] a2);
+ char[] charArray(in char[] a0, out char[] a1, inout char[] a2);
+ int[] intArray(in int[] a0, out int[] a1, inout int[] a2);
+ long[] longArray(in long[] a0, out long[] a1, inout long[] a2);
+ float[] floatArray(in float[] a0, out float[] a1, inout float[] a2);
+ double[] doubleArray(in double[] a0, out double[] a1, inout double[] a2);
+ String[] stringArray(in String[] a0, out String[] a1, inout String[] a2);
+ AidlTest.TestParcelable[] parcelableArray(in AidlTest.TestParcelable[] a0,
+ out AidlTest.TestParcelable[] a1,
+ inout AidlTest.TestParcelable[] a2);
+
+ void voidSecurityException();
+ int intSecurityException();
+}
diff --git a/core/java/android/os/IParentalControlCallback.aidl b/core/tests/coretests/src/android/os/IBinderThreadPriorityService.aidl
index 2f1a563..b30f04c 100644
--- a/core/java/android/os/IParentalControlCallback.aidl
+++ b/core/tests/coretests/src/android/os/IBinderThreadPriorityService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008 The Android Open Source Project
+ * 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.
@@ -16,12 +16,9 @@
package android.os;
-import com.google.android.net.ParentalControlState;
-
-/**
- * This callback interface is used to deliver the parental control state to the calling application.
- * {@hide}
- */
-oneway interface IParentalControlCallback {
- void onResult(in ParentalControlState state);
+interface IBinderThreadPriorityService {
+ int getThreadPriority();
+ String getThreadSchedulerGroup();
+ void callBack(IBinderThreadPriorityService recurse);
+ void setPriorityAndCallBack(int priority, IBinderThreadPriorityService recurse);
}
diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java
new file mode 100644
index 0000000..6c0a862
--- /dev/null
+++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue.IdleHandler;
+import android.test.suitebuilder.annotation.MediumTest;
+import junit.framework.TestCase;
+
+public class IdleHandlerTest extends TestCase {
+
+ private static class BaseTestHandler extends TestHandlerThread {
+ Handler mHandler;
+
+ public BaseTestHandler() {
+ }
+
+ public void go() {
+ mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ BaseTestHandler.this.handleMessage(msg);
+ }
+ };
+ }
+
+ public void addIdleHandler() {
+ Looper.myQueue().addIdleHandler(new IdleHandler() {
+ public boolean queueIdle() {
+ return BaseTestHandler.this.queueIdle();
+ }
+ });
+ }
+
+ public void handleMessage(Message msg) {
+ }
+
+ public boolean queueIdle() {
+ return false;
+ }
+ }
+
+ @MediumTest
+ public void testOneShotFirst() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ int mCount;
+
+ public void go() {
+ super.go();
+ mCount = 0;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 100);
+ addIdleHandler();
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100);
+ } else if (msg.what == 1) {
+ if (mCount == 1) {
+ success();
+ } else {
+ failure(new RuntimeException(
+ "Idle handler called " + mCount + " times"));
+ }
+ }
+ }
+
+ public boolean queueIdle() {
+ mCount++;
+ return false;
+ }
+ };
+
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void testOneShotLater() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ int mCount;
+
+ public void go() {
+ super.go();
+ mCount = 0;
+ mHandler.sendMessage(mHandler.obtainMessage(0));
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == 0) {
+ addIdleHandler();
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100);
+ } else if (msg.what == 1) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(2), 100);
+ } else if (msg.what == 2) {
+ if (mCount == 1) {
+ success();
+ } else {
+ failure(new RuntimeException(
+ "Idle handler called " + mCount + " times"));
+ }
+ }
+ }
+
+ public boolean queueIdle() {
+ mCount++;
+ return false;
+ }
+ };
+
+ tester.doTest(1000);
+ }
+
+
+ @MediumTest
+ public void testRepeatedFirst() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ int mCount;
+
+ public void go() {
+ super.go();
+ mCount = 0;
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 100);
+ addIdleHandler();
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100);
+ } else if (msg.what == 1) {
+ if (mCount == 2) {
+ success();
+ } else {
+ failure(new RuntimeException(
+ "Idle handler called " + mCount + " times"));
+ }
+ }
+ }
+
+ public boolean queueIdle() {
+ mCount++;
+ return true;
+ }
+ };
+
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void testRepeatedLater() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ int mCount;
+
+ public void go() {
+ super.go();
+ mCount = 0;
+ mHandler.sendMessage(mHandler.obtainMessage(0));
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.what == 0) {
+ addIdleHandler();
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(1), 100);
+ } else if (msg.what == 1) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(2), 100);
+ } else if (msg.what == 2) {
+ if (mCount == 2) {
+ success();
+ } else {
+ failure(new RuntimeException(
+ "Idle handler called " + mCount + " times"));
+ }
+ }
+ }
+
+ public boolean queueIdle() {
+ mCount++;
+ return true;
+ }
+ };
+
+ tester.doTest(1000);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/os/MemoryFileTest.java b/core/tests/coretests/src/android/os/MemoryFileTest.java
new file mode 100644
index 0000000..009a0e2
--- /dev/null
+++ b/core/tests/coretests/src/android/os/MemoryFileTest.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MemoryFileTest extends AndroidTestCase {
+
+ private void compareBuffers(byte[] buffer1, byte[] buffer2, int length) throws Exception {
+ for (int i = 0; i < length; i++) {
+ if (buffer1[i] != buffer2[i]) {
+ throw new Exception("readBytes did not read back what writeBytes wrote");
+ }
+ }
+ }
+
+ /**
+ * Keep allocating new files till the system purges them.
+ */
+ // Flaky test - temporarily suppress from large suite for now
+ // @LargeTest
+ public void testPurge() throws Exception {
+ List<MemoryFile> files = new ArrayList<MemoryFile>();
+ try {
+ while (true) {
+ // This will fail if the process runs out of file descriptors before
+ // the kernel starts purging ashmem areas.
+ MemoryFile newFile = new MemoryFile("MemoryFileTest", 10000000);
+ newFile.allowPurging(true);
+ newFile.writeBytes(testString, 0, 0, testString.length);
+ files.add(newFile);
+ for (MemoryFile file : files) {
+ try {
+ file.readBytes(testString, 0, 0, testString.length);
+ } catch (IOException e) {
+ // Expected
+ return;
+ }
+ }
+ }
+ } finally {
+ for (MemoryFile fileToClose : files) {
+ fileToClose.close();
+ }
+ }
+ }
+
+ @SmallTest
+ public void testRun() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+
+ byte[] buffer = new byte[testString.length];
+
+ // check low level accessors
+ file.writeBytes(testString, 0, 2000, testString.length);
+ file.readBytes(buffer, 2000, 0, testString.length);
+ compareBuffers(testString, buffer, testString.length);
+
+ // check streams
+ buffer = new byte[testString.length];
+
+ OutputStream os = file.getOutputStream();
+ os.write(testString);
+
+ InputStream is = file.getInputStream();
+ is.mark(testString.length);
+ is.read(buffer);
+ compareBuffers(testString, buffer, testString.length);
+
+ // test mark/reset
+ buffer = new byte[testString.length];
+ is.reset();
+ is.read(buffer);
+ compareBuffers(testString, buffer, testString.length);
+
+ file.close();
+ }
+
+ // Tests for the IndexOutOfBoundsException cases in read().
+
+ private void readIndexOutOfBoundsException(int offset, int count, String msg)
+ throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", testString.length);
+ try {
+ file.writeBytes(testString, 0, 0, testString.length);
+ InputStream is = file.getInputStream();
+ byte[] buffer = new byte[testString.length + 10];
+ try {
+ is.read(buffer, offset, count);
+ fail(msg);
+ } catch (IndexOutOfBoundsException ex) {
+ // this is what should happen
+ } finally {
+ is.close();
+ }
+ } finally {
+ file.close();
+ }
+ }
+
+ @SmallTest
+ public void testReadNegativeOffset() throws Exception {
+ readIndexOutOfBoundsException(-1, 5,
+ "read() with negative offset should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadNegativeCount() throws Exception {
+ readIndexOutOfBoundsException(5, -1,
+ "read() with negative length should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadOffsetOverflow() throws Exception {
+ readIndexOutOfBoundsException(testString.length + 10, 5,
+ "read() with offset outside buffer should throw IndexOutOfBoundsException");
+ }
+
+ @SmallTest
+ public void testReadOffsetCountOverflow() throws Exception {
+ readIndexOutOfBoundsException(testString.length, 11,
+ "read() with offset + count outside buffer should throw IndexOutOfBoundsException");
+ }
+
+ // Test behavior of read() at end of file
+ @SmallTest
+ public void testReadEOF() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", testString.length);
+ try {
+ file.writeBytes(testString, 0, 0, testString.length);
+ InputStream is = file.getInputStream();
+ try {
+ byte[] buffer = new byte[testString.length + 10];
+ // read() with count larger than data should succeed, and return # of bytes read
+ assertEquals(testString.length, is.read(buffer));
+ compareBuffers(testString, buffer, testString.length);
+ // Read at EOF should return -1
+ assertEquals(-1, is.read());
+ } finally {
+ is.close();
+ }
+ } finally {
+ file.close();
+ }
+ }
+
+ // Tests that close() is idempotent
+ @SmallTest
+ public void testCloseClose() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ byte[] data = new byte[512];
+ file.writeBytes(data, 0, 0, 128);
+ file.close();
+ file.close();
+ }
+
+ // Tests that we can't read from a closed memory file
+ @SmallTest
+ public void testCloseRead() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ file.close();
+
+ try {
+ byte[] data = new byte[512];
+ assertEquals(128, file.readBytes(data, 0, 0, 128));
+ fail("readBytes() after close() did not throw IOException.");
+ } catch (IOException e) {
+ // this is what should happen
+ }
+ }
+
+ // Tests that we can't write to a closed memory file
+ @SmallTest
+ public void testCloseWrite() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ file.close();
+
+ try {
+ byte[] data = new byte[512];
+ file.writeBytes(data, 0, 0, 128);
+ fail("writeBytes() after close() did not throw IOException.");
+ } catch (IOException e) {
+ // this is what should happen
+ }
+ }
+
+ // Tests that we can't call allowPurging() after close()
+ @SmallTest
+ public void testCloseAllowPurging() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ byte[] data = new byte[512];
+ file.writeBytes(data, 0, 0, 128);
+ file.close();
+
+ try {
+ file.allowPurging(true);
+ fail("allowPurging() after close() did not throw IOException.");
+ } catch (IOException e) {
+ // this is what should happen
+ }
+ }
+
+ // Tests that we don't leak file descriptors or mmap areas
+ @LargeTest
+ public void testCloseLeak() throws Exception {
+ // open enough memory files that we should run out of
+ // file descriptors or address space if we leak either.
+ for (int i = 0; i < 1025; i++) {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 5000000);
+ file.writeBytes(testString, 0, 0, testString.length);
+ file.close();
+ }
+ }
+
+ @SmallTest
+ public void testIsMemoryFile() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ FileDescriptor fd = file.getFileDescriptor();
+ assertNotNull(fd);
+ assertTrue(fd.valid());
+ assertTrue(MemoryFile.isMemoryFile(fd));
+ file.close();
+
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.in));
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.out));
+ assertFalse(MemoryFile.isMemoryFile(FileDescriptor.err));
+
+ File tempFile = File.createTempFile("MemoryFileTest",".tmp", getContext().getFilesDir());
+ assertNotNull(file);
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(tempFile);
+ FileDescriptor fileFd = out.getFD();
+ assertNotNull(fileFd);
+ assertFalse(MemoryFile.isMemoryFile(fileFd));
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ tempFile.delete();
+ }
+ }
+
+ @SmallTest
+ public void testFileDescriptor() throws Exception {
+ MemoryFile file = new MemoryFile("MemoryFileTest", 1000000);
+ MemoryFile ref = new MemoryFile(file.getFileDescriptor(), file.length(), "r");
+ byte[] buffer;
+
+ // write to original, read from reference
+ file.writeBytes(testString, 0, 2000, testString.length);
+ buffer = new byte[testString.length];
+ ref.readBytes(buffer, 2000, 0, testString.length);
+ compareBuffers(testString, buffer, testString.length);
+
+ file.close();
+ ref.close(); // Doesn't actually do anything, since the file descriptor is not dup(2):ed
+ }
+
+ private static final byte[] testString = new byte[] {
+ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 9, 5, 0, 2, 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 5, 8, 2, 0, 9, 7, 4, 9, 4, 4, 5, 9, 2, 3, 0, 7, 8, 1, 6, 4,
+ 0, 6, 2, 8, 6, 2, 0, 8, 9, 9, 8, 6, 2, 8, 0, 3, 4, 8, 2, 5, 3, 4, 2, 1, 1, 7, 0, 6, 7, 9, 8, 2, 1, 4, 8, 0, 8, 6, 5, 1, 3, 2, 8, 2, 3, 0, 6, 6, 4, 7, 0, 9, 3, 8, 4, 4, 6, 0, 9, 5, 5, 0, 5, 8, 2, 2, 3, 1, 7, 2,
+ 5, 3, 5, 9, 4, 0, 8, 1, 2, 8, 4, 8, 1, 1, 1, 7, 4, 5, 0, 2, 8, 4, 1, 0, 2, 7, 0, 1, 9, 3, 8, 5, 2, 1, 1, 0, 5, 5, 5, 9, 6, 4, 4, 6, 2, 2, 9, 4, 8, 9, 5, 4, 9, 3, 0, 3, 8, 1, 9, 6, 4, 4, 2, 8, 8, 1, 0, 9, 7, 5,
+ 6, 6, 5, 9, 3, 3, 4, 4, 6, 1, 2, 8, 4, 7, 5, 6, 4, 8, 2, 3, 3, 7, 8, 6, 7, 8, 3, 1, 6, 5, 2, 7, 1, 2, 0, 1, 9, 0, 9, 1, 4, 5, 6, 4, 8, 5, 6, 6, 9, 2, 3, 4, 6, 0, 3, 4, 8, 6, 1, 0, 4, 5, 4, 3, 2, 6, 6, 4, 8, 2,
+ 1, 3, 3, 9, 3, 6, 0, 7, 2, 6, 0, 2, 4, 9, 1, 4, 1, 2, 7, 3, 7, 2, 4, 5, 8, 7, 0, 0, 6, 6, 0, 6, 3, 1, 5, 5, 8, 8, 1, 7, 4, 8, 8, 1, 5, 2, 0, 9, 2, 0, 9, 6, 2, 8, 2, 9, 2, 5, 4, 0, 9, 1, 7, 1, 5, 3, 6, 4, 3, 6,
+ 7, 8, 9, 2, 5, 9, 0, 3, 6, 0, 0, 1, 1, 3, 3, 0, 5, 3, 0, 5, 4, 8, 8, 2, 0, 4, 6, 6, 5, 2, 1, 3, 8, 4, 1, 4, 6, 9, 5, 1, 9, 4, 1, 5, 1, 1, 6, 0, 9, 4, 3, 3, 0, 5, 7, 2, 7, 0, 3, 6, 5, 7, 5, 9, 5, 9, 1, 9, 5, 3,
+ 0, 9, 2, 1, 8, 6, 1, 1, 7, 3, 8, 1, 9, 3, 2, 6, 1, 1, 7, 9, 3, 1, 0, 5, 1, 1, 8, 5, 4, 8, 0, 7, 4, 4, 6, 2, 3, 7, 9, 9, 6, 2, 7, 4, 9, 5, 6, 7, 3, 5, 1, 8, 8, 5, 7, 5, 2, 7, 2, 4, 8, 9, 1, 2, 2, 7, 9, 3, 8, 1,
+ 8, 3, 0, 1, 1, 9, 4, 9, 1, 2, 9, 8, 3, 3, 6, 7, 3, 3, 6, 2, 4, 4, 0, 6, 5, 6, 6, 4, 3, 0, 8, 6, 0, 2, 1, 3, 9, 4, 9, 4, 6, 3, 9, 5, 2, 2, 4, 7, 3, 7, 1, 9, 0, 7, 0, 2, 1, 7, 9, 8, 6, 0, 9, 4, 3, 7, 0, 2, 7, 7,
+ 0, 5, 3, 9, 2, 1, 7, 1, 7, 6, 2, 9, 3, 1, 7, 6, 7, 5, 2, 3, 8, 4, 6, 7, 4, 8, 1, 8, 4, 6, 7, 6, 6, 9, 4, 0, 5, 1, 3, 2, 0, 0, 0, 5, 6, 8, 1, 2, 7, 1, 4, 5, 2, 6, 3, 5, 6, 0, 8, 2, 7, 7, 8, 5, 7, 7, 1, 3, 4, 2,
+ 7, 5, 7, 7, 8, 9, 6, 0, 9, 1, 7, 3, 6, 3, 7, 1, 7, 8, 7, 2, 1, 4, 6, 8, 4, 4, 0, 9, 0, 1, 2, 2, 4, 9, 5, 3, 4, 3, 0, 1, 4, 6, 5, 4, 9, 5, 8, 5, 3, 7, 1, 0, 5, 0, 7, 9, 2, 2, 7, 9, 6, 8, 9, 2, 5, 8, 9, 2, 3, 5,
+ 4, 2, 0, 1, 9, 9, 5, 6, 1, 1, 2, 1, 2, 9, 0, 2, 1, 9, 6, 0, 8, 6, 4, 0, 3, 4, 4, 1, 8, 1, 5, 9, 8, 1, 3, 6, 2, 9, 7, 7, 4, 7, 7, 1, 3, 0, 9, 9, 6, 0, 5, 1, 8, 7, 0, 7, 2, 1, 1, 3, 4, 9, 9, 9, 9, 9, 9, 8, 3, 7,
+ 2, 9, 7, 8, 0, 4, 9, 9, 5, 1, 0, 5, 9, 7, 3, 1, 7, 3, 2, 8, 1, 6, 0, 9, 6, 3, 1, 8, 5, 9, 5, 0, 2, 4, 4, 5, 9, 4, 5, 5, 3, 4, 6, 9, 0, 8, 3, 0, 2, 6, 4, 2, 5, 2, 2, 3, 0, 8, 2, 5, 3, 3, 4, 4, 6, 8, 5, 0, 3, 5,
+ 2, 6, 1, 9, 3, 1, 1, 8, 8, 1, 7, 1, 0, 1, 0, 0, 0, 3, 1, 3, 7, 8, 3, 8, 7, 5, 2, 8, 8, 6, 5, 8, 7, 5, 3, 3, 2, 0, 8, 3, 8, 1, 4, 2, 0, 6, 1, 7, 1, 7, 7, 6, 6, 9, 1, 4, 7, 3, 0, 3, 5, 9, 8, 2, 5, 3, 4, 9, 0, 4,
+ 2, 8, 7, 5, 5, 4, 6, 8, 7, 3, 1, 1, 5, 9, 5, 6, 2, 8, 6, 3, 8, 8, 2, 3, 5, 3, 7, 8, 7, 5, 9, 3, 7, 5, 1, 9, 5, 7, 7, 8, 1, 8, 5, 7, 7, 8, 0, 5, 3, 2, 1, 7, 1, 2, 2, 6, 8, 0, 6, 6, 1, 3, 0, 0, 1, 9, 2, 7, 8, 7,
+ 6, 6, 1, 1, 1, 9, 5, 9, 0, 9, 2, 1, 6, 4, 2, 0, 1, 9, 8, 9, 3, 8, 0, 9, 5, 2, 5, 7, 2, 0, 1, 0, 6, 5, 4, 8, 5, 8, 6, 3, 2, 7, 8, 8, 6, 5, 9, 3, 6, 1, 5, 3, 3, 8, 1, 8, 2, 7, 9, 6, 8, 2, 3, 0, 3, 0, 1, 9, 5, 2,
+ 0, 3, 5, 3, 0, 1, 8, 5, 2, 9, 6, 8, 9, 9, 5, 7, 7, 3, 6, 2, 2, 5, 9, 9, 4, 1, 3, 8, 9, 1, 2, 4, 9, 7, 2, 1, 7, 7, 5, 2, 8, 3, 4, 7, 9, 1, 3, 1, 5, 1, 5, 5, 7, 4, 8, 5, 7, 2, 4, 2, 4, 5, 4, 1, 5, 0, 6, 9, 5, 9,
+ 5, 0, 8, 2, 9, 5, 3, 3, 1, 1, 6, 8, 6, 1, 7, 2, 7, 8, 5, 5, 8, 8, 9, 0, 7, 5, 0, 9, 8, 3, 8, 1, 7, 5, 4, 6, 3, 7, 4, 6, 4, 9, 3, 9, 3, 1, 9, 2, 5, 5, 0, 6, 0, 4, 0, 0, 9, 2, 7, 7, 0, 1, 6, 7, 1, 1, 3, 9, 0, 0,
+ 9, 8, 4, 8, 8, 2, 4, 0, 1, 2, 8, 5, 8, 3, 6, 1, 6, 0, 3, 5, 6, 3, 7, 0, 7, 6, 6, 0, 1, 0, 4, 7, 1, 0, 1, 8, 1, 9, 4, 2, 9, 5, 5, 5, 9, 6, 1, 9, 8, 9, 4, 6, 7, 6, 7, 8, 3, 7, 4, 4, 9, 4, 4, 8, 2, 5, 5, 3, 7, 9,
+ 7, 7, 4, 7, 2, 6, 8, 4, 7, 1, 0, 4, 0, 4, 7, 5, 3, 4, 6, 4, 6, 2, 0, 8, 0, 4, 6, 6, 8, 4, 2, 5, 9, 0, 6, 9, 4, 9, 1, 2, 9, 3, 3, 1, 3, 6, 7, 7, 0, 2, 8, 9, 8, 9, 1, 5, 2, 1, 0, 4, 7, 5, 2, 1, 6, 2, 0, 5, 6, 9,
+ 6, 6, 0, 2, 4, 0, 5, 8, 0, 3, 8, 1, 5, 0, 1, 9, 3, 5, 1, 1, 2, 5, 3, 3, 8, 2, 4, 3, 0, 0, 3, 5, 5, 8, 7, 6, 4, 0, 2, 4, 7, 4, 9, 6, 4, 7, 3, 2, 6, 3, 9, 1, 4, 1, 9, 9, 2, 7, 2, 6, 0, 4, 2, 6, 9, 9, 2, 2, 7, 9,
+ 6, 7, 8, 2, 3, 5, 4, 7, 8, 1, 6, 3, 6, 0, 0, 9, 3, 4, 1, 7, 2, 1, 6, 4, 1, 2, 1, 9, 9, 2, 4, 5, 8, 6, 3, 1, 5, 0, 3, 0, 2, 8, 6, 1, 8, 2, 9, 7, 4, 5, 5, 5, 7, 0, 6, 7, 4, 9, 8, 3, 8, 5, 0, 5, 4, 9, 4, 5, 8, 8,
+ 5, 8, 6, 9, 2, 6, 9, 9, 5, 6, 9, 0, 9, 2, 7, 2, 1, 0, 7, 9, 7, 5, 0, 9, 3, 0, 2, 9, 5, 5, 3, 2, 1, 1, 6, 5, 3, 4, 4, 9, 8, 7, 2, 0, 2, 7, 5, 5, 9, 6, 0, 2, 3, 6, 4, 8, 0, 6, 6, 5, 4, 9, 9, 1, 1, 9, 8, 8, 1, 8,
+ 3, 4, 7, 9, 7, 7, 5, 3, 5, 6, 6, 3, 6, 9, 8, 0, 7, 4, 2, 6, 5, 4, 2, 5, 2, 7, 8, 6, 2, 5, 5, 1, 8, 1, 8, 4, 1, 7, 5, 7, 4, 6, 7, 2, 8, 9, 0, 9, 7, 7, 7, 7, 2, 7, 9, 3, 8, 0, 0, 0, 8, 1, 6, 4, 7, 0, 6, 0, 0, 1,
+ 6, 1, 4, 5, 2, 4, 9, 1, 9, 2, 1, 7, 3, 2, 1, 7, 2, 1, 4, 7, 7, 2, 3, 5, 0, 1, 4, 1, 4, 4, 1, 9, 7, 3, 5, 6, 8, 5, 4, 8, 1, 6, 1, 3, 6, 1, 1, 5, 7, 3, 5, 2, 5, 5, 2, 1, 3, 3, 4, 7, 5, 7, 4, 1, 8, 4, 9, 4, 6, 8,
+ 4, 3, 8, 5, 2, 3, 3, 2, 3, 9, 0, 7, 3, 9, 4, 1, 4, 3, 3, 3, 4, 5, 4, 7, 7, 6, 2, 4, 1, 6, 8, 6, 2, 5, 1, 8, 9, 8, 3, 5, 6, 9, 4, 8, 5, 5, 6, 2, 0, 9, 9, 2, 1, 9, 2, 2, 2, 1, 8, 4, 2, 7, 2, 5, 5, 0, 2, 5, 4, 2,
+ 5, 6, 8, 8, 7, 6, 7, 1, 7, 9, 0, 4, 9, 4, 6, 0, 1, 6, 5, 3, 4, 6, 6, 8, 0, 4, 9, 8, 8, 6, 2, 7, 2, 3, 2, 7, 9, 1, 7, 8, 6, 0, 8, 5, 7, 8, 4, 3, 8, 3, 8, 2, 7, 9, 6, 7, 9, 7, 6, 6, 8, 1, 4, 5, 4, 1, 0, 0, 9, 5,
+ 3, 8, 8, 3, 7, 8, 6, 3, 6, 0, 9, 5, 0, 6, 8, 0, 0, 6, 4, 2, 2, 5, 1, 2, 5, 2, 0, 5, 1, 1, 7, 3, 9, 2, 9, 8, 4, 8, 9, 6, 0, 8, 4, 1, 2, 8, 4, 8, 8, 6, 2, 6, 9, 4, 5, 6, 0, 4, 2, 4, 1, 9, 6, 5, 2, 8, 5, 0, 2, 2,
+ 2, 1, 0, 6, 6, 1, 1, 8, 6, 3, 0, 6, 7, 4, 4, 2, 7, 8, 6, 2, 2, 0, 3, 9, 1, 9, 4, 9, 4, 5, 0, 4, 7, 1, 2, 3, 7, 1, 3, 7, 8, 6, 9, 6, 0, 9, 5, 6, 3, 6, 4, 3, 7, 1, 9, 1, 7, 2, 8, 7, 4, 6, 7, 7, 6, 4, 6, 5, 7, 5,
+ 7, 3, 9, 6, 2, 4, 1, 3, 8, 9, 0, 8, 6, 5, 8, 3, 2, 6, 4, 5, 9, 9, 5, 8, 1, 3, 3, 9, 0, 4, 7, 8, 0, 2, 7, 5, 9, 0, 0, 9, 9, 4, 6, 5, 7, 6, 4, 0, 7, 8, 9, 5, 1, 2, 6, 9, 4, 6, 8, 3, 9, 8, 3, 5, 2, 5, 9, 5, 7, 0,
+ 9, 8, 2, 5, 8, 2, 2, 6, 2, 0, 5, 2, 2, 4, 8, 9, 4, 0, 7, 7, 2, 6, 7, 1, 9, 4, 7, 8, 2, 6, 8, 4, 8, 2, 6, 0, 1, 4, 7, 6, 9, 9, 0, 9, 0, 2, 6, 4, 0, 1, 3, 6, 3, 9, 4, 4, 3, 7, 4, 5, 5, 3, 0, 5, 0, 6, 8, 2, 0, 3,
+ 4, 9, 6, 2, 5, 2, 4, 5, 1, 7, 4, 9, 3, 9, 9, 6, 5, 1, 4, 3, 1, 4, 2, 9, 8, 0, 9, 1, 9, 0, 6, 5, 9, 2, 5, 0, 9, 3, 7, 2, 2, 1, 6, 9, 6, 4, 6, 1, 5, 1, 5, 7, 0, 9, 8, 5, 8, 3, 8, 7, 4, 1, 0, 5, 9, 7, 8, 8, 5, 9,
+ 5, 9, 7, 7, 2, 9, 7, 5, 4, 9, 8, 9, 3, 0, 1, 6, 1, 7, 5, 3, 9, 2, 8, 4, 6, 8, 1, 3, 8, 2, 6, 8, 6, 8, 3, 8, 6, 8, 9, 4, 2, 7, 7, 4, 1, 5, 5, 9, 9, 1, 8, 5, 5, 9, 2, 5, 2, 4, 5, 9, 5, 3, 9, 5, 9, 4, 3, 1, 0, 4,
+ 9, 9, 7, 2, 5, 2, 4, 6, 8, 0, 8, 4, 5, 9, 8, 7, 2, 7, 3, 6, 4, 4, 6, 9, 5, 8, 4, 8, 6, 5, 3, 8, 3, 6, 7, 3, 6, 2, 2, 2, 6, 2, 6, 0, 9, 9, 1, 2, 4, 6, 0, 8, 0, 5, 1, 2, 4, 3, 8, 8, 4, 3, 9, 0, 4, 5, 1, 2, 4, 4,
+ 1, 3, 6, 5, 4, 9, 7, 6, 2, 7, 8, 0, 7, 9, 7, 7, 1, 5, 6, 9, 1, 4, 3, 5, 9, 9, 7, 7, 0, 0, 1, 2, 9, 6, 1, 6, 0, 8, 9, 4, 4, 1, 6, 9, 4, 8, 6, 8, 5, 5, 5, 8, 4, 8, 4, 0, 6, 3, 5, 3, 4, 2, 2, 0, 7, 2, 2, 2, 5, 8,
+ 2, 8, 4, 8, 8, 6, 4, 8, 1, 5, 8, 4, 5, 6, 0, 2, 8, 5, 0, 6, 0, 1, 6, 8, 4, 2, 7, 3, 9, 4, 5, 2, 2, 6, 7, 4, 6, 7, 6, 7, 8, 8, 9, 5, 2, 5, 2, 1, 3, 8, 5, 2, 2, 5, 4, 9, 9, 5, 4, 6, 6, 6, 7, 2, 7, 8, 2, 3, 9, 8,
+ 6, 4, 5, 6, 5, 9, 6, 1, 1, 6, 3, 5, 4, 8, 8, 6, 2, 3, 0, 5, 7, 7, 4, 5, 6, 4, 9, 8, 0, 3, 5, 5, 9, 3, 6, 3, 4, 5, 6, 8, 1, 7, 4, 3, 2, 4, 1, 1, 2, 5, 1, 5, 0, 7, 6, 0, 6, 9, 4, 7, 9, 4, 5, 1, 0, 9, 6, 5, 9, 6,
+ 0, 9, 4, 0, 2, 5, 2, 2, 8, 8, 7, 9, 7, 1, 0, 8, 9, 3, 1, 4, 5, 6, 6, 9, 1, 3, 6, 8, 6, 7, 2, 2, 8, 7, 4, 8, 9, 4, 0, 5, 6, 0, 1, 0, 1, 5, 0, 3, 3, 0, 8, 6, 1, 7, 9, 2, 8, 6, 8, 0, 9, 2, 0, 8, 7, 4, 7, 6, 0, 9,
+ 1, 7, 8, 2, 4, 9, 3, 8, 5, 8, 9, 0, 0, 9, 7, 1, 4, 9, 0, 9, 6, 7, 5, 9, 8, 5, 2, 6, 1, 3, 6, 5, 5, 4, 9, 7, 8, 1, 8, 9, 3, 1, 2, 9, 7, 8, 4, 8, 2, 1, 6, 8, 2, 9, 9, 8, 9, 4, 8, 7, 2, 2, 6, 5, 8, 8, 0, 4, 8, 5,
+ 7, 5, 6, 4, 0, 1, 4, 2, 7, 0, 4, 7, 7, 5, 5, 5, 1, 3, 2, 3, 7, 9, 6, 4, 1, 4, 5, 1, 5, 2, 3, 7, 4, 6, 2, 3, 4, 3, 6, 4, 5, 4, 2, 8, 5, 8, 4, 4, 4, 7, 9, 5, 2, 6, 5, 8, 6, 7, 8, 2, 1, 0, 5, 1, 1, 4, 1, 3, 5, 4,
+ 7, 3, 5, 7, 3, 9, 5, 2, 3, 1, 1, 3, 4, 2, 7, 1, 6, 6, 1, 0, 2, 1, 3, 5, 9, 6, 9, 5, 3, 6, 2, 3, 1, 4, 4, 2, 9, 5, 2, 4, 8, 4, 9, 3, 7, 1, 8, 7, 1, 1, 0, 1, 4, 5, 7, 6, 5, 4, 0, 3, 5, 9, 0, 2, 7, 9, 9, 3, 4, 4,
+ 0, 3, 7, 4, 2, 0, 0, 7, 3, 1, 0, 5, 7, 8, 5, 3, 9, 0, 6, 2, 1, 9, 8, 3, 8, 7, 4, 4, 7, 8, 0, 8, 4, 7, 8, 4, 8, 9, 6, 8, 3, 3, 2, 1, 4, 4, 5, 7, 1, 3, 8, 6, 8, 7, 5, 1, 9, 4, 3, 5, 0, 6, 4, 3, 0, 2, 1, 8, 4, 5,
+ 3, 1, 9, 1, 0, 4, 8, 4, 8, 1, 0, 0, 5, 3, 7, 0, 6, 1, 4, 6, 8, 0, 6, 7, 4, 9, 1, 9, 2, 7, 8, 1, 9, 1, 1, 9, 7, 9, 3, 9, 9, 5, 2, 0, 6, 1, 4, 1, 9, 6, 6, 3, 4, 2, 8, 7, 5, 4, 4, 4, 0, 6, 4, 3, 7, 4, 5, 1, 2, 3,
+ 7, 1, 8, 1, 9, 2, 1, 7, 9, 9, 9, 8, 3, 9, 1, 0, 1, 5, 9, 1, 9, 5, 6, 1, 8, 1, 4, 6, 7, 5, 1, 4, 2, 6, 9, 1, 2, 3, 9, 7, 4, 8, 9, 4, 0, 9, 0, 7, 1, 8, 6, 4, 9, 4, 2, 3, 1, 9, 6, 1, 5, 6, 7, 9, 4, 5, 2, 0, 8, 0,
+ 9, 5, 1, 4, 6, 5, 5, 0, 2, 2, 5, 2, 3, 1, 6, 0, 3, 8, 8, 1, 9, 3, 0, 1, 4, 2, 0, 9, 3, 7, 6, 2, 1, 3, 7, 8, 5, 5, 9, 5, 6, 6, 3, 8, 9, 3, 7, 7, 8, 7, 0, 8, 3, 0, 3, 9, 0, 6, 9, 7, 9, 2, 0, 7, 7, 3, 4, 6, 7, 2,
+ 2, 1, 8, 2, 5, 6, 2, 5, 9, 9, 6, 6, 1, 5, 0, 1, 4, 2, 1, 5, 0, 3, 0, 6, 8, 0, 3, 8, 4, 4, 7, 7, 3, 4, 5, 4, 9, 2, 0, 2, 6, 0, 5, 4, 1, 4, 6, 6, 5, 9, 2, 5, 2, 0, 1, 4, 9, 7, 4, 4, 2, 8, 5, 0, 7, 3, 2, 5, 1, 8,
+ 6, 6, 6, 0, 0, 2, 1, 3, 2, 4, 3, 4, 0, 8, 8, 1, 9, 0, 7, 1, 0, 4, 8, 6, 3, 3, 1, 7, 3, 4, 6, 4, 9, 6, 5, 1, 4, 5, 3, 9, 0, 5, 7, 9, 6, 2, 6, 8, 5, 6, 1, 0, 0, 5, 5, 0, 8, 1, 0, 6, 6, 5, 8, 7, 9, 6, 9, 9, 8, 1,
+ 6, 3, 5, 7, 4, 7, 3, 6, 3, 8, 4, 0, 5, 2, 5, 7, 1, 4, 5, 9, 1, 0, 2, 8, 9, 7, 0, 6, 4, 1, 4, 0, 1, 1, 0, 9, 7, 1, 2, 0, 6, 2, 8, 0, 4, 3, 9, 0, 3, 9, 7, 5, 9, 5, 1, 5, 6, 7, 7, 1, 5, 7, 7, 0, 0, 4, 2, 0, 3, 3,
+ 7, 8, 6, 9, 9, 3, 6, 0, 0, 7, 2, 3, 0, 5, 5, 8, 7, 6, 3, 1, 7, 6, 3, 5, 9, 4, 2, 1, 8, 7, 3, 1, 2, 5, 1, 4, 7, 1, 2, 0, 5, 3, 2, 9, 2, 8, 1, 9, 1, 8, 2, 6, 1, 8, 6, 1, 2, 5, 8, 6, 7, 3, 2, 1, 5, 7, 9, 1, 9, 8,
+ 4, 1, 4, 8, 4, 8, 8, 2, 9, 1, 6, 4, 4, 7, 0, 6, 0, 9, 5, 7, 5, 2, 7, 0, 6, 9, 5, 7, 2, 2, 0, 9, 1, 7, 5, 6, 7, 1, 1, 6, 7, 2, 2, 9, 1, 0, 9, 8, 1, 6, 9, 0, 9, 1, 5, 2, 8, 0, 1, 7, 3, 5, 0, 6, 7, 1, 2, 7, 4, 8,
+ 5, 8, 3, 2, 2, 2, 8, 7, 1, 8, 3, 5, 2, 0, 9, 3, 5, 3, 9, 6, 5, 7, 2, 5, 1, 2, 1, 0, 8, 3, 5, 7, 9, 1, 5, 1, 3, 6, 9, 8, 8, 2, 0, 9, 1, 4, 4, 4, 2, 1, 0, 0, 6, 7, 5, 1, 0, 3, 3, 4, 6, 7, 1, 1, 0, 3, 1, 4, 1, 2,
+ 6, 7, 1, 1, 1, 3, 6, 9, 9, 0, 8, 6, 5, 8, 5, 1, 6, 3, 9, 8, 3, 1, 5, 0, 1, 9, 7, 0, 1, 6, 5, 1, 5, 1, 1, 6, 8, 5, 1, 7, 1, 4, 3, 7, 6, 5, 7, 6, 1, 8, 3, 5, 1, 5, 5, 6, 5, 0, 8, 8, 4, 9, 0, 9, 9, 8, 9, 8, 5, 9,
+ 9, 8, 2, 3, 8, 7, 3, 4, 5, 5, 2, 8, 3, 3, 1, 6, 3, 5, 5, 0, 7, 6, 4, 7, 9, 1, 8, 5, 3, 5, 8, 9, 3, 2, 2, 6, 1, 8, 5, 4, 8, 9, 6, 3, 2, 1, 3, 2, 9, 3, 3, 0, 8, 9, 8, 5, 7, 0, 6, 4, 2, 0, 4, 6, 7, 5, 2, 5, 9, 0,
+ 7, 0, 9, 1, 5, 4, 8, 1, 4, 1, 6, 5, 4, 9, 8, 5, 9, 4, 6, 1, 6, 3, 7, 1, 8, 0, 2, 7, 0, 9, 8, 1, 9, 9, 4, 3, 0, 9, 9, 2, 4, 4, 8, 8, 9, 5, 7, 5, 7, 1, 2, 8, 2, 8, 9, 0, 5, 9, 2, 3, 2, 3, 3, 2, 6, 0, 9, 7, 2, 9,
+ 9, 7, 1, 2, 0, 8, 4, 4, 3, 3, 5, 7, 3, 2, 6, 5, 4, 8, 9, 3, 8, 2, 3, 9, 1, 1, 9, 3, 2, 5, 9, 7, 4, 6, 3, 6, 6, 7, 3, 0, 5, 8, 3, 6, 0, 4, 1, 4, 2, 8, 1, 3, 8, 8, 3, 0, 3, 2, 0, 3, 8, 2, 4, 9, 0, 3, 7, 5, 8, 9,
+ 8, 5, 2, 4, 3, 7, 4, 4, 1, 7, 0, 2, 9, 1, 3, 2, 7, 6, 5, 6, 1, 8, 0, 9, 3, 7, 7, 3, 4, 4, 4, 0, 3, 0, 7, 0, 7, 4, 6, 9, 2, 1, 1, 2, 0, 1, 9, 1, 3, 0, 2, 0, 3, 3, 0, 3, 8, 0, 1, 9, 7, 6, 2, 1, 1, 0, 1, 1, 0, 0,
+ 4, 4, 9, 2, 9, 3, 2, 1, 5, 1, 6, 0, 8, 4, 2, 4, 4, 4, 8, 5, 9, 6, 3, 7, 6, 6, 9, 8, 3, 8, 9, 5, 2, 2, 8, 6, 8, 4, 7, 8, 3, 1, 2, 3, 5, 5, 2, 6, 5, 8, 2, 1, 3, 1, 4, 4, 9, 5, 7, 6, 8, 5, 7, 2, 6, 2, 4, 3, 3, 4,
+ 4, 1, 8, 9, 3, 0, 3, 9, 6, 8, 6, 4, 2, 6, 2, 4, 3, 4, 1, 0, 7, 7, 3, 2, 2, 6, 9, 7, 8, 0, 2, 8, 0, 7, 3, 1, 8, 9, 1, 5, 4, 4, 1, 1, 0, 1, 0, 4, 4, 6, 8, 2, 3, 2, 5, 2, 7, 1, 6, 2, 0, 1, 0, 5, 2, 6, 5, 2, 2, 7,
+ 2, 1, 1, 1, 6, 6, 0, 3, 9, 6, 6, 6, 5, 5, 7, 3, 0, 9, 2, 5, 4, 7, 1, 1, 0, 5, 5, 7, 8, 5, 3, 7, 6, 3, 4, 6, 6, 8, 2, 0, 6, 5, 3, 1, 0, 9, 8, 9, 6, 5, 2, 6, 9, 1, 8, 6, 2, 0, 5, 6, 4, 7, 6, 9, 3, 1, 2, 5, 7, 0,
+ 5, 8, 6, 3, 5, 6, 6, 2, 0, 1, 8, 5, 5, 8, 1, 0, 0, 7, 2, 9, 3, 6, 0, 6, 5, 9, 8, 7, 6, 4, 8, 6, 1, 1, 7, 9, 1, 0, 4, 5, 3, 3, 4, 8, 8, 5, 0, 3, 4, 6, 1, 1, 3, 6, 5, 7, 6, 8, 6, 7, 5, 3, 2, 4, 9, 4, 4, 1, 6, 6,
+ 8, 0, 3, 9, 6, 2, 6, 5, 7, 9, 7, 8, 7, 7, 1, 8, 5, 5, 6, 0, 8, 4, 5, 5, 2, 9, 6, 5, 4, 1, 2, 6, 6, 5, 4, 0, 8, 5, 3, 0, 6, 1, 4, 3, 4, 4, 4, 3, 1, 8, 5, 8, 6, 7, 6, 9, 7, 5, 1, 4, 5, 6, 6, 1, 4, 0, 6, 8, 0, 0,
+ 7, 0, 0, 2, 3, 7, 8, 7, 7, 6, 5, 9, 1, 3, 4, 4, 0, 1, 7, 1, 2, 7, 4, 9, 4, 7, 0, 4, 2, 0, 5, 6, 2, 2, 3, 0, 5, 3, 8, 9, 9, 4, 5, 6, 1, 3, 1, 4, 0, 7, 1, 1, 2, 7, 0, 0, 0, 4, 0, 7, 8, 5, 4, 7, 3, 3, 2, 6, 9, 9,
+ 3, 9, 0, 8, 1, 4, 5, 4, 6, 6, 4, 6, 4, 5, 8, 8, 0, 7, 9, 7, 2, 7, 0, 8, 2, 6, 6, 8, 3, 0, 6, 3, 4, 3, 2, 8, 5, 8, 7, 8, 5, 6, 9, 8, 3, 0, 5, 2, 3, 5, 8, 0, 8, 9, 3, 3, 0, 6, 5, 7, 5, 7, 4, 0, 6, 7, 9, 5, 4, 5,
+ 7, 1, 6, 3, 7, 7, 5, 2, 5, 4, 2, 0, 2, 1, 1, 4, 9, 5, 5, 7, 6, 1, 5, 8, 1, 4, 0, 0, 2, 5, 0, 1, 2, 6, 2, 2, 8, 5, 9, 4, 1, 3, 0, 2, 1, 6, 4, 7, 1, 5, 5, 0, 9, 7, 9, 2, 5, 9, 2, 3, 0, 9, 9, 0, 7, 9, 6, 5, 4, 7,
+ 3, 7, 6, 1, 2, 5, 5, 1, 7, 6, 5, 6, 7, 5, 1, 3, 5, 7, 5, 1, 7, 8, 2, 9, 6, 6, 6, 4, 5, 4, 7, 7, 9, 1, 7, 4, 5, 0, 1, 1, 2, 9, 9, 6, 1, 4, 8, 9, 0, 3, 0, 4, 6, 3, 9, 9, 4, 7, 1, 3, 2, 9, 6, 2, 1, 0, 7, 3, 4, 0,
+ 4, 3, 7, 5, 1, 8, 9, 5, 7, 3, 5, 9, 6, 1, 4, 5, 8, 9, 0, 1, 9, 3, 8, 9, 7, 1, 3, 1, 1, 1, 7, 9, 0, 4, 2, 9, 7, 8, 2, 8, 5, 6, 4, 7, 5, 0, 3, 2, 0, 3, 1, 9, 8, 6, 9, 1, 5, 1, 4, 0, 2, 8, 7, 0, 8, 0, 8, 5, 9, 9,
+ 0, 4, 8, 0, 1, 0, 9, 4, 1, 2, 1, 4, 7, 2, 2, 1, 3, 1, 7, 9, 4, 7, 6, 4, 7, 7, 7, 2, 6, 2, 2, 4, 1, 4, 2, 5, 4, 8, 5, 4, 5, 4, 0, 3, 3, 2, 1, 5, 7, 1, 8, 5, 3, 0, 6, 1, 4, 2, 2, 8, 8, 1, 3, 7, 5, 8, 5, 0, 4, 3,
+ 0, 6, 3, 3, 2, 1, 7, 5, 1, 8, 2, 9, 7, 9, 8, 6, 6, 2, 2, 3, 7, 1, 7, 2, 1, 5, 9, 1, 6, 0, 7, 7, 1, 6, 6, 9, 2, 5, 4, 7, 4, 8, 7, 3, 8, 9, 8, 6, 6, 5, 4, 9, 4, 9, 4, 5, 0, 1, 1, 4, 6, 5, 4, 0, 6, 2, 8, 4, 3, 3,
+ 6, 6, 3, 9, 3, 7, 9, 0, 0, 3, 9, 7, 6, 9, 2, 6, 5, 6, 7, 2, 1, 4, 6, 3, 8, 5, 3, 0, 6, 7, 3, 6, 0, 9, 6, 5, 7, 1, 2, 0, 9, 1, 8, 0, 7, 6, 3, 8, 3, 2, 7, 1, 6, 6, 4, 1, 6, 2, 7, 4, 8, 8, 8, 8, 0, 0, 7, 8, 6, 9,
+ 2, 5, 6, 0, 2, 9, 0, 2, 2, 8, 4, 7, 2, 1, 0, 4, 0, 3, 1, 7, 2, 1, 1, 8, 6, 0, 8, 2, 0, 4, 1, 9, 0, 0, 0, 4, 2, 2, 9, 6, 6, 1, 7, 1, 1, 9, 6, 3, 7, 7, 9, 2, 1, 3, 3, 7, 5, 7, 5, 1, 1, 4, 9, 5, 9, 5, 0, 1, 5, 6,
+ 6, 0, 4, 9, 6, 3, 1, 8, 6, 2, 9, 4, 7, 2, 6, 5, 4, 7, 3, 6, 4, 2, 5, 2, 3, 0, 8, 1, 7, 7, 0, 3, 6, 7, 5, 1, 5, 9, 0, 6, 7, 3, 5, 0, 2, 3, 5, 0, 7, 2, 8, 3, 5, 4, 0, 5, 6, 7, 0, 4, 0, 3, 8, 6, 7, 4, 3, 5, 1, 3,
+ 6, 2, 2, 2, 2, 4, 7, 7, 1, 5, 8, 9, 1, 5, 0, 4, 9, 5, 3, 0, 9, 8, 4, 4, 4, 8, 9, 3, 3, 3, 0, 9, 6, 3, 4, 0, 8, 7, 8, 0, 7, 6, 9, 3, 2, 5, 9, 9, 3, 9, 7, 8, 0, 5, 4, 1, 9, 3, 4, 1, 4, 4, 7, 3, 7, 7, 4, 4, 1, 8,
+ 4, 2, 6, 3, 1, 2, 9, 8, 6, 0, 8, 0, 9, 9, 8, 8, 8, 6, 8, 7, 4, 1, 3, 2, 6, 0, 4, 7, 2, 1, 5, 6, 9, 5, 1, 6, 2, 3, 9, 6, 5, 8, 6, 4, 5, 7, 3, 0, 2, 1, 6, 3, 1, 5, 9, 8, 1, 9, 3, 1, 9, 5, 1, 6, 7, 3, 5, 3, 8, 1,
+ 2, 9, 7, 4, 1, 6, 7, 7, 2, 9, 4, 7, 8, 6, 7, 2, 4, 2, 2, 9, 2, 4, 6, 5, 4, 3, 6, 6, 8, 0, 0, 9, 8, 0, 6, 7, 6, 9, 2, 8, 2, 3, 8, 2, 8, 0, 6, 8, 9, 9, 6, 4, 0, 0, 4, 8, 2, 4, 3, 5, 4, 0, 3, 7, 0, 1, 4, 1, 6, 3,
+ 1, 4, 9, 6, 5, 8, 9, 7, 9, 4, 0, 9, 2, 4, 3, 2, 3, 7, 8, 9, 6, 9, 0, 7, 0, 6, 9, 7, 7, 9, 4, 2, 2, 3, 6, 2, 5, 0, 8, 2, 2, 1, 6, 8, 8, 9, 5, 7, 3, 8, 3, 7, 9, 8, 6, 2, 3, 0, 0, 1, 5, 9, 3, 7, 7, 6, 4, 7, 1, 6,
+ 5, 1, 2, 2, 8, 9, 3, 5, 7, 8, 6, 0, 1, 5, 8, 8, 1, 6, 1, 7, 5, 5, 7, 8, 2, 9, 7, 3, 5, 2, 3, 3, 4, 4, 6, 0, 4, 2, 8, 1, 5, 1, 2, 6, 2, 7, 2, 0, 3, 7, 3, 4, 3, 1, 4, 6, 5, 3, 1, 9, 7, 7, 7, 7, 4, 1, 6, 0, 3, 1,
+ 9, 9, 0, 6, 6, 5, 5, 4, 1, 8, 7, 6, 3, 9, 7, 9, 2, 9, 3, 3, 4, 4, 1, 9, 5, 2, 1, 5, 4, 1, 3, 4, 1, 8, 9, 9, 4, 8, 5, 4, 4, 4, 7, 3, 4, 5, 6, 7, 3, 8, 3, 1, 6, 2, 4, 9, 9, 3, 4, 1, 9, 1, 3, 1, 8, 1, 4, 8, 0, 9,
+ 2, 7, 7, 7, 7, 1, 0, 3, 8, 6, 3, 8, 7, 7, 3, 4, 3, 1, 7, 7, 2, 0, 7, 5, 4, 5, 6, 5, 4, 5, 3, 2, 2, 0, 7, 7, 7, 0, 9, 2, 1, 2, 0, 1, 9, 0, 5, 1, 6, 6, 0, 9, 6, 2, 8, 0, 4, 9, 0, 9, 2, 6, 3, 6, 0, 1, 9, 7, 5, 9,
+ 8, 8, 2, 8, 1, 6, 1, 3, 3, 2, 3, 1, 6, 6, 6, 3, 6, 5, 2, 8, 6, 1, 9, 3, 2, 6, 6, 8, 6, 3, 3, 6, 0, 6, 2, 7, 3, 5, 6, 7, 6, 3, 0, 3, 5, 4, 4, 7, 7, 6, 2, 8, 0, 3, 5, 0, 4, 5, 0, 7, 7, 7, 2, 3, 5, 5, 4, 7, 1, 0,
+ 5, 8, 5, 9, 5, 4, 8, 7, 0, 2, 7, 9, 0, 8, 1, 4, 3, 5, 6, 2, 4, 0, 1, 4, 5, 1, 7, 1, 8, 0, 6, 2, 4, 6, 4, 3, 6, 2, 6, 7, 9, 4, 5, 6, 1, 2, 7, 5, 3, 1, 8, 1, 3, 4, 0, 7, 8, 3, 3, 0, 3, 3, 6, 2, 5, 4, 2, 3, 2, 7,
+ 8, 3, 9, 4, 4, 9, 7, 5, 3, 8, 2, 4, 3, 7, 2, 0, 5, 8, 3, 5, 3, 1, 1, 4, 7, 7, 1, 1, 9, 9, 2, 6, 0, 6, 3, 8, 1, 3, 3, 4, 6, 7, 7, 6, 8, 7, 9, 6, 9, 5, 9, 7, 0, 3, 0, 9, 8, 3, 3, 9, 1, 3, 0, 7, 7, 1, 0, 9, 8, 7,
+ 0, 4, 0, 8, 5, 9, 1, 3, 3, 7, 4, 6, 4, 1, 4, 4, 2, 8, 2, 2, 7, 7, 2, 6, 3, 4, 6, 5, 9, 4, 7, 0, 4, 7, 4, 5, 8, 7, 8, 4, 7, 7, 8, 7, 2, 0, 1, 9, 2, 7, 7, 1, 5, 2, 8, 0, 7, 3, 1, 7, 6, 7, 9, 0, 7, 7, 0, 7, 1, 5,
+ 7, 2, 1, 3, 4, 4, 4, 7, 3, 0, 6, 0, 5, 7, 0, 0, 7, 3, 3, 4, 9, 2, 4, 3, 6, 9, 3, 1, 1, 3, 8, 3, 5, 0, 4, 9, 3, 1, 6, 3, 1, 2, 8, 4, 0, 4, 2, 5, 1, 2, 1, 9, 2, 5, 6, 5, 1, 7, 9, 8, 0, 6, 9, 4, 1, 1, 3, 5, 2, 8,
+ 0, 1, 3, 1, 4, 7, 0, 1, 3, 0, 4, 7, 8, 1, 6, 4, 3, 7, 8, 8, 5, 1, 8, 5, 2, 9, 0, 9, 2, 8, 5, 4, 5, 2, 0, 1, 1, 6, 5, 8, 3, 9, 3, 4, 1, 9, 6, 5, 6, 2, 1, 3, 4, 9, 1, 4, 3, 4, 1, 5, 9, 5, 6, 2, 5, 8, 6, 5, 8, 6,
+ 5, 5, 7, 0, 5, 5, 2, 6, 9, 0, 4, 9, 6, 5, 2, 0, 9, 8, 5, 8, 0, 3, 3, 8, 5, 0, 7, 2, 2, 4, 2, 6, 4, 8, 2, 9, 3, 9, 7, 2, 8, 5, 8, 4, 7, 8, 3, 1, 6, 3, 0, 5, 7, 7, 7, 7, 5, 6, 0, 6, 8, 8, 8, 7, 6, 4, 4, 6, 2, 4,
+ 8, 2, 4, 6, 8, 5, 7, 9, 2, 6, 0, 3, 9, 5, 3, 5, 2, 7, 7, 3, 4, 8, 0, 3, 0, 4, 8, 0, 2, 9, 0, 0, 5, 8, 7, 6, 0, 7, 5, 8, 2, 5, 1, 0, 4, 7, 4, 7, 0, 9, 1, 6, 4, 3, 9, 6, 1, 3, 6, 2, 6, 7, 6, 0, 4, 4, 9, 2, 5, 6,
+ 2, 7, 4, 2, 0, 4, 2, 0, 8, 3, 2, 0, 8, 5, 6, 6, 1, 1, 9, 0, 6, 2, 5, 4, 5, 4, 3, 3, 7, 2, 1, 3, 1, 5, 3, 5, 9, 5, 8, 4, 5, 0, 6, 8, 7, 7, 2, 4, 6, 0, 2, 9, 0, 1, 6, 1, 8, 7, 6, 6, 7, 9, 5, 2, 4, 0, 6, 1, 6, 3,
+ 4, 2, 5, 2, 2, 5, 7, 7, 1, 9, 5, 4, 2, 9, 1, 6, 2, 9, 9, 1, 9, 3, 0, 6, 4, 5, 5, 3, 7, 7, 9, 9, 1, 4, 0, 3, 7, 3, 4, 0, 4, 3, 2, 8, 7, 5, 2, 6, 2, 8, 8, 8, 9, 6, 3, 9, 9, 5, 8, 7, 9, 4, 7, 5, 7, 2, 9, 1, 7, 4,
+ 6, 4, 2, 6, 3, 5, 7, 4, 5, 5, 2, 5, 4, 0, 7, 9, 0, 9, 1, 4, 5, 1, 3, 5, 7, 1, 1, 1, 3, 6, 9, 4, 1, 0, 9, 1, 1, 9, 3, 9, 3, 2, 5, 1, 9, 1, 0, 7, 6, 0, 2, 0, 8, 2, 5, 2, 0, 2, 6, 1, 8, 7, 9, 8, 5, 3, 1, 8, 8, 7,
+ 7, 0, 5, 8, 4, 2, 9, 7, 2, 5, 9, 1, 6, 7, 7, 8, 1, 3, 1, 4, 9, 6, 9, 9, 0, 0, 9, 0, 1, 9, 2, 1, 1, 6, 9, 7, 1, 7, 3, 7, 2, 7, 8, 4, 7, 6, 8, 4, 7, 2, 6, 8, 6, 0, 8, 4, 9, 0, 0, 3, 3, 7, 7, 0, 2, 4, 2, 4, 2, 9,
+ 1, 6, 5, 1, 3, 0, 0, 5, 0, 0, 5, 1, 6, 8, 3, 2, 3, 3, 6, 4, 3, 5, 0, 3, 8, 9, 5, 1, 7, 0, 2, 9, 8, 9, 3, 9, 2, 2, 3, 3, 4, 5, 1, 7, 2, 2, 0, 1, 3, 8, 1, 2, 8, 0, 6, 9, 6, 5, 0, 1, 1, 7, 8, 4, 4, 0, 8, 7, 4, 5,
+ 1, 9, 6, 0, 1, 2, 1, 2, 2, 8, 5, 9, 9, 3, 7, 1, 6, 2, 3, 1, 3, 0, 1, 7, 1, 1, 4, 4, 4, 8, 4, 6, 4, 0, 9, 0, 3, 8, 9, 0, 6, 4, 4, 9, 5, 4, 4, 4, 0, 0, 6, 1, 9, 8, 6, 9, 0, 7, 5, 4, 8, 5, 1, 6, 0, 2, 6, 3, 2, 7,
+ 5, 0, 5, 2, 9, 8, 3, 4, 9, 1, 8, 7, 4, 0, 7, 8, 6, 6, 8, 0, 8, 8, 1, 8, 3, 3, 8, 5, 1, 0, 2, 2, 8, 3, 3, 4, 5, 0, 8, 5, 0, 4, 8, 6, 0, 8, 2, 5, 0, 3, 9, 3, 0, 2, 1, 3, 3, 2, 1, 9, 7, 1, 5, 5, 1, 8, 4, 3, 0, 6,
+ 3, 5, 4, 5, 5, 0, 0, 7, 6, 6, 8, 2, 8, 2, 9, 4, 9, 3, 0, 4, 1, 3, 7, 7, 6, 5, 5, 2, 7, 9, 3, 9, 7, 5, 1, 7, 5, 4, 6, 1, 3, 9, 5, 3, 9, 8, 4, 6, 8, 3, 3, 9, 3, 6, 3, 8, 3, 0, 4, 7, 4, 6, 1, 1, 9, 9, 6, 6, 5, 3,
+ 8, 5, 8, 1, 5, 3, 8, 4, 2, 0, 5, 6, 8, 5, 3, 3, 8, 6, 2, 1, 8, 6, 7, 2, 5, 2, 3, 3, 4, 0, 2, 8, 3, 0, 8, 7, 1, 1, 2, 3, 2, 8, 2, 7, 8, 9, 2, 1, 2, 5, 0, 7, 7, 1, 2, 6, 2, 9, 4, 6, 3, 2, 2, 9, 5, 6, 3, 9, 8, 9,
+ 8, 9, 8, 9, 3, 5, 8, 2, 1, 1, 6, 7, 4, 5, 6, 2, 7, 0, 1, 0, 2, 1, 8, 3, 5, 6, 4, 6, 2, 2, 0, 1, 3, 4, 9, 6, 7, 1, 5, 1, 8, 8, 1, 9, 0, 9, 7, 3, 0, 3, 8, 1, 1, 9, 8, 0, 0, 4, 9, 7, 3, 4, 0, 7, 2, 3, 9, 6, 1, 0,
+ 3, 6, 8, 5, 4, 0, 6, 6, 4, 3, 1, 9, 3, 9, 5, 0, 9, 7, 9, 0, 1, 9, 0, 6, 9, 9, 6, 3, 9, 5, 5, 2, 4, 5, 3, 0, 0, 5, 4, 5, 0, 5, 8, 0, 6, 8, 5, 5, 0, 1, 9, 5, 6, 7, 3, 0, 2, 2, 9, 2, 1, 9, 1, 3, 9, 3, 3, 9, 1, 8,
+ 5, 6, 8, 0, 3, 4, 4, 9, 0, 3, 9, 8, 2, 0, 5, 9, 5, 5, 1, 0, 0, 2, 2, 6, 3, 5, 3, 5, 3, 6, 1, 9, 2, 0, 4, 1, 9, 9, 4, 7, 4, 5, 5, 3, 8, 5, 9, 3, 8, 1, 0, 2, 3, 4, 3, 9, 5, 5, 4, 4, 9, 5, 9, 7, 7, 8, 3, 7, 7, 9,
+ 0, 2, 3, 7, 4, 2, 1, 6, 1, 7, 2, 7, 1, 1, 1, 7, 2, 3, 6, 4, 3, 4, 3, 5, 4, 3, 9, 4, 7, 8, 2, 2, 1, 8, 1, 8, 5, 2, 8, 6, 2, 4, 0, 8, 5, 1, 4, 0, 0, 6, 6, 6, 0, 4, 4, 3, 3, 2, 5, 8, 8, 8, 5, 6, 9, 8, 6, 7, 0, 5,
+ 4, 3, 1, 5, 4, 7, 0, 6, 9, 6, 5, 7, 4, 7, 4, 5, 8, 5, 5, 0, 3, 3, 2, 3, 2, 3, 3, 4, 2, 1, 0, 7, 3, 0, 1, 5, 4, 5, 9, 4, 0, 5, 1, 6, 5, 5, 3, 7, 9, 0, 6, 8, 6, 6, 2, 7, 3, 3, 3, 7, 9, 9, 5, 8, 5, 1, 1, 5, 6, 2,
+ 5, 7, 8, 4, 3, 2, 2, 9, 8, 8, 2, 7, 3, 7, 2, 3, 1, 9, 8, 9, 8, 7, 5, 7, 1, 4, 1, 5, 9, 5, 7, 8, 1, 1, 1, 9, 6, 3, 5, 8, 3, 3, 0, 0, 5, 9, 4, 0, 8, 7, 3, 0, 6, 8, 1, 2, 1, 6, 0, 2, 8, 7, 6, 4, 9, 6, 2, 8, 6, 7,
+ 4, 4, 6, 0, 4, 7, 7, 4, 6, 4, 9, 1, 5, 9, 9, 5, 0, 5, 4, 9, 7, 3, 7, 4, 2, 5, 6, 2, 6, 9, 0, 1, 0, 4, 9, 0, 3, 7, 7, 8, 1, 9, 8, 6, 8, 3, 5, 9, 3, 8, 1, 4, 6, 5, 7, 4, 1, 2, 6, 8, 0, 4, 9, 2, 5, 6, 4, 8, 7, 9,
+ 8, 5, 5, 6, 1, 4, 5, 3, 7, 2, 3, 4, 7, 8, 6, 7, 3, 3, 0, 3, 9, 0, 4, 6, 8, 8, 3, 8, 3, 4, 3, 6, 3, 4, 6, 5, 5, 3, 7, 9, 4, 9, 8, 6, 4, 1, 9, 2, 7, 0, 5, 6, 3, 8, 7, 2, 9, 3, 1, 7, 4, 8, 7, 2, 3, 3, 2, 0, 8, 3,
+ 7, 6, 0, 1, 1, 2, 3, 0, 2, 9, 9, 1, 1, 3, 6, 7, 9, 3, 8, 6, 2, 7, 0, 8, 9, 4, 3, 8, 7, 9, 9, 3, 6, 2, 0, 1, 6, 2, 9, 5, 1, 5, 4, 1, 3, 3, 7, 1, 4, 2, 4, 8, 9, 2, 8, 3, 0, 7, 2, 2, 0, 1, 2, 6, 9, 0, 1, 4, 7, 5,
+ 4, 6, 6, 8, 4, 7, 6, 5, 3, 5, 7, 6, 1, 6, 4, 7, 7, 3, 7, 9, 4, 6, 7, 5, 2, 0, 0, 4, 9, 0, 7, 5, 7, 1, 5, 5, 5, 2, 7, 8, 1, 9, 6, 5, 3, 6, 2, 1, 3, 2, 3, 9, 2, 6, 4, 0, 6, 1, 6, 0, 1, 3, 6, 3, 5, 8, 1, 5, 5, 9,
+ 0, 7, 4, 2, 2, 0, 2, 0, 2, 0, 3, 1, 8, 7, 2, 7, 7, 6, 0, 5, 2, 7, 7, 2, 1, 9, 0, 0, 5, 5, 6, 1, 4, 8, 4, 2, 5, 5, 5, 1, 8, 7, 9, 2, 5, 3, 0, 3, 4, 3, 5, 1, 3, 9, 8, 4, 4, 2, 5, 3, 2, 2, 3, 4, 1, 5, 7, 6, 2, 3,
+ 3, 6, 1, 0, 6, 4, 2, 5, 0, 6, 3, 9, 0, 4, 9, 7, 5, 0, 0, 8, 6, 5, 6, 2, 7, 1, 0, 9, 5, 3, 5, 9, 1, 9, 4, 6, 5, 8, 9, 7, 5, 1, 4, 1, 3, 1, 0, 3, 4, 8, 2, 2, 7, 6, 9, 3, 0, 6, 2, 4, 7, 4, 3, 5, 3, 6, 3, 2, 5, 6,
+ 9, 1, 6, 0, 7, 8, 1, 5, 4, 7, 8, 1, 8, 1, 1, 5, 2, 8, 4, 3, 6, 6, 7, 9, 5, 7, 0, 6, 1, 1, 0, 8, 6, 1, 5, 3, 3, 1, 5, 0, 4, 4, 5, 2, 1, 2, 7, 4, 7, 3, 9, 2, 4, 5, 4, 4, 9, 4, 5, 4, 2, 3, 6, 8, 2, 8, 8, 6, 0, 6,
+ 1, 3, 4, 0, 8, 4, 1, 4, 8, 6, 3, 7, 7, 6, 7, 0, 0, 9, 6, 1, 2, 0, 7, 1, 5, 1, 2, 4, 9, 1, 4, 0, 4, 3, 0, 2, 7, 2, 5, 3, 8, 6, 0, 7, 6, 4, 8, 2, 3, 6, 3, 4, 1, 4, 3, 3, 4, 6, 2, 3, 5, 1, 8, 9, 7, 5, 7, 6, 6, 4,
+ 5, 2, 1, 6, 4, 1, 3, 7, 6, 7, 9, 6, 9, 0, 3, 1, 4, 9, 5, 0, 1, 9, 1, 0, 8, 5, 7, 5, 9, 8, 4, 4, 2, 3, 9, 1, 9, 8, 6, 2, 9, 1, 6, 4, 2, 1, 9, 3, 9, 9, 4, 9, 0, 7, 2, 3, 6, 2, 3, 4, 6, 4, 6, 8, 4, 4, 1, 1, 7, 3,
+ 9, 4, 0, 3, 2, 6, 5, 9, 1, 8, 4, 0, 4, 4, 3, 7, 8, 0, 5, 1, 3, 3, 3, 8, 9, 4, 5, 2, 5, 7, 4, 2, 3, 9, 9, 5, 0, 8, 2, 9, 6, 5, 9, 1, 2, 2, 8, 5, 0, 8, 5, 5, 5, 8, 2, 1, 5, 7, 2, 5, 0, 3, 1, 0, 7, 1, 2, 5, 7, 0,
+ 1, 2, 6, 6, 8, 3, 0, 2, 4, 0, 2, 9, 2, 9, 5, 2, 5, 2, 2, 0, 1, 1, 8, 7, 2, 6, 7, 6, 7, 5, 6, 2, 2, 0, 4, 1, 5, 4, 2, 0, 5, 1, 6, 1, 8, 4, 1, 6, 3, 4, 8, 4, 7, 5, 6, 5, 1, 6, 9, 9, 9, 8, 1, 1, 6, 1, 4, 1, 0, 1,
+ 0, 0, 2, 9, 9, 6, 0, 7, 8, 3, 8, 6, 9, 0, 9, 2, 9, 1, 6, 0, 3, 0, 2, 8, 8, 4, 0, 0, 2, 6, 9, 1, 0, 4, 1, 4, 0, 7, 9, 2, 8, 8, 6, 2, 1, 5, 0, 7, 8, 4, 2, 4, 5, 1, 6, 7, 0, 9, 0, 8, 7, 0, 0, 0, 6, 9, 9, 2, 8, 2,
+ 1, 2, 0, 6, 6, 0, 4, 1, 8, 3, 7, 1, 8, 0, 6, 5, 3, 5, 5, 6, 7, 2, 5, 2, 5, 3, 2, 5, 6, 7, 5, 3, 2, 8, 6, 1, 2, 9, 1, 0, 4, 2, 4, 8, 7, 7, 6, 1, 8, 2, 5, 8, 2, 9, 7, 6, 5, 1, 5, 7, 9, 5, 9, 8, 4, 7, 0, 3, 5, 6,
+ 2, 2, 2, 6, 2, 9, 3, 4, 8, 6, 0, 0, 3, 4, 1, 5, 8, 7, 2, 2, 9, 8, 0, 5, 3, 4, 9, 8, 9, 6, 5, 0, 2, 2, 6, 2, 9, 1, 7, 4, 8, 7, 8, 8, 2, 0, 2, 7, 3, 4, 2, 0, 9, 2, 2, 2, 2, 4, 5, 3, 3, 9, 8, 5, 6, 2, 6, 4, 7, 6,
+ 6, 9, 1, 4, 9, 0, 5, 5, 6, 2, 8, 4, 2, 5, 0, 3, 9, 1, 2, 7, 5, 7, 7, 1, 0, 2, 8, 4, 0, 2, 7, 9, 9, 8, 0, 6, 6, 3, 6, 5, 8, 2, 5, 4, 8, 8, 9, 2, 6, 4, 8, 8, 0, 2, 5, 4, 5, 6, 6, 1, 0, 1, 7, 2, 9, 6, 7, 0, 2, 6,
+ 6, 4, 0, 7, 6, 5, 5, 9, 0, 4, 2, 9, 0, 9, 9, 4, 5, 6, 8, 1, 5, 0, 6, 5, 2, 6, 5, 3, 0, 5, 3, 7, 1, 8, 2, 9, 4, 1, 2, 7, 0, 3, 3, 6, 9, 3, 1, 3, 7, 8, 5, 1, 7, 8, 6, 0, 9, 0, 4, 0, 7, 0, 8, 6, 6, 7, 1, 1, 4, 9,
+ 6, 5, 5, 8, 3, 4, 3, 4, 3, 4, 7, 6, 9, 3, 3, 8, 5, 7, 8, 1, 7, 1, 1, 3, 8, 6, 4, 5, 5, 8, 7, 3, 6, 7, 8, 1, 2, 3, 0, 1, 4, 5, 8, 7, 6, 8, 7, 1, 2, 6, 6, 0, 3, 4, 8, 9, 1, 3, 9, 0, 9, 5, 6, 2, 0, 0, 9, 9, 3, 9,
+ 3, 6, 1, 0, 3, 1, 0, 2, 9, 1, 6, 1, 6, 1, 5, 2, 8, 8, 1, 3, 8, 4, 3, 7, 9, 0, 9, 9, 0, 4, 2, 3, 1, 7, 4, 7, 3, 3, 6, 3, 9, 4, 8, 0, 4, 5, 7, 5, 9, 3, 1, 4, 9, 3, 1, 4, 0, 5, 2, 9, 7, 6, 3, 4, 7, 5, 7, 4, 8, 1,
+ 1, 9, 3, 5, 6, 7, 0, 9, 1, 1, 0, 1, 3, 7, 7, 5, 1, 7, 2, 1, 0, 0, 8, 0, 3, 1, 5, 5, 9, 0, 2, 4, 8, 5, 3, 0, 9, 0, 6, 6, 9, 2, 0, 3, 7, 6, 7, 1, 9, 2, 2, 0, 3, 3, 2, 2, 9, 0, 9, 4, 3, 3, 4, 6, 7, 6, 8, 5, 1, 4,
+ 2, 2, 1, 4, 4, 7, 7, 3, 7, 9, 3, 9, 3, 7, 5, 1, 7, 0, 3, 4, 4, 3, 6, 6, 1, 9, 9, 1, 0, 4, 0, 3, 3, 7, 5, 1, 1, 1, 7, 3, 5, 4, 7, 1, 9, 1, 8, 5, 5, 0, 4, 6, 4, 4, 9, 0, 2, 6, 3, 6, 5, 5, 1, 2, 8, 1, 6, 2, 2, 8,
+ 8, 2, 4, 4, 6, 2, 5, 7, 5, 9, 1, 6, 3, 3, 3, 0, 3, 9, 1, 0, 7, 2, 2, 5, 3, 8, 3, 7, 4, 2, 1, 8, 2, 1, 4, 0, 8, 8, 3, 5, 0, 8, 6, 5, 7, 3, 9, 1, 7, 7, 1, 5, 0, 9, 6, 8, 2, 8, 8, 7, 4, 7, 8, 2, 6, 5, 6, 9, 9, 5,
+ 9, 9, 5, 7, 4, 4, 9, 0, 6, 6, 1, 7, 5, 8, 3, 4, 4, 1, 3, 7, 5, 2, 2, 3, 9, 7, 0, 9, 6, 8, 3, 4, 0, 8, 0, 0, 5, 3, 5, 5, 9, 8, 4, 9, 1, 7, 5, 4, 1, 7, 3, 8, 1, 8, 8, 3, 9, 9, 9, 4, 4, 6, 9, 7, 4, 8, 6, 7, 6, 2,
+ 6, 5, 5, 1, 6, 5, 8, 2, 7, 6, 5, 8, 4, 8, 3, 5, 8, 8, 4, 5, 3, 1, 4, 2, 7, 7, 5, 6, 8, 7, 9, 0, 0, 2, 9, 0, 9, 5, 1, 7, 0, 2, 8, 3, 5, 2, 9, 7, 1, 6, 3, 4, 4, 5, 6, 2, 1, 2, 9, 6, 4, 0, 4, 3, 5, 2, 3, 1, 1, 7,
+ 6, 0, 0, 6, 6, 5, 1, 0, 1, 2, 4, 1, 2, 0, 0, 6, 5, 9, 7, 5, 5, 8, 5, 1, 2, 7, 6, 1, 7, 8, 5, 8, 3, 8, 2, 9, 2, 0, 4, 1, 9, 7, 4, 8, 4, 4, 2, 3, 6, 0, 8, 0, 0, 7, 1, 9, 3, 0, 4, 5, 7, 6, 1, 8, 9, 3, 2, 3, 4, 9,
+ 2, 2, 9, 2, 7, 9, 6, 5, 0, 1, 9, 8, 7, 5, 1, 8, 7, 2, 1, 2, 7, 2, 6, 7, 5, 0, 7, 9, 8, 1, 2, 5, 5, 4, 7, 0, 9, 5, 8, 9, 0, 4, 5, 5, 6, 3, 5, 7, 9, 2, 1, 2, 2, 1, 0, 3, 3, 3, 4, 6, 6, 9, 7, 4, 9, 9, 2, 3, 5, 6,
+ 3, 0, 2, 5, 4, 9, 4, 7, 8, 0, 2, 4, 9, 0, 1, 1, 4, 1, 9, 5, 2, 1, 2, 3, 8, 2, 8, 1, 5, 3, 0, 9, 1, 1, 4, 0, 7, 9, 0, 7, 3, 8, 6, 0, 2, 5, 1, 5, 2, 2, 7, 4, 2, 9, 9, 5, 8, 1, 8, 0, 7, 2, 4, 7, 1, 6, 2, 5, 9, 1,
+ 6, 6, 8, 5, 4, 5, 1, 3, 3, 3, 1, 2, 3, 9, 4, 8, 0, 4, 9, 4, 7, 0, 7, 9, 1, 1, 9, 1, 5, 3, 2, 6, 7, 3, 4, 3, 0, 2, 8, 2, 4, 4, 1, 8, 6, 0, 4, 1, 4, 2, 6, 3, 6, 3, 9, 5, 4, 8, 0, 0, 0, 4, 4, 8, 0, 0, 2, 6, 7, 0,
+ 4, 9, 6, 2, 4, 8, 2, 0, 1, 7, 9, 2, 8, 9, 6, 4, 7, 6, 6, 9, 7, 5, 8, 3, 1, 8, 3, 2, 7, 1, 3, 1, 4, 2, 5, 1, 7, 0, 2, 9, 6, 9, 2, 3, 4, 8, 8, 9, 6, 2, 7, 6, 6, 8, 4, 4, 0, 3, 2, 3, 2, 6, 0, 9, 2, 7, 5, 2, 4, 9,
+ 6, 0, 3, 5, 7, 9, 9, 6, 4, 6, 9, 2, 5, 6, 5, 0, 4, 9, 3, 6, 8, 1, 8, 3, 6, 0, 9, 0, 0, 3, 2, 3, 8, 0, 9, 2, 9, 3, 4, 5,
+ 9, 5, 8, 8, 9, 7, 0, 6, 9, 5, 3, 6, 5, 3, 4, 9, 4, 0, 6, 0, 3, 4, 0, 2, 1, 6, 6, 5, 4, 4, 3, 7, 5, 5, 8, 9, 0, 0, 4, 5, 6, 3, 2, 8, 8, 2, 2, 5, 0, 5, 4, 5, 2, 5, 5, 6, 4, 0, 5, 6, 4, 4, 8, 2, 4, 6, 5, 1, 5, 1,
+ 8, 7, 5, 4, 7, 1, 1, 9, 6, 2, 1, 8, 4, 4, 3, 9, 6, 5, 8, 2, 5, 3, 3, 7, 5, 4, 3, 8, 8, 5, 6, 9, 0, 9, 4, 1, 1, 3, 0, 3, 1, 5, 0, 9, 5, 2, 6, 1, 7, 9, 3, 7, 8, 0, 0, 2, 9, 7, 4, 1, 2, 0, 7, 6, 6, 5, 1, 4, 7, 9,
+ 3, 9, 4, 2, 5, 9, 0, 2, 9, 8, 9, 6, 9, 5, 9, 4, 6, 9, 9, 5, 5, 6, 5, 7, 6, 1, 2, 1, 8, 6, 5, 6, 1, 9, 6, 7, 3, 3, 7, 8, 6, 2, 3, 6, 2, 5, 6, 1, 2, 5, 2, 1, 6, 3, 2, 0, 8, 6, 2, 8, 6, 9, 2, 2, 2, 1, 0, 3, 2, 7,
+ 4, 8, 8, 9, 2, 1, 8, 6, 5, 4, 3, 6, 4, 8, 0, 2, 2, 9, 6, 7, 8, 0, 7, 0, 5, 7, 6, 5, 6, 1, 5, 1, 4, 4, 6, 3, 2, 0, 4, 6, 9, 2, 7, 9, 0, 6, 8, 2, 1, 2, 0, 7, 3, 8, 8, 3, 7, 7, 8, 1, 4, 2, 3, 3, 5, 6, 2, 8, 2, 3,
+ 6, 0, 8, 9, 6, 3, 2, 0, 8, 0, 6, 8, 2, 2, 2, 4, 6, 8, 0, 1, 2, 2, 4, 8, 2, 6, 1, 1, 7, 7, 1, 8, 5, 8, 9, 6, 3, 8, 1, 4, 0, 9, 1, 8, 3, 9, 0, 3, 6, 7, 3, 6, 7, 2, 2, 2, 0, 8, 8, 8, 3, 2, 1, 5, 1, 3, 7, 5, 5, 6,
+ 0, 0, 3, 7, 2, 7, 9, 8, 3, 9, 4, 0, 0, 4, 1, 5, 2, 9, 7, 0, 0, 2, 8, 7, 8, 3, 0, 7, 6, 6, 7, 0, 9, 4, 4, 4, 7, 4, 5, 6, 0, 1, 3, 4, 5, 5, 6, 4, 1, 7, 2, 5, 4, 3, 7, 0, 9, 0, 6, 9, 7, 9, 3, 9, 6, 1, 2, 2, 5, 7,
+ 1, 4, 2, 9, 8, 9, 4, 6, 7, 1, 5, 4, 3, 5, 7, 8, 4, 6, 8, 7, 8, 8, 6, 1, 4, 4, 4, 5, 8, 1, 2, 3, 1, 4, 5, 9, 3, 5, 7, 1, 9, 8, 4, 9, 2, 2, 5, 2, 8, 4, 7, 1, 6, 0, 5, 0, 4, 9, 2, 2, 1, 2, 4, 2, 4, 7, 0, 1, 4, 1,
+ 2, 1, 4, 7, 8, 0, 5, 7, 3, 4, 5, 5, 1, 0, 5, 0, 0, 8, 0, 1, 9, 0, 8, 6, 9, 9, 6, 0, 3, 3, 0, 2, 7, 6, 3, 4, 7, 8, 7, 0, 8, 1, 0, 8, 1, 7, 5, 4, 5, 0, 1, 1, 9, 3, 0, 7, 1, 4, 1, 2, 2, 3, 3, 9, 0, 8, 6, 6, 3, 9,
+ 3, 8, 3, 3, 9, 5, 2, 9, 4, 2, 5, 7, 8, 6, 9, 0, 5, 0, 7, 6, 4, 3, 1, 0, 0, 6, 3, 8, 3, 5, 1, 9, 8, 3, 4, 3, 8, 9, 3, 4, 1, 5, 9, 6, 1, 3, 1, 8, 5, 4, 3, 4, 7, 5, 4, 6, 4, 9, 5, 5, 6, 9, 7, 8, 1, 0, 3, 8, 2, 9,
+ 3, 0, 9, 7, 1, 6, 4, 6, 5, 1, 4, 3, 8, 4, 0, 7, 0, 0, 7, 0, 7, 3, 6, 0, 4, 1, 1, 2, 3, 7, 3, 5, 9, 9, 8, 4, 3, 4, 5, 2, 2, 5, 1, 6, 1, 0, 5, 0, 7, 0, 2, 7, 0, 5, 6, 2, 3, 5, 2, 6, 6, 0, 1, 2, 7, 6, 4, 8, 4, 8,
+ 3, 0, 8, 4, 0, 7, 6, 1, 1, 8, 3, 0, 1, 3, 0, 5, 2, 7, 9, 3, 2, 0, 5, 4, 2, 7, 4, 6, 2, 8, 6, 5, 4, 0, 3, 6, 0, 3, 6, 7, 4, 5, 3, 2, 8, 6, 5, 1, 0, 5, 7, 0, 6, 5, 8, 7, 4, 8, 8, 2, 2, 5, 6, 9, 8, 1, 5, 7, 9, 3,
+ 6, 7, 8, 9, 7, 6, 6, 9, 7, 4, 2, 2, 0, 5, 7, 5, 0, 5, 9, 6, 8, 3, 4, 4, 0, 8, 6, 9, 7, 3, 5, 0, 2, 0, 1, 4, 1, 0, 2, 0, 6, 7, 2, 3, 5, 8, 5, 0, 2, 0, 0, 7, 2, 4, 5, 2, 2, 5, 6, 3, 2, 6, 5, 1, 3, 4, 1, 0, 5, 5,
+ 9, 2, 4, 0, 1, 9, 0, 2, 7, 4, 2, 1, 6, 2, 4, 8, 4, 3, 9, 1, 4, 0, 3, 5, 9, 9, 8, 9, 5, 3, 5, 3, 9, 4, 5, 9, 0, 9, 4, 4, 0, 7, 0, 4, 6, 9, 1, 2, 0, 9, 1, 4, 0, 9, 3, 8, 7, 0, 0, 1, 2, 6, 4, 5, 6, 0, 0, 1, 6, 2,
+ 3, 7, 4, 2, 8, 8, 0, 2, 1, 0, 9, 2, 7, 6, 4, 5, 7, 9, 3, 1, 0, 6, 5, 7, 9, 2, 2, 9, 5, 5, 2, 4, 9, 8, 8, 7, 2, 7, 5, 8, 4, 6, 1, 0, 1, 2, 6, 4, 8, 3, 6, 9, 9, 9, 8, 9, 2, 2, 5, 6, 9, 5, 9, 6, 8, 8, 1, 5, 9, 2,
+ 0, 5, 6, 0, 0, 1, 0, 1, 6, 5, 5, 2, 5, 6, 3, 7, 5, 6, 7, 8
+ };
+}
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
new file mode 100644
index 0000000..b7c2d1f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import junit.framework.TestCase;
+
+public class MessageQueueTest extends TestCase {
+
+ private static class BaseTestHandler extends TestHandlerThread {
+ Handler mHandler;
+ int mLastMessage;
+ int mCount;
+
+ public BaseTestHandler() {
+ }
+
+ public void go() {
+ mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ BaseTestHandler.this.handleMessage(msg);
+ }
+ };
+ }
+
+ public void handleMessage(Message msg) {
+ if (mCount <= mLastMessage) {
+ if (msg.what != mCount) {
+ failure(new RuntimeException(
+ "Expected message #" + mCount
+ + ", received #" + msg.what));
+ } else if (mCount == mLastMessage) {
+ success();
+ }
+ mCount++;
+ } else {
+ failure(new RuntimeException(
+ "Message received after done, #" + msg.what));
+ }
+ }
+ }
+
+ @MediumTest
+ public void testMessageOrder() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ public void go() {
+ super.go();
+ long now = SystemClock.uptimeMillis() + 200;
+ mLastMessage = 4;
+ mCount = 0;
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(2), now + 1);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now + 2);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(4), now + 2);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(0), now + 0);
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(1), now + 0);
+ }
+ };
+
+ tester.doTest(1000);
+ }
+
+ @MediumTest
+ public void testAtFrontOfQueue() throws Exception {
+ TestHandlerThread tester = new BaseTestHandler() {
+ public void go() {
+ super.go();
+ long now = SystemClock.uptimeMillis() + 200;
+ mLastMessage = 3;
+ mCount = 0;
+ mHandler.sendMessageAtTime(mHandler.obtainMessage(3), now);
+ mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(2));
+ mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(0));
+ }
+
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ if (msg.what == 0) {
+ mHandler.sendMessageAtFrontOfQueue(mHandler.obtainMessage(1));
+ }
+ }
+ };
+
+ tester.doTest(1000);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/os/MessengerService.java b/core/tests/coretests/src/android/os/MessengerService.java
new file mode 100644
index 0000000..f15e134
--- /dev/null
+++ b/core/tests/coretests/src/android/os/MessengerService.java
@@ -0,0 +1,50 @@
+/*
+ * 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.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+
+public class MessengerService extends Service {
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Message reply = Message.obtain();
+ reply.copyFrom(msg);
+ try {
+ msg.replyTo.send(reply);
+ } catch (RemoteException e) {
+ }
+ }
+ };
+
+ private final Messenger mMessenger = new Messenger(mHandler);
+
+ public MessengerService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+}
+
diff --git a/core/tests/coretests/src/android/os/MessengerTest.java b/core/tests/coretests/src/android/os/MessengerTest.java
new file mode 100644
index 0000000..473ffe2
--- /dev/null
+++ b/core/tests/coretests/src/android/os/MessengerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+public class MessengerTest extends AndroidTestCase {
+ private Messenger mServiceMessenger;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (MessengerTest.this) {
+ mServiceMessenger = new Messenger(service);
+ MessengerTest.this.notifyAll();
+ }
+ }
+ public void onServiceDisconnected(ComponentName name) {
+ mServiceMessenger = null;
+ }
+ };
+
+ private class TestThread extends TestHandlerThread {
+ private Handler mTestHandler;
+ private Messenger mTestMessenger;
+
+ public void go() {
+ synchronized (MessengerTest.this) {
+ mTestHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ TestThread.this.handleMessage(msg);
+ }
+ };
+ mTestMessenger = new Messenger(mTestHandler);
+ TestThread.this.executeTest();
+ }
+ }
+
+ public void executeTest() {
+ Message msg = Message.obtain();
+ msg.arg1 = 100;
+ msg.arg2 = 1000;
+ msg.replyTo = mTestMessenger;
+ try {
+ mServiceMessenger.send(msg);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void handleMessage(Message msg) {
+ if (msg.arg1 != 100) {
+ failure(new RuntimeException(
+ "Message.arg1 is not 100: " + msg.arg1));
+ return;
+ }
+ if (msg.arg2 != 1000) {
+ failure(new RuntimeException(
+ "Message.arg2 is not 1000: " + msg.arg2));
+ return;
+ }
+ if (!mTestMessenger.equals(msg.replyTo)) {
+ failure(new RuntimeException(
+ "Message.replyTo is not me: " + msg.replyTo));
+ return;
+ }
+ success();
+ }
+ };
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getContext().bindService(new Intent(mContext, MessengerService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
+ synchronized (this) {
+ while (mServiceMessenger == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ getContext().unbindService(mConnection);
+ }
+
+ @MediumTest
+ public void testSend() {
+ (new TestThread()).doTest(1000);
+
+ }
+}
diff --git a/core/java/android/os/Hardware.java b/core/tests/coretests/src/android/os/OsTests.java
index efc5617..582bf1a 100644
--- a/core/java/android/os/Hardware.java
+++ b/core/tests/coretests/src/android/os/OsTests.java
@@ -16,34 +16,24 @@
package android.os;
-/**
- * {@hide}
- */
-public class Hardware
-{
+import com.google.android.collect.Lists;
+import junit.framework.TestSuite;
+import java.util.Enumeration;
+import java.util.List;
- /* ********************************************************************************
- *
- *
- *
- *
- *
- *
- *
- *
- * Don't add anything else to this class. Add it to HardwareService instead.
- *
- *
- *
- *
- *
- *
- *
- * ********************************************************************************/
+public class OsTests {
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite(OsTests.class.getName());
+ suite.addTestSuite(AidlTest.class);
+ suite.addTestSuite(BroadcasterTest.class);
+ suite.addTestSuite(FileObserverTest.class);
+ suite.addTestSuite(IdleHandlerTest.class);
+ suite.addTestSuite(MessageQueueTest.class);
+ suite.addTestSuite(MessengerTest.class);
+ suite.addTestSuite(SystemPropertiesTest.class);
- public static native boolean getFlashlightEnabled();
- public static native void setFlashlightEnabled(boolean on);
- public static native void enableCameraFlash(int milliseconds);
+ return suite;
+ }
}
diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
new file mode 100644
index 0000000..a382239
--- /dev/null
+++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.PerformanceCollector;
+import android.os.Process;
+import android.os.PerformanceCollector.PerformanceResultsWriter;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class PerformanceCollectorTest extends TestCase {
+
+ private PerformanceCollector mPerfCollector;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPerfCollector = new PerformanceCollector();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mPerfCollector = null;
+ }
+
+ @SmallTest
+ public void testBeginSnapshotNoWriter() throws Exception {
+ mPerfCollector.beginSnapshot("testBeginSnapshotNoWriter");
+
+ assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0);
+ assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0);
+ Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector);
+ assertNotNull(snapshot);
+ assertEquals(2, snapshot.size());
+ }
+
+ @SmallTest
+ public void testEndSnapshotNoWriter() throws Exception {
+ mPerfCollector.beginSnapshot("testEndSnapshotNoWriter");
+ workForRandomLongPeriod();
+ Bundle snapshot = mPerfCollector.endSnapshot();
+
+ verifySnapshotBundle(snapshot);
+ }
+
+ @SmallTest
+ public void testStartTimingNoWriter() throws Exception {
+ mPerfCollector.startTiming("testStartTimingNoWriter");
+
+ assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0);
+ assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0);
+ Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector);
+ assertNotNull(measurement);
+ verifyTimingBundle(measurement, new ArrayList<String>());
+ }
+
+ @SmallTest
+ public void testAddIterationNoWriter() throws Exception {
+ mPerfCollector.startTiming("testAddIterationNoWriter");
+ workForRandomTinyPeriod();
+ Bundle iteration = mPerfCollector.addIteration("timing1");
+
+ verifyIterationBundle(iteration, "timing1");
+ }
+
+ @SmallTest
+ public void testStopTimingNoWriter() throws Exception {
+ mPerfCollector.startTiming("testStopTimingNoWriter");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("timing2");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("timing3");
+ workForRandomShortPeriod();
+ Bundle timing = mPerfCollector.stopTiming("timing4");
+
+ ArrayList<String> labels = new ArrayList<String>();
+ labels.add("timing2");
+ labels.add("timing3");
+ labels.add("timing4");
+ verifyTimingBundle(timing, labels);
+ }
+
+ @SmallTest
+ public void testBeginSnapshot() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.beginSnapshot("testBeginSnapshot");
+
+ assertEquals("testBeginSnapshot", writer.snapshotLabel);
+ assertTrue((Long)readPrivateField("mSnapshotCpuTime", mPerfCollector) > 0);
+ assertTrue((Long)readPrivateField("mSnapshotExecTime", mPerfCollector) > 0);
+ Bundle snapshot = (Bundle)readPrivateField("mPerfSnapshot", mPerfCollector);
+ assertNotNull(snapshot);
+ assertEquals(2, snapshot.size());
+ }
+
+ @SmallTest
+ public void testEndSnapshot() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.beginSnapshot("testEndSnapshot");
+ workForRandomLongPeriod();
+ Bundle snapshot1 = mPerfCollector.endSnapshot();
+ Bundle snapshot2 = writer.snapshotResults;
+
+ assertEqualsBundle(snapshot1, snapshot2);
+ verifySnapshotBundle(snapshot1);
+ }
+
+ @SmallTest
+ public void testStartTiming() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.startTiming("testStartTiming");
+
+ assertEquals("testStartTiming", writer.timingLabel);
+ assertTrue((Long)readPrivateField("mCpuTime", mPerfCollector) > 0);
+ assertTrue((Long)readPrivateField("mExecTime", mPerfCollector) > 0);
+ Bundle measurement = (Bundle)readPrivateField("mPerfMeasurement", mPerfCollector);
+ assertNotNull(measurement);
+ verifyTimingBundle(measurement, new ArrayList<String>());
+ }
+
+ @SmallTest
+ public void testAddIteration() throws Exception {
+ mPerfCollector.startTiming("testAddIteration");
+ workForRandomTinyPeriod();
+ Bundle iteration = mPerfCollector.addIteration("timing5");
+
+ verifyIterationBundle(iteration, "timing5");
+ }
+
+ @SmallTest
+ public void testStopTiming() throws Exception {
+ mPerfCollector.startTiming("testStopTiming");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("timing6");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("timing7");
+ workForRandomShortPeriod();
+ Bundle timing = mPerfCollector.stopTiming("timing8");
+
+ ArrayList<String> labels = new ArrayList<String>();
+ labels.add("timing6");
+ labels.add("timing7");
+ labels.add("timing8");
+ verifyTimingBundle(timing, labels);
+ }
+
+ @SmallTest
+ public void testAddMeasurementLong() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.startTiming("testAddMeasurementLong");
+ mPerfCollector.addMeasurement("testAddMeasurementLongZero", 0);
+ mPerfCollector.addMeasurement("testAddMeasurementLongPos", 348573);
+ mPerfCollector.addMeasurement("testAddMeasurementLongNeg", -19354);
+ mPerfCollector.stopTiming("");
+
+ assertEquals("testAddMeasurementLong", writer.timingLabel);
+ Bundle results = writer.timingResults;
+ assertEquals(4, results.size());
+ assertTrue(results.containsKey("testAddMeasurementLongZero"));
+ assertEquals(0, results.getLong("testAddMeasurementLongZero"));
+ assertTrue(results.containsKey("testAddMeasurementLongPos"));
+ assertEquals(348573, results.getLong("testAddMeasurementLongPos"));
+ assertTrue(results.containsKey("testAddMeasurementLongNeg"));
+ assertEquals(-19354, results.getLong("testAddMeasurementLongNeg"));
+ }
+
+ @SmallTest
+ public void testAddMeasurementFloat() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.startTiming("testAddMeasurementFloat");
+ mPerfCollector.addMeasurement("testAddMeasurementFloatZero", 0.0f);
+ mPerfCollector.addMeasurement("testAddMeasurementFloatPos", 348573.345f);
+ mPerfCollector.addMeasurement("testAddMeasurementFloatNeg", -19354.093f);
+ mPerfCollector.stopTiming("");
+
+ assertEquals("testAddMeasurementFloat", writer.timingLabel);
+ Bundle results = writer.timingResults;
+ assertEquals(4, results.size());
+ assertTrue(results.containsKey("testAddMeasurementFloatZero"));
+ assertEquals(0.0f, results.getFloat("testAddMeasurementFloatZero"));
+ assertTrue(results.containsKey("testAddMeasurementFloatPos"));
+ assertEquals(348573.345f, results.getFloat("testAddMeasurementFloatPos"));
+ assertTrue(results.containsKey("testAddMeasurementFloatNeg"));
+ assertEquals(-19354.093f, results.getFloat("testAddMeasurementFloatNeg"));
+ }
+
+ @SmallTest
+ public void testAddMeasurementString() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.startTiming("testAddMeasurementString");
+ mPerfCollector.addMeasurement("testAddMeasurementStringNull", null);
+ mPerfCollector.addMeasurement("testAddMeasurementStringEmpty", "");
+ mPerfCollector.addMeasurement("testAddMeasurementStringNonEmpty", "Hello World");
+ mPerfCollector.stopTiming("");
+
+ assertEquals("testAddMeasurementString", writer.timingLabel);
+ Bundle results = writer.timingResults;
+ assertEquals(4, results.size());
+ assertTrue(results.containsKey("testAddMeasurementStringNull"));
+ assertNull(results.getString("testAddMeasurementStringNull"));
+ assertTrue(results.containsKey("testAddMeasurementStringEmpty"));
+ assertEquals("", results.getString("testAddMeasurementStringEmpty"));
+ assertTrue(results.containsKey("testAddMeasurementStringNonEmpty"));
+ assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty"));
+ }
+
+ @SmallTest
+ public void testSimpleSequence() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.beginSnapshot("testSimpleSequence");
+ mPerfCollector.startTiming("testSimpleSequenceTiming");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration1");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration2");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration3");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration4");
+ workForRandomShortPeriod();
+ Bundle timing = mPerfCollector.stopTiming("iteration5");
+ workForRandomLongPeriod();
+ Bundle snapshot1 = mPerfCollector.endSnapshot();
+ Bundle snapshot2 = writer.snapshotResults;
+
+ assertEqualsBundle(snapshot1, snapshot2);
+ verifySnapshotBundle(snapshot1);
+
+ ArrayList<String> labels = new ArrayList<String>();
+ labels.add("iteration1");
+ labels.add("iteration2");
+ labels.add("iteration3");
+ labels.add("iteration4");
+ labels.add("iteration5");
+ verifyTimingBundle(timing, labels);
+ }
+
+ @SmallTest
+ public void testLongSequence() throws Exception {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.beginSnapshot("testLongSequence");
+ mPerfCollector.startTiming("testLongSequenceTiming1");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration1");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration2");
+ workForRandomShortPeriod();
+ Bundle timing1 = mPerfCollector.stopTiming("iteration3");
+ workForRandomLongPeriod();
+
+ mPerfCollector.startTiming("testLongSequenceTiming2");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration4");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration5");
+ workForRandomShortPeriod();
+ Bundle timing2 = mPerfCollector.stopTiming("iteration6");
+ workForRandomLongPeriod();
+
+ mPerfCollector.startTiming("testLongSequenceTiming3");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration7");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration8");
+ workForRandomShortPeriod();
+ Bundle timing3 = mPerfCollector.stopTiming("iteration9");
+ workForRandomLongPeriod();
+
+ mPerfCollector.startTiming("testLongSequenceTiming4");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration10");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration11");
+ workForRandomShortPeriod();
+ Bundle timing4 = mPerfCollector.stopTiming("iteration12");
+ workForRandomLongPeriod();
+
+ mPerfCollector.startTiming("testLongSequenceTiming5");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration13");
+ workForRandomTinyPeriod();
+ mPerfCollector.addIteration("iteration14");
+ workForRandomShortPeriod();
+ Bundle timing5 = mPerfCollector.stopTiming("iteration15");
+ workForRandomLongPeriod();
+ Bundle snapshot1 = mPerfCollector.endSnapshot();
+ Bundle snapshot2 = writer.snapshotResults;
+
+ assertEqualsBundle(snapshot1, snapshot2);
+ verifySnapshotBundle(snapshot1);
+
+ ArrayList<String> labels1 = new ArrayList<String>();
+ labels1.add("iteration1");
+ labels1.add("iteration2");
+ labels1.add("iteration3");
+ verifyTimingBundle(timing1, labels1);
+ ArrayList<String> labels2 = new ArrayList<String>();
+ labels2.add("iteration4");
+ labels2.add("iteration5");
+ labels2.add("iteration6");
+ verifyTimingBundle(timing2, labels2);
+ ArrayList<String> labels3 = new ArrayList<String>();
+ labels3.add("iteration7");
+ labels3.add("iteration8");
+ labels3.add("iteration9");
+ verifyTimingBundle(timing3, labels3);
+ ArrayList<String> labels4 = new ArrayList<String>();
+ labels4.add("iteration10");
+ labels4.add("iteration11");
+ labels4.add("iteration12");
+ verifyTimingBundle(timing4, labels4);
+ ArrayList<String> labels5 = new ArrayList<String>();
+ labels5.add("iteration13");
+ labels5.add("iteration14");
+ labels5.add("iteration15");
+ verifyTimingBundle(timing5, labels5);
+ }
+
+ /*
+ * Verify that snapshotting and timing do not interfere w/ each other,
+ * by staggering calls to snapshot and timing functions.
+ */
+ @SmallTest
+ public void testOutOfOrderSequence() {
+ MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter();
+ mPerfCollector.setPerformanceResultsWriter(writer);
+ mPerfCollector.startTiming("testOutOfOrderSequenceTiming");
+ workForRandomShortPeriod();
+ mPerfCollector.beginSnapshot("testOutOfOrderSequenceSnapshot");
+ workForRandomShortPeriod();
+ Bundle timing1 = mPerfCollector.stopTiming("timing1");
+ workForRandomShortPeriod();
+ Bundle snapshot1 = mPerfCollector.endSnapshot();
+
+ Bundle timing2 = writer.timingResults;
+ Bundle snapshot2 = writer.snapshotResults;
+
+ assertEqualsBundle(snapshot1, snapshot2);
+ verifySnapshotBundle(snapshot1);
+
+ assertEqualsBundle(timing1, timing2);
+ ArrayList<String> labels = new ArrayList<String>();
+ labels.add("timing1");
+ verifyTimingBundle(timing1, labels);
+ }
+
+ private void workForRandomPeriod(int minDuration, int maxDuration) {
+ Random random = new Random();
+ int period = minDuration + random.nextInt(maxDuration - minDuration);
+ long start = Process.getElapsedCpuTime();
+ // Generate positive amount of work, so cpu time is measurable in
+ // milliseconds
+ while (Process.getElapsedCpuTime() - start < period) {
+ for (int i = 0, temp = 0; i < 50; i++ ) {
+ temp += i;
+ }
+ }
+ }
+
+ private void workForRandomTinyPeriod() {
+ workForRandomPeriod(2, 5);
+ }
+
+ private void workForRandomShortPeriod() {
+ workForRandomPeriod(10, 25);
+ }
+
+ private void workForRandomLongPeriod() {
+ workForRandomPeriod(50, 100);
+ }
+
+ private void verifySnapshotBundle(Bundle snapshot) {
+ assertTrue("At least 26 metrics collected", 26 <= snapshot.size());
+
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0);
+
+ assertTrue(snapshot.containsKey(
+ PerformanceCollector.METRIC_KEY_PRE_RECEIVED_TRANSACTIONS));
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_PRE_SENT_TRANSACTIONS));
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_RECEIVED_TRANSACTIONS));
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_SENT_TRANSACTIONS));
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GC_INVOCATION_COUNT));
+
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_ALLOCATED) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_FREE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_FREE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PRIVATE_DIRTY) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_PSS));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_PSS) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SHARED_DIRTY) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_JAVA_SIZE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_JAVA_SIZE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_ALLOCATED) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_FREE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_FREE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PRIVATE_DIRTY) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_PSS));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_PSS) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SHARED_DIRTY) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_NATIVE_SIZE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_NATIVE_SIZE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_COUNT) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_ALLOC_SIZE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_COUNT) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_GLOBAL_FREED_SIZE) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PRIVATE_DIRTY) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_PSS));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_PSS) > 0);
+ assertTrue(snapshot.containsKey(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY));
+ assertTrue(snapshot.getLong(PerformanceCollector.METRIC_KEY_OTHER_SHARED_DIRTY) > 0);
+ }
+
+ private void verifyIterationBundle(Bundle iteration, String label) {
+ assertEquals(3, iteration.size());
+ assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_LABEL));
+ assertEquals(label, iteration.getString(PerformanceCollector.METRIC_KEY_LABEL));
+ assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_CPU_TIME));
+ assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_CPU_TIME) > 0);
+ assertTrue(iteration.containsKey(PerformanceCollector.METRIC_KEY_EXECUTION_TIME));
+ assertTrue(iteration.getLong(PerformanceCollector.METRIC_KEY_EXECUTION_TIME) > 0);
+ }
+
+ private void verifyTimingBundle(Bundle timing, ArrayList<String> labels) {
+ assertEquals(1, timing.size());
+ assertTrue(timing.containsKey(PerformanceCollector.METRIC_KEY_ITERATIONS));
+ ArrayList<Parcelable> iterations = timing.getParcelableArrayList(
+ PerformanceCollector.METRIC_KEY_ITERATIONS);
+ assertNotNull(iterations);
+ assertEquals(labels.size(), iterations.size());
+ for (int i = 0; i < labels.size(); i ++) {
+ Bundle iteration = (Bundle)iterations.get(i);
+ verifyIterationBundle(iteration, labels.get(i));
+ }
+ }
+
+ private void assertEqualsBundle(Bundle b1, Bundle b2) {
+ assertEquals(b1.keySet(), b2.keySet());
+ for (String key : b1.keySet()) {
+ assertEquals(b1.get(key), b2.get(key));
+ }
+ }
+
+ private Object readPrivateField(String fieldName, Object object) throws Exception {
+ Field f = object.getClass().getDeclaredField(fieldName);
+ f.setAccessible(true);
+ return f.get(object);
+ }
+
+ private class MockPerformanceResultsWriter implements PerformanceResultsWriter {
+
+ public String snapshotLabel;
+ public Bundle snapshotResults = new Bundle();
+ public String timingLabel;
+ public Bundle timingResults = new Bundle();
+
+ public void writeBeginSnapshot(String label) {
+ snapshotLabel = label;
+ }
+
+ public void writeEndSnapshot(Bundle results) {
+ snapshotResults.putAll(results);
+ }
+
+ public void writeStartTiming(String label) {
+ timingLabel = label;
+ }
+
+ public void writeStopTiming(Bundle results) {
+ timingResults.putAll(results);
+ }
+
+ public void writeMeasurement(String label, long value) {
+ timingResults.putLong(label, value);
+ }
+
+ public void writeMeasurement(String label, float value) {
+ timingResults.putFloat(label, value);
+ }
+
+ public void writeMeasurement(String label, String value) {
+ timingResults.putString(label, value);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
new file mode 100644
index 0000000..e089b3e
--- /dev/null
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+public class PowerManagerTest extends AndroidTestCase {
+
+ private PowerManager mPm;
+
+ /**
+ * Setup any common data for the upcoming tests.
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ }
+
+ /**
+ * Confirm that the setup is good.
+ *
+ * @throws Exception
+ */
+ @MediumTest
+ public void testPreconditions() throws Exception {
+ assertNotNull(mPm);
+ }
+
+ /**
+ * Confirm that we can create functional wakelocks.
+ *
+ * @throws Exception
+ */
+ @MediumTest
+ public void testNewWakeLock() throws Exception {
+ PowerManager.WakeLock wl = mPm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "FULL_WAKE_LOCK");
+ doTestWakeLock(wl);
+
+ wl = mPm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "SCREEN_BRIGHT_WAKE_LOCK");
+ doTestWakeLock(wl);
+
+ wl = mPm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "SCREEN_DIM_WAKE_LOCK");
+ doTestWakeLock(wl);
+
+ wl = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PARTIAL_WAKE_LOCK");
+ doTestWakeLock(wl);
+
+ doTestSetBacklightBrightness();
+
+ // TODO: Some sort of functional test (maybe not in the unit test here?)
+ // that confirms that things are really happening e.g. screen power, keyboard power.
+}
+
+ /**
+ * Confirm that we can't create dysfunctional wakelocks.
+ *
+ * @throws Exception
+ */
+ @MediumTest
+ public void testBadNewWakeLock() throws Exception {
+
+ final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ | PowerManager.SCREEN_DIM_WAKE_LOCK;
+ // wrap in try because we want the error here
+ try {
+ PowerManager.WakeLock wl = mPm.newWakeLock(badFlags, "foo");
+ } catch (IllegalArgumentException e) {
+ return;
+ }
+ fail("Bad WakeLock flag was not caught.");
+ }
+
+ /**
+ * Apply a few tests to a wakelock to make sure it's healthy.
+ *
+ * @param wl The wakelock to be tested.
+ */
+ private void doTestWakeLock(PowerManager.WakeLock wl) {
+ // First try simple acquire/release
+ wl.acquire();
+ assertTrue(wl.isHeld());
+ wl.release();
+ assertFalse(wl.isHeld());
+
+ // Try ref-counted acquire/release
+ wl.setReferenceCounted(true);
+ wl.acquire();
+ assertTrue(wl.isHeld());
+ wl.acquire();
+ assertTrue(wl.isHeld());
+ wl.release();
+ assertTrue(wl.isHeld());
+ wl.release();
+ assertFalse(wl.isHeld());
+
+ // Try non-ref-counted
+ wl.setReferenceCounted(false);
+ wl.acquire();
+ assertTrue(wl.isHeld());
+ wl.acquire();
+ assertTrue(wl.isHeld());
+ wl.release();
+ assertFalse(wl.isHeld());
+
+ // TODO: Threaded test (needs handler) to make sure timed wakelocks work too
+ }
+
+
+ /**
+ * Test that calling {@link android.os.IHardwareService#setBacklights(int)} requires
+ * permissions.
+ * <p>Tests permission:
+ * {@link android.Manifest.permission#DEVICE_POWER}
+ */
+ private void doTestSetBacklightBrightness() {
+ try {
+ mPm.setBacklightBrightness(0);
+ fail("setBacklights did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/os/SystemPropertiesTest.java b/core/tests/coretests/src/android/os/SystemPropertiesTest.java
new file mode 100644
index 0000000..25868ce
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SystemPropertiesTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.os;
+
+import static junit.framework.Assert.assertEquals;
+import junit.framework.TestCase;
+
+import android.os.SystemProperties;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class SystemPropertiesTest extends TestCase {
+ private static final String KEY = "com.android.frameworks.coretests";
+ @SmallTest
+ public void testProperties() throws Exception {
+ if (false) {
+ String value;
+
+ SystemProperties.set(KEY, "");
+ value = SystemProperties.get(KEY, "default");
+ assertEquals("default", value);
+
+ SystemProperties.set(KEY, "AAA");
+ value = SystemProperties.get(KEY, "default");
+ assertEquals("AAA", value);
+
+ value = SystemProperties.get(KEY);
+ assertEquals("AAA", value);
+
+ SystemProperties.set(KEY, "");
+ value = SystemProperties.get(KEY, "default");
+ assertEquals("default", value);
+
+ value = SystemProperties.get(KEY);
+ assertEquals("", value);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/TestHandlerThread.java b/core/tests/coretests/src/android/os/TestHandlerThread.java
new file mode 100644
index 0000000..7e84af3
--- /dev/null
+++ b/core/tests/coretests/src/android/os/TestHandlerThread.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue.IdleHandler;
+
+abstract class TestHandlerThread {
+ private boolean mDone = false;
+ private boolean mSuccess = false;
+ private RuntimeException mFailure = null;
+ private Looper mLooper;
+
+ public abstract void go();
+
+ public TestHandlerThread() {
+ }
+
+ public void doTest(long timeout) {
+ (new LooperThread()).start();
+
+ synchronized (this) {
+ long now = System.currentTimeMillis();
+ long endTime = now + timeout;
+ while (!mDone && now < endTime) {
+ try {
+ wait(endTime-now);
+ }
+ catch (InterruptedException e) {
+ }
+ now = System.currentTimeMillis();
+ }
+ }
+
+ mLooper.quit();
+
+ if (!mDone) {
+ throw new RuntimeException("test timed out");
+ }
+ if (!mSuccess) {
+ throw mFailure;
+ }
+ }
+
+ public Looper getLooper() {
+ return mLooper;
+ }
+
+ public void success() {
+ synchronized (this) {
+ mSuccess = true;
+ quit();
+ }
+ }
+
+ public void failure(RuntimeException failure) {
+ synchronized (this) {
+ mSuccess = false;
+ mFailure = failure;
+ quit();
+ }
+ }
+
+ class LooperThread extends Thread {
+ public void run() {
+ Looper.prepare();
+ mLooper = Looper.myLooper();
+ go();
+ Looper.loop();
+
+ synchronized (TestHandlerThread.this) {
+ mDone = true;
+ if (!mSuccess && mFailure == null) {
+ mFailure = new RuntimeException("no failure exception set");
+ }
+ TestHandlerThread.this.notifyAll();
+ }
+ }
+
+ }
+
+ private void quit() {
+ synchronized (this) {
+ mDone = true;
+ notifyAll();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
new file mode 100644
index 0000000..7a788ee
--- /dev/null
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.os;
+
+import android.os.Debug;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+/**
+ * This class is used to test the native tracing support. Run this test
+ * while tracing on the emulator and then run traceview to view the trace.
+ */
+public class TraceTest extends AndroidTestCase
+{
+ private static final String TAG = "TraceTest";
+ private int eMethodCalls = 0;
+ private int fMethodCalls = 0;
+ private int gMethodCalls = 0;
+
+ @SmallTest
+ public void testNativeTracingFromJava()
+ {
+ long start = System.currentTimeMillis();
+ Debug.startNativeTracing();
+ //nativeMethod();
+ int count = 0;
+ for (int ii = 0; ii < 20; ii++) {
+ count = eMethod();
+ }
+ Debug.stopNativeTracing();
+ long end = System.currentTimeMillis();
+ long elapsed = end - start;
+ Log.i(TAG, "elapsed millis: " + elapsed);
+ Log.i(TAG, "eMethod calls: " + eMethodCalls
+ + " fMethod calls: " + fMethodCalls
+ + " gMethod calls: " + gMethodCalls);
+ }
+
+ // This should not run in the automated suite.
+ @Suppress
+ public void disableTestNativeTracingFromC()
+ {
+ long start = System.currentTimeMillis();
+ nativeMethodAndStartTracing();
+ long end = System.currentTimeMillis();
+ long elapsed = end - start;
+ Log.i(TAG, "elapsed millis: " + elapsed);
+ }
+
+ native void nativeMethod();
+ native void nativeMethodAndStartTracing();
+
+ @LargeTest
+ public void testMethodTracing()
+ {
+ long start = System.currentTimeMillis();
+ Debug.startMethodTracing("traceTest");
+ topMethod();
+ Debug.stopMethodTracing();
+ long end = System.currentTimeMillis();
+ long elapsed = end - start;
+ Log.i(TAG, "elapsed millis: " + elapsed);
+ }
+
+ private void topMethod() {
+ aMethod();
+ bMethod();
+ cMethod();
+ dMethod(5);
+
+ Thread t1 = new aThread();
+ t1.start();
+ Thread t2 = new aThread();
+ t2.start();
+ Thread t3 = new aThread();
+ t3.start();
+ try {
+ t1.join();
+ t2.join();
+ t3.join();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private class aThread extends Thread {
+ @Override
+ public void run() {
+ aMethod();
+ bMethod();
+ cMethod();
+ }
+ }
+
+ /** Calls other methods to make some interesting trace data.
+ *
+ * @return a meaningless value
+ */
+ private int aMethod() {
+ int count = 0;
+ for (int ii = 0; ii < 6; ii++) {
+ count += bMethod();
+ }
+ for (int ii = 0; ii < 5; ii++) {
+ count += cMethod();
+ }
+ for (int ii = 0; ii < 4; ii++) {
+ count += dMethod(ii);
+ }
+ return count;
+ }
+
+ /** Calls another method to make some interesting trace data.
+ *
+ * @return a meaningless value
+ */
+ private int bMethod() {
+ int count = 0;
+ for (int ii = 0; ii < 4; ii++) {
+ count += cMethod();
+ }
+ return count;
+ }
+
+ /** Executes a simple loop to make some interesting trace data.
+ *
+ * @return a meaningless value
+ */
+ private int cMethod() {
+ int count = 0;
+ for (int ii = 0; ii < 1000; ii++) {
+ count += ii;
+ }
+ return count;
+ }
+
+ /** Calls itself recursively to make some interesting trace data.
+ *
+ * @return a meaningless value
+ */
+ private int dMethod(int level) {
+ int count = 0;
+ if (level > 0) {
+ count = dMethod(level - 1);
+ }
+ for (int ii = 0; ii < 100; ii++) {
+ count += ii;
+ }
+ if (level == 0) {
+ return count;
+ }
+ return dMethod(level - 1);
+ }
+
+ public int eMethod() {
+ eMethodCalls += 1;
+ int count = fMethod();
+ count += gMethod(3);
+ return count;
+ }
+
+ public int fMethod() {
+ fMethodCalls += 1;
+ int count = 0;
+ for (int ii = 0; ii < 10; ii++) {
+ count += ii;
+ }
+ return count;
+ }
+
+ public int gMethod(int level) {
+ gMethodCalls += 1;
+ int count = level;
+ if (level > 1)
+ count += gMethod(level - 1);
+ return count;
+ }
+
+ /*
+ * This causes the native shared library to be loaded when the
+ * class is first used. The library is only loaded once, even if
+ * multiple classes include this line.
+ *
+ * The library must be in java.library.path, which is derived from
+ * LD_LIBRARY_PATH. The actual library name searched for will be
+ * "libtrace_test.so" under Linux, but may be different on other
+ * platforms.
+ */
+ static {
+ Log.i(TAG, "Loading trace_test native library...");
+ try {
+ System.loadLibrary("trace_test");
+ Log.i(TAG, "Successfully loaded trace_test native library");
+ }
+ catch (UnsatisfiedLinkError ule) {
+ Log.w(TAG, "Could not load trace_test native library");
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/storage/AsecTests.java b/core/tests/coretests/src/android/os/storage/AsecTests.java
new file mode 100755
index 0000000..dda3010
--- /dev/null
+++ b/core/tests/coretests/src/android/os/storage/AsecTests.java
@@ -0,0 +1,612 @@
+/*
+ * 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.os.storage;
+
+import android.content.Context;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+import junit.framework.Assert;
+
+public class AsecTests extends AndroidTestCase {
+ private static final boolean localLOGV = true;
+ public static final String TAG="AsecTests";
+
+ void failStr(String errMsg) {
+ Log.w(TAG, "errMsg="+errMsg);
+ }
+
+ void failStr(Exception e) {
+ Log.w(TAG, "e.getMessage="+e.getMessage());
+ Log.w(TAG, "e="+e);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
+ cleanupContainers();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
+ cleanupContainers();
+ }
+
+ private void cleanupContainers() throws RemoteException {
+ IMountService ms = getMs();
+ String[] containers = ms.getSecureContainerList();
+
+ for (int i = 0; i < containers.length; i++) {
+ if (containers[i].startsWith("com.android.unittests.AsecTests.")) {
+ ms.destroySecureContainer(containers[i], true);
+ }
+ }
+ }
+
+ private boolean containerExists(String localId) throws RemoteException {
+ IMountService ms = getMs();
+ String[] containers = ms.getSecureContainerList();
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ for (int i = 0; i < containers.length; i++) {
+ if (containers[i].equals(fullId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int createContainer(String localId, int size, String key) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.createSecureContainer(fullId, size, "fat", key, android.os.Process.myUid());
+ }
+
+ private int mountContainer(String localId, String key) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.mountSecureContainer(fullId, key, android.os.Process.myUid());
+ }
+
+ private int renameContainer(String localId1, String localId2) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId1 = "com.android.unittests.AsecTests." + localId1;
+ String fullId2 = "com.android.unittests.AsecTests." + localId2;
+
+ IMountService ms = getMs();
+ return ms.renameSecureContainer(fullId1, fullId2);
+ }
+
+ private int unmountContainer(String localId, boolean force) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.unmountSecureContainer(fullId, force);
+ }
+
+ private int destroyContainer(String localId, boolean force) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.destroySecureContainer(fullId, force);
+ }
+
+ private boolean isContainerMounted(String localId) throws RemoteException {
+ Assert.assertTrue(isMediaMounted());
+ String fullId = "com.android.unittests.AsecTests." + localId;
+
+ IMountService ms = getMs();
+ return ms.isSecureContainerMounted(fullId);
+ }
+
+ private IMountService getMs() {
+ IBinder service = ServiceManager.getService("mount");
+ if (service != null) {
+ return IMountService.Stub.asInterface(service);
+ } else {
+ Log.e(TAG, "Can't get mount service");
+ }
+ return null;
+ }
+
+ private boolean isMediaMounted() {
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ String state = getMs().getVolumeState(mPath);
+ return Environment.MEDIA_MOUNTED.equals(state);
+ } catch (RemoteException e) {
+ failStr(e);
+ return false;
+ }
+ }
+
+ public void testCreateContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateContainer", 4, "none"));
+ Assert.assertEquals(true, containerExists("testCreateContainer"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateMinSizeContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateContainer", 1, "none"));
+ Assert.assertEquals(true, containerExists("testCreateContainer"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateZeroSizeContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ createContainer("testCreateZeroContainer", 0, "none"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testCreateDuplicateContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testCreateDupContainer", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ createContainer("testCreateDupContainer", 4, "none"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testDestroyContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testDestroyContainer", 4, "none"));
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ destroyContainer("testDestroyContainer", false));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testMountContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testMountContainer", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testMountContainer", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ mountContainer("testMountContainer", "none"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testMountBadKey() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testMountBadKey", 4, "00000000000000000000000000000000"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testMountBadKey", false));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ mountContainer("testMountContainer", "000000000000000000000000000000001"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedInternalError,
+ mountContainer("testMountContainer", "none"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testNonExistPath() {
+ IMountService ms = getMs();
+ try {
+ String path = ms.getSecureContainerPath("jparks.broke.it");
+ failStr(path);
+ } catch (IllegalArgumentException e) {
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testUnmountBusyContainer() {
+ IMountService ms = getMs();
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testUnmountBusyContainer", 4, "none"));
+
+ String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testUnmountBusyContainer");
+
+ File f = new File(path, "reference");
+ FileOutputStream fos = new FileOutputStream(f);
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
+ unmountContainer("testUnmountBusyContainer", false));
+
+ fos.close();
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testUnmountBusyContainer", false));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testDestroyBusyContainer() {
+ IMountService ms = getMs();
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testDestroyBusyContainer", 4, "none"));
+
+ String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testDestroyBusyContainer");
+
+ File f = new File(path, "reference");
+ FileOutputStream fos = new FileOutputStream(f);
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageBusy,
+ destroyContainer("testDestroyBusyContainer", false));
+
+ fos.close();
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ destroyContainer("testDestroyBusyContainer", false));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testRenameContainer.1", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+
+ Assert.assertEquals(false, containerExists("testRenameContainer.1"));
+ Assert.assertEquals(true, containerExists("testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameSrcMountedContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testRenameDstMountedContainer() {
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.1", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ unmountContainer("testRenameContainer.1", false));
+
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testRenameContainer.2", 4, "none"));
+
+ Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted,
+ renameContainer("testRenameContainer.1", "testRenameContainer.2"));
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ public void testContainerSize() {
+ IMountService ms = getMs();
+ try {
+ Assert.assertEquals(StorageResultCode.OperationSucceeded,
+ createContainer("testContainerSize", 1, "none"));
+ String path = ms.getSecureContainerPath("com.android.unittests.AsecTests.testUnmountBusyContainer");
+
+ byte[] buf = new byte[4096];
+ File f = new File(path, "reference");
+ FileOutputStream fos = new FileOutputStream(f);
+ for (int i = 0; i < (1024 * 1024); i+= buf.length) {
+ fos.write(buf);
+ }
+ fos.close();
+ } catch (Exception e) {
+ failStr(e);
+ }
+ }
+
+ /*------------ Tests for unmounting volume ---*/
+ public final long MAX_WAIT_TIME=120*1000;
+ public final long WAIT_TIME_INCR=20*1000;
+ boolean getMediaState() {
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ String state = getMs().getVolumeState(mPath);
+ return Environment.MEDIA_MOUNTED.equals(state);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ boolean mountMedia() {
+ if (getMediaState()) {
+ return true;
+ }
+ try {
+ String mPath = Environment.getExternalStorageDirectory().toString();
+ int ret = getMs().mountVolume(mPath);
+ return ret == StorageResultCode.OperationSucceeded;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ class StorageListener extends StorageEventListener {
+ String oldState;
+ String newState;
+ String path;
+ private boolean doneFlag = false;
+
+ public void action() {
+ synchronized (this) {
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
+ this.oldState = oldState;
+ this.newState = newState;
+ this.path = path;
+ action();
+ }
+ }
+
+ private boolean unmountMedia() {
+ if (!getMediaState()) {
+ return true;
+ }
+ String path = Environment.getExternalStorageDirectory().toString();
+ StorageListener observer = new StorageListener();
+ StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+ sm.registerListener(observer);
+ try {
+ // Wait on observer
+ synchronized(observer) {
+ getMs().unmountVolume(path, false);
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ throw new Exception("Timed out waiting for packageInstalled callback");
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ return false;
+ } finally {
+ sm.unregisterListener(observer);
+ }
+ }
+ public void testUnmount() {
+ boolean oldStatus = getMediaState();
+ Log.i(TAG, "oldStatus="+oldStatus);
+ try {
+ // Mount media firsts
+ if (!getMediaState()) {
+ mountMedia();
+ }
+ assertTrue(unmountMedia());
+ } finally {
+ // Restore old status
+ boolean currStatus = getMediaState();
+ if (oldStatus != currStatus) {
+ if (oldStatus) {
+ // Mount media
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+ }
+
+ class MultipleStorageLis extends StorageListener {
+ int count = 0;
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ count++;
+ super.action();
+ }
+ }
+ /*
+ * This test invokes unmount multiple time and expects the call back
+ * to be invoked just once.
+ */
+ public void testUnmountMultiple() {
+ boolean oldStatus = getMediaState();
+ StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
+ MultipleStorageLis observer = new MultipleStorageLis();
+ try {
+ // Mount media firsts
+ if (!getMediaState()) {
+ mountMedia();
+ }
+ String path = Environment.getExternalStorageDirectory().toString();
+ sm.registerListener(observer);
+ // Wait on observer
+ synchronized(observer) {
+ for (int i = 0; i < 5; i++) {
+ getMs().unmountVolume(path, false);
+ }
+ long waitTime = 0;
+ while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
+ observer.wait(WAIT_TIME_INCR);
+ waitTime += WAIT_TIME_INCR;
+ }
+ if(!observer.isDone()) {
+ failStr("Timed out waiting for packageInstalled callback");
+ }
+ }
+ assertEquals(observer.count, 1);
+ } catch (Exception e) {
+ failStr(e);
+ } finally {
+ sm.unregisterListener(observer);
+ // Restore old status
+ boolean currStatus = getMediaState();
+ if (oldStatus != currStatus) {
+ if (oldStatus) {
+ // Mount media
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+ }
+
+ class ShutdownObserver extends IMountShutdownObserver.Stub{
+ private boolean doneFlag = false;
+ int statusCode;
+
+ public void action() {
+ synchronized (this) {
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+ public void onShutDownComplete(int statusCode) throws RemoteException {
+ this.statusCode = statusCode;
+ action();
+ }
+
+ }
+
+ boolean invokeShutdown() {
+ IMountService ms = getMs();
+ ShutdownObserver observer = new ShutdownObserver();
+ synchronized (observer) {
+ try {
+ ms.shutdown(observer);
+ return true;
+ } catch (RemoteException e) {
+ failStr(e);
+ }
+ }
+ return false;
+ }
+
+ public void testShutdown() {
+ boolean oldStatus = getMediaState();
+ try {
+ // Mount media firsts
+ if (!getMediaState()) {
+ mountMedia();
+ }
+ assertTrue(invokeShutdown());
+ } finally {
+ // Restore old status
+ boolean currStatus = getMediaState();
+ if (oldStatus != currStatus) {
+ if (oldStatus) {
+ // Mount media
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+ }
+
+ /*
+ * This test invokes unmount multiple time and expects the call back
+ * to be invoked just once.
+ */
+ public void testShutdownMultiple() {
+ boolean oldStatus = getMediaState();
+ try {
+ // Mount media firsts
+ if (!getMediaState()) {
+ mountMedia();
+ }
+ IMountService ms = getMs();
+ ShutdownObserver observer = new ShutdownObserver();
+ synchronized (observer) {
+ try {
+ ms.shutdown(observer);
+ for (int i = 0; i < 4; i++) {
+ ms.shutdown(null);
+ }
+ } catch (RemoteException e) {
+ failStr(e);
+ }
+ }
+ } finally {
+ // Restore old status
+ boolean currStatus = getMediaState();
+ if (oldStatus != currStatus) {
+ if (oldStatus) {
+ // Mount media
+ mountMedia();
+ } else {
+ unmountMedia();
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/os/storage/StorageListener.java b/core/tests/coretests/src/android/os/storage/StorageListener.java
new file mode 100644
index 0000000..d6dae22
--- /dev/null
+++ b/core/tests/coretests/src/android/os/storage/StorageListener.java
@@ -0,0 +1,45 @@
+/*
+ * 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.os.storage;
+
+import android.util.Log;
+
+public class StorageListener extends StorageEventListener {
+ private static final boolean localLOGV = true;
+
+ public static final String TAG="StorageListener";
+
+ String oldState;
+ String newState;
+ String path;
+ private boolean doneFlag = false;
+ @Override
+ public void onStorageStateChanged(String path, String oldState, String newState) {
+ if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
+ synchronized (this) {
+ this.oldState = oldState;
+ this.newState = newState;
+ this.path = path;
+ doneFlag = true;
+ notifyAll();
+ }
+ }
+
+ public boolean isDone() {
+ return doneFlag;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/RecurrenceSetTest.java b/core/tests/coretests/src/android/pim/RecurrenceSetTest.java
new file mode 100644
index 0000000..64cd6c4
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/RecurrenceSetTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.pim;
+
+import android.content.ContentValues;
+import android.pim.ICalendar;
+import android.pim.RecurrenceSet;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.provider.Calendar;
+import junit.framework.TestCase;
+
+/**
+ * Test some pim.RecurrenceSet functionality.
+ */
+public class RecurrenceSetTest extends TestCase {
+
+ // Test a recurrence
+ @SmallTest
+ public void testRecurrenceSet0() throws Exception {
+ String recurrence = "DTSTART;TZID=America/New_York:20080221T070000\n"
+ + "DTEND;TZID=America/New_York:20080221T190000\n"
+ + "RRULE:FREQ=DAILY;UNTIL=20080222T000000Z\n"
+ + "EXDATE:20080222T120000Z";
+ verifyPopulateContentValues(recurrence, "FREQ=DAILY;UNTIL=20080222T000000Z", null,
+ null, "20080222T120000Z", 1203595200000L, "America/New_York", "P43200S", 0);
+ }
+
+ // Test 1 day all-day event
+ @SmallTest
+ public void testRecurrenceSet1() throws Exception {
+ String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090822\n"
+ + "RRULE:FREQ=YEARLY;WKST=SU";
+ verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
+ null, null, 1250812800000L, null, "P1D", 1);
+ }
+
+ // Test 2 day all-day event
+ @SmallTest
+ public void testRecurrenceSet2() throws Exception {
+ String recurrence = "DTSTART;VALUE=DATE:20090821\nDTEND;VALUE=DATE:20090823\n"
+ + "RRULE:FREQ=YEARLY;WKST=SU";
+ verifyPopulateContentValues(recurrence, "FREQ=YEARLY;WKST=SU", null,
+ null, null, 1250812800000L, null, "P2D", 1);
+ }
+
+ // run populateContentValues and verify the results
+ private void verifyPopulateContentValues(String recurrence, String rrule, String rdate,
+ String exrule, String exdate, long dtstart, String tzid, String duration, int allDay)
+ throws ICalendar.FormatException {
+ ICalendar.Component recurrenceComponent =
+ new ICalendar.Component("DUMMY", null /* parent */);
+ ICalendar.parseComponent(recurrenceComponent, recurrence);
+ ContentValues values = new ContentValues();
+ RecurrenceSet.populateContentValues(recurrenceComponent, values);
+ Log.d("KS", "values " + values);
+
+ assertEquals(rrule, values.get(android.provider.Calendar.Events.RRULE));
+ assertEquals(rdate, values.get(android.provider.Calendar.Events.RDATE));
+ assertEquals(exrule, values.get(android.provider.Calendar.Events.EXRULE));
+ assertEquals(exdate, values.get(android.provider.Calendar.Events.EXDATE));
+ assertEquals(dtstart, (long) values.getAsLong(Calendar.Events.DTSTART));
+ assertEquals(tzid, values.get(android.provider.Calendar.Events.EVENT_TIMEZONE));
+ assertEquals(duration, values.get(android.provider.Calendar.Events.DURATION));
+ assertEquals(allDay,
+ (int) values.getAsInteger(android.provider.Calendar.Events.ALL_DAY));
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
new file mode 100644
index 0000000..b3c0773
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+
+/**
+ * ContentValues-like class which enables users to chain put() methods and restricts
+ * the other methods.
+ */
+/* package */ class ContentValuesBuilder {
+ private final ContentValues mContentValues;
+
+ public ContentValuesBuilder(final ContentValues contentValues) {
+ mContentValues = contentValues;
+ }
+
+ public ContentValuesBuilder put(String key, String value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Byte value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Short value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Integer value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Long value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Float value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Double value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, Boolean value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder put(String key, byte[] value) {
+ mContentValues.put(key, value);
+ return this;
+ }
+
+ public ContentValuesBuilder putNull(String key) {
+ mContentValues.putNull(key);
+ return this;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
new file mode 100644
index 0000000..b9e9875
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
@@ -0,0 +1,101 @@
+/*
+ * 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.pim.vcard;
+
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntry;
+import android.pim.vcard.VCardEntryConstructor;
+import android.pim.vcard.VCardEntryHandler;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/* package */ class ContentValuesVerifier implements VCardEntryHandler {
+ private AndroidTestCase mTestCase;
+ private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
+ new ArrayList<ContentValuesVerifierElem>();
+ private int mIndex;
+
+ public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
+ mTestCase = androidTestCase;
+ ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase);
+ mContentValuesVerifierElemList.add(importVerifier);
+ return importVerifier;
+ }
+
+ public void verify(int resId, int vCardType) throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(int resId, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId),
+ vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30(true); // use StrictParsing
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ verify(is, vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ VCardEntryConstructor builder =
+ new VCardEntryConstructor(null, null, false, vCardType, null);
+ builder.addEntryHandler(this);
+ try {
+ vCardParser.parse(is, builder);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void onStart() {
+ for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
+ elem.onParsingStart();
+ }
+ }
+
+ public void onEntryCreated(VCardEntry entry) {
+ mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
+ mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
+ mIndex++;
+ }
+
+ public void onEnd() {
+ for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
+ elem.onParsingEnd();
+ elem.verifyResolver();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
new file mode 100644
index 0000000..2edbb36
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
@@ -0,0 +1,95 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntry;
+import android.pim.vcard.VCardEntryCommitter;
+import android.pim.vcard.VCardEntryConstructor;
+import android.pim.vcard.VCardEntryHandler;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.provider.ContactsContract.Data;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/* package */ class ContentValuesVerifierElem {
+ private final AndroidTestCase mTestCase;
+ private final ImportTestResolver mResolver;
+ private final VCardEntryHandler mHandler;
+
+ public ContentValuesVerifierElem(AndroidTestCase androidTestCase) {
+ mTestCase = androidTestCase;
+ mResolver = new ImportTestResolver(androidTestCase);
+ mHandler = new VCardEntryCommitter(mResolver);
+ }
+
+ public ContentValuesBuilder addExpected(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mResolver.addExpectedContentValues(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public void verify(int resId, int vCardType)
+ throws IOException, VCardException {
+ verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30(true); // use StrictParsing
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ VCardEntryConstructor builder =
+ new VCardEntryConstructor(null, null, false, vCardType, null);
+ builder.addEntryHandler(mHandler);
+ try {
+ vCardParser.parse(is, builder);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ verifyResolver();
+ }
+
+ public void verifyResolver() {
+ mResolver.verify();
+ }
+
+ public void onParsingStart() {
+ mHandler.onStart();
+ }
+
+ public void onEntryCreated(VCardEntry entry) {
+ mHandler.onEntryCreated(entry);
+ }
+
+ public void onParsingEnd() {
+ mHandler.onEnd();
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
new file mode 100644
index 0000000..5968e83
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
@@ -0,0 +1,216 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockCursor;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/* package */ public class ExportTestResolver extends MockContentResolver {
+ ExportTestProvider mProvider;
+ public ExportTestResolver(TestCase testCase) {
+ mProvider = new ExportTestProvider(testCase);
+ addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
+ addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
+ }
+
+ public ContactEntry addInputContactEntry() {
+ return mProvider.buildInputEntry();
+ }
+}
+
+/* package */ class MockEntityIterator implements EntityIterator {
+ List<Entity> mEntityList;
+ Iterator<Entity> mIterator;
+
+ public MockEntityIterator(List<ContentValues> contentValuesList) {
+ mEntityList = new ArrayList<Entity>();
+ Entity entity = new Entity(new ContentValues());
+ for (ContentValues contentValues : contentValuesList) {
+ entity.addSubValue(Data.CONTENT_URI, contentValues);
+ }
+ mEntityList.add(entity);
+ mIterator = mEntityList.iterator();
+ }
+
+ public boolean hasNext() {
+ return mIterator.hasNext();
+ }
+
+ public Entity next() {
+ return mIterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove not supported");
+ }
+
+ public void reset() {
+ mIterator = mEntityList.iterator();
+ }
+
+ public void close() {
+ }
+}
+
+/**
+ * Represents one contact, which should contain multiple ContentValues like
+ * StructuredName, Email, etc.
+ */
+/* package */ class ContactEntry {
+ private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
+
+ public ContentValuesBuilder addContentValues(String mimeType) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Data.MIMETYPE, mimeType);
+ mContentValuesList.add(contentValues);
+ return new ContentValuesBuilder(contentValues);
+ }
+
+ public List<ContentValues> getList() {
+ return mContentValuesList;
+ }
+}
+
+/* package */ class ExportTestProvider extends MockContentProvider {
+ final private TestCase mTestCase;
+ final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+
+ public ExportTestProvider(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ public ContactEntry buildInputEntry() {
+ ContactEntry contactEntry = new ContactEntry();
+ mContactEntryList.add(contactEntry);
+ return contactEntry;
+ }
+
+ /**
+ * <p>
+ * An old method which had existed but was removed from ContentResolver.
+ * </p>
+ * <p>
+ * We still keep using this method since we don't have a propeer way to know
+ * which value in the ContentValue corresponds to the entry in Contacts database.
+ * </p>
+ * <p>
+ * Detail:
+ * There's an easy way to know which index "family name" corresponds to, via
+ * {@link android.provider.ContactsContract}.
+ * FAMILY_NAME equals DATA3, so the corresponding index
+ * for "family name" should be 2 (note that index is 0-origin).
+ * However, we cannot know what the index 2 corresponds to; it may be "family name",
+ * "label" for now, but may be the other some column in the future. We don't have
+ * convenient way to know the original data structure.
+ * </p>
+ */
+ public EntityIterator queryEntities(Uri uri,
+ String selection, String[] selectionArgs, String sortOrder) {
+ mTestCase.assertTrue(uri != null);
+ mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+ final String authority = uri.getAuthority();
+ mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+ mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
+ mTestCase.assertEquals(1, selectionArgs.length);
+ final int id = Integer.parseInt(selectionArgs[0]);
+ mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
+
+ return new MockEntityIterator(mContactEntryList.get(id).getList());
+ }
+
+ @Override
+ public Cursor query(Uri uri,String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+ // In this test, following arguments are not supported.
+ mTestCase.assertNull(selection);
+ mTestCase.assertNull(selectionArgs);
+ mTestCase.assertNull(sortOrder);
+
+ return new MockCursor() {
+ int mCurrentPosition = -1;
+
+ @Override
+ public int getCount() {
+ return mContactEntryList.size();
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ mCurrentPosition = 0;
+ return true;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ if (mCurrentPosition < mContactEntryList.size()) {
+ mCurrentPosition++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ return mCurrentPosition < 0;
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return mCurrentPosition >= mContactEntryList.size();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ mTestCase.assertEquals(Contacts._ID, columnName);
+ return 0;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ mTestCase.assertEquals(0, columnIndex);
+ mTestCase.assertTrue(mCurrentPosition >= 0
+ && mCurrentPosition < mContactEntryList.size());
+ return mCurrentPosition;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return String.valueOf(getInt(columnIndex));
+ }
+
+ @Override
+ public void close() {
+ }
+ };
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
new file mode 100644
index 0000000..c3f6f79
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
@@ -0,0 +1,299 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.test.mock.MockContentResolver;
+import android.text.TextUtils;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+/* package */ class ImportTestResolver extends MockContentResolver {
+ final ImportTestProvider mProvider;
+
+ public ImportTestResolver(TestCase testCase) {
+ mProvider = new ImportTestProvider(testCase);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(String authority,
+ ArrayList<ContentProviderOperation> operations) {
+ equalsString(authority, RawContacts.CONTENT_URI.toString());
+ return mProvider.applyBatch(operations);
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ mProvider.addExpectedContentValues(expectedContentValues);
+ }
+
+ public void verify() {
+ mProvider.verify();
+ }
+
+ private static boolean equalsString(String a, String b) {
+ if (a == null || a.length() == 0) {
+ return b == null || b.length() == 0;
+ } else {
+ return a.equals(b);
+ }
+ }
+}
+
+/* package */ class ImportTestProvider extends MockContentProvider {
+ private static final Set<String> sKnownMimeTypeSet =
+ new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
+ Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
+ Email.CONTENT_ITEM_TYPE, StructuredPostal.CONTENT_ITEM_TYPE,
+ Im.CONTENT_ITEM_TYPE, Organization.CONTENT_ITEM_TYPE,
+ Event.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE,
+ Note.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE,
+ Relation.CONTENT_ITEM_TYPE, Event.CONTENT_ITEM_TYPE,
+ GroupMembership.CONTENT_ITEM_TYPE));
+
+ final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
+
+ private final TestCase mTestCase;
+
+ public ImportTestProvider(TestCase testCase) {
+ mTestCase = testCase;
+ mMimeTypeToExpectedContentValues =
+ new HashMap<String, Collection<ContentValues>>();
+ for (String acceptanbleMimeType : sKnownMimeTypeSet) {
+ // Do not use HashSet since the current implementation changes the content of
+ // ContentValues after the insertion, which make the result of hashCode()
+ // changes...
+ mMimeTypeToExpectedContentValues.put(
+ acceptanbleMimeType, new ArrayList<ContentValues>());
+ }
+ }
+
+ public void addExpectedContentValues(ContentValues expectedContentValues) {
+ final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
+ if (!sKnownMimeTypeSet.contains(mimeType)) {
+ mTestCase.fail(String.format(
+ "Unknow MimeType %s in the test code. Test code should be broken.",
+ mimeType));
+ }
+
+ final Collection<ContentValues> contentValuesCollection =
+ mMimeTypeToExpectedContentValues.get(mimeType);
+ contentValuesCollection.add(expectedContentValues);
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(
+ ArrayList<ContentProviderOperation> operations) {
+ if (operations == null) {
+ mTestCase.fail("There is no operation.");
+ }
+
+ final int size = operations.size();
+ ContentProviderResult[] fakeResultArray = new ContentProviderResult[size];
+ for (int i = 0; i < size; i++) {
+ Uri uri = Uri.withAppendedPath(RawContacts.CONTENT_URI, String.valueOf(i));
+ fakeResultArray[i] = new ContentProviderResult(uri);
+ }
+
+ for (int i = 0; i < size; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ ContentValues contentValues = operation.resolveValueBackReferences(
+ fakeResultArray, i);
+ }
+ for (int i = 0; i < size; i++) {
+ ContentProviderOperation operation = operations.get(i);
+ ContentValues actualContentValues = operation.resolveValueBackReferences(
+ fakeResultArray, i);
+ final Uri uri = operation.getUri();
+ if (uri.equals(RawContacts.CONTENT_URI)) {
+ mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
+ mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
+ } else if (uri.equals(Data.CONTENT_URI)) {
+ final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
+ if (!sKnownMimeTypeSet.contains(mimeType)) {
+ mTestCase.fail(String.format(
+ "Unknown MimeType %s. Probably added after developing this test",
+ mimeType));
+ }
+ // Remove data meaningless in this unit tests.
+ // Specifically, Data.DATA1 - DATA7 are set to null or empty String
+ // regardless of the input, but it may change depending on how
+ // resolver-related code handles it.
+ // Here, we ignore these implementation-dependent specs and
+ // just check whether vCard importer correctly inserts rellevent data.
+ Set<String> keyToBeRemoved = new HashSet<String>();
+ for (Entry<String, Object> entry : actualContentValues.valueSet()) {
+ Object value = entry.getValue();
+ if (value == null || TextUtils.isEmpty(value.toString())) {
+ keyToBeRemoved.add(entry.getKey());
+ }
+ }
+ for (String key: keyToBeRemoved) {
+ actualContentValues.remove(key);
+ }
+ /* for testing
+ Log.d("@@@",
+ String.format("MimeType: %s, data: %s",
+ mimeType, actualContentValues.toString())); */
+ // Remove RAW_CONTACT_ID entry just for safety, since we do not care
+ // how resolver-related code handles the entry in this unit test,
+ if (actualContentValues.containsKey(Data.RAW_CONTACT_ID)) {
+ actualContentValues.remove(Data.RAW_CONTACT_ID);
+ }
+ final Collection<ContentValues> contentValuesCollection =
+ mMimeTypeToExpectedContentValues.get(mimeType);
+ if (contentValuesCollection.isEmpty()) {
+ mTestCase.fail("ContentValues for MimeType " + mimeType
+ + " is not expected at all (" + actualContentValues + ")");
+ }
+ boolean checked = false;
+ for (ContentValues expectedContentValues : contentValuesCollection) {
+ /*for testing
+ Log.d("@@@", "expected: "
+ + convertToEasilyReadableString(expectedContentValues));
+ Log.d("@@@", "actual : "
+ + convertToEasilyReadableString(actualContentValues));*/
+ if (equalsForContentValues(expectedContentValues,
+ actualContentValues)) {
+ mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
+ checked = true;
+ break;
+ }
+ }
+ if (!checked) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Unexpected: ");
+ builder.append(convertToEasilyReadableString(actualContentValues));
+ builder.append("\nExpected: ");
+ for (ContentValues expectedContentValues : contentValuesCollection) {
+ builder.append(convertToEasilyReadableString(expectedContentValues));
+ }
+ mTestCase.fail(builder.toString());
+ }
+ } else {
+ mTestCase.fail("Unexpected Uri has come: " + uri);
+ }
+ } // for (int i = 0; i < size; i++) {
+ return fakeResultArray;
+ }
+
+ public void verify() {
+ StringBuilder builder = new StringBuilder();
+ for (Collection<ContentValues> contentValuesCollection :
+ mMimeTypeToExpectedContentValues.values()) {
+ for (ContentValues expectedContentValues: contentValuesCollection) {
+ builder.append(convertToEasilyReadableString(expectedContentValues));
+ builder.append("\n");
+ }
+ }
+ if (builder.length() > 0) {
+ final String failMsg =
+ "There is(are) remaining expected ContentValues instance(s): \n"
+ + builder.toString();
+ mTestCase.fail(failMsg);
+ }
+ }
+
+ /**
+ * Utility method to print ContentValues whose content is printed with sorted keys.
+ */
+ private String convertToEasilyReadableString(ContentValues contentValues) {
+ if (contentValues == null) {
+ return "null";
+ }
+ String mimeTypeValue = "";
+ SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+ for (Entry<String, Object> entry : contentValues.valueSet()) {
+ final String key = entry.getKey();
+ final Object value = entry.getValue();
+ final String valueString = (value != null ? value.toString() : null);
+ if (Data.MIMETYPE.equals(key)) {
+ mimeTypeValue = valueString;
+ } else {
+ mTestCase.assertNotNull(key);
+ sortedMap.put(key, valueString);
+ }
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(Data.MIMETYPE);
+ builder.append('=');
+ builder.append(mimeTypeValue);
+ for (Entry<String, String> entry : sortedMap.entrySet()) {
+ final String key = entry.getKey();
+ final String value = entry.getValue();
+ builder.append(' ');
+ builder.append(key);
+ builder.append("=\"");
+ builder.append(value);
+ builder.append('"');
+ }
+ return builder.toString();
+ }
+
+ private static boolean equalsForContentValues(
+ ContentValues expected, ContentValues actual) {
+ if (expected == actual) {
+ return true;
+ } else if (expected == null || actual == null || expected.size() != actual.size()) {
+ return false;
+ }
+
+ for (Entry<String, Object> entry : expected.valueSet()) {
+ final String key = entry.getKey();
+ final Object value = entry.getValue();
+ if (!actual.containsKey(key)) {
+ return false;
+ }
+ if (value instanceof byte[]) {
+ Object actualValue = actual.get(key);
+ if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
+ return false;
+ }
+ } else if (!value.equals(actual.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
new file mode 100644
index 0000000..cef15fd
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
@@ -0,0 +1,65 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.Context;
+import android.pim.vcard.VCardComposer;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+
+class LineVerifier implements VCardComposer.OneEntryHandler {
+ private final TestCase mTestCase;
+ private final ArrayList<LineVerifierElem> mLineVerifierElemList;
+ private int mVCardType;
+ private int index;
+
+ public LineVerifier(TestCase testCase, int vcardType) {
+ mTestCase = testCase;
+ mLineVerifierElemList = new ArrayList<LineVerifierElem>();
+ mVCardType = vcardType;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType);
+ mLineVerifierElemList.add(lineVerifier);
+ return lineVerifier;
+ }
+
+ public void verify(String vcard) {
+ if (index >= mLineVerifierElemList.size()) {
+ mTestCase.fail("Insufficient number of LineVerifier (" + index + ")");
+ }
+
+ LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
+ lineVerifier.verify(vcard);
+
+ index++;
+ }
+
+ public boolean onEntryCreated(String vcard) {
+ verify(vcard);
+ return true;
+ }
+
+ public boolean onInit(Context context) {
+ return true;
+ }
+
+ public void onTerminate() {
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
new file mode 100644
index 0000000..b23b29b
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
@@ -0,0 +1,105 @@
+/*
+ * 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.pim.vcard;
+
+import android.pim.vcard.VCardConfig;
+import android.text.TextUtils;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class LineVerifierElem {
+ private final TestCase mTestCase;
+ private final List<String> mExpectedLineList = new ArrayList<String>();
+ private final boolean mIsV30;
+
+ public LineVerifierElem(TestCase testCase, int vcardType) {
+ mTestCase = testCase;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ }
+
+ public LineVerifierElem addExpected(final String line) {
+ if (!TextUtils.isEmpty(line)) {
+ mExpectedLineList.add(line);
+ }
+ return this;
+ }
+
+ public void verify(final String vcard) {
+ final String[] lineArray = vcard.split("\\r?\\n");
+ final int length = lineArray.length;
+ boolean beginExists = false;
+ boolean endExists = false;
+ boolean versionExists = false;
+
+ for (int i = 0; i < length; i++) {
+ final String line = lineArray[i];
+ if (TextUtils.isEmpty(line)) {
+ continue;
+ }
+
+ if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
+ if (beginExists) {
+ mTestCase.fail("Multiple \"BEGIN:VCARD\" line found");
+ } else {
+ beginExists = true;
+ continue;
+ }
+ } else if ("END:VCARD".equalsIgnoreCase(line)) {
+ if (endExists) {
+ mTestCase.fail("Multiple \"END:VCARD\" line found");
+ } else {
+ endExists = true;
+ continue;
+ }
+ } else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) {
+ if (versionExists) {
+ mTestCase.fail("Multiple VERSION line + found");
+ } else {
+ versionExists = true;
+ continue;
+ }
+ }
+
+ if (!beginExists) {
+ mTestCase.fail("Property other than BEGIN came before BEGIN property: "
+ + line);
+ } else if (endExists) {
+ mTestCase.fail("Property other than END came after END property: "
+ + line);
+ }
+
+ final int index = mExpectedLineList.indexOf(line);
+ if (index >= 0) {
+ mExpectedLineList.remove(index);
+ } else {
+ mTestCase.fail("Unexpected line: " + line);
+ }
+ }
+
+ if (!mExpectedLineList.isEmpty()) {
+ StringBuffer buffer = new StringBuffer();
+ for (String expectedLine : mExpectedLineList) {
+ buffer.append(expectedLine);
+ buffer.append("\n");
+ }
+
+ mTestCase.fail("Expected line(s) not found:" + buffer.toString());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
new file mode 100644
index 0000000..2c1f6d2
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
@@ -0,0 +1,198 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardEntry;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Previously used in main vCard handling code but now exists only for testing.
+ *
+ * Especially useful for testing parser code (VCardParser), since all properties can be
+ * checked via this class unlike {@link VCardEntry}, which only emits the result of
+ * interpretation of the content of each vCard. We cannot know whether vCard parser or
+ * ContactStruct is wrong withouth this class.
+ */
+public class PropertyNode {
+ public String propName;
+ public String propValue;
+ public List<String> propValue_vector;
+
+ /** Store value as byte[],after decode.
+ * Used when propValue is encoded by something like BASE64, QUOTED-PRINTABLE, etc.
+ */
+ public byte[] propValue_bytes;
+
+ /** param store: key=paramType, value=paramValue
+ * Note that currently PropertyNode class does not support multiple param-values
+ * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
+ * one String value like "A,B", not ["A", "B"]...
+ * TODO: fix this.
+ */
+ public ContentValues paramMap;
+
+ /** Only for TYPE=??? param store. */
+ public Set<String> paramMap_TYPE;
+
+ /** Store group values. Used only in VCard. */
+ public Set<String> propGroupSet;
+
+ public PropertyNode() {
+ propName = "";
+ propValue = "";
+ propValue_vector = new ArrayList<String>();
+ paramMap = new ContentValues();
+ paramMap_TYPE = new HashSet<String>();
+ propGroupSet = new HashSet<String>();
+ }
+
+ public PropertyNode(
+ String propName, String propValue, List<String> propValue_vector,
+ byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
+ Set<String> propGroupSet) {
+ if (propName != null) {
+ this.propName = propName;
+ } else {
+ this.propName = "";
+ }
+ if (propValue != null) {
+ this.propValue = propValue;
+ } else {
+ this.propValue = "";
+ }
+ if (propValue_vector != null) {
+ this.propValue_vector = propValue_vector;
+ } else {
+ this.propValue_vector = new ArrayList<String>();
+ }
+ this.propValue_bytes = propValue_bytes;
+ if (paramMap != null) {
+ this.paramMap = paramMap;
+ } else {
+ this.paramMap = new ContentValues();
+ }
+ if (paramMap_TYPE != null) {
+ this.paramMap_TYPE = paramMap_TYPE;
+ } else {
+ this.paramMap_TYPE = new HashSet<String>();
+ }
+ if (propGroupSet != null) {
+ this.propGroupSet = propGroupSet;
+ } else {
+ this.propGroupSet = new HashSet<String>();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ // vCard may contain more than one same line in one entry, while HashSet or any other
+ // library which utilize hashCode() does not honor that, so intentionally throw an
+ // Exception.
+ throw new UnsupportedOperationException(
+ "PropertyNode does not provide hashCode() implementation intentionally.");
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PropertyNode)) {
+ return false;
+ }
+
+ PropertyNode node = (PropertyNode)obj;
+
+ if (propName == null || !propName.equals(node.propName)) {
+ return false;
+ } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+ return false;
+ } else if (!paramMap_TYPE.equals(node.paramMap_TYPE)) {
+ return false;
+ } else if (!propGroupSet.equals(node.propGroupSet)) {
+ return false;
+ }
+
+ if (propValue_bytes != null && Arrays.equals(propValue_bytes, node.propValue_bytes)) {
+ return true;
+ } else {
+ if (!propValue.equals(node.propValue)) {
+ return false;
+ }
+
+ // The value in propValue_vector is not decoded even if it should be
+ // decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
+ // is 1, the encoded value is stored in propValue, so we do not have to
+ // check it.
+ return (propValue_vector.equals(node.propValue_vector) ||
+ propValue_vector.size() == 1 ||
+ node.propValue_vector.size() == 1);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("propName: ");
+ builder.append(propName);
+ builder.append(", paramMap: ");
+ builder.append(paramMap.toString());
+ builder.append(", paramMap_TYPE: [");
+ boolean first = true;
+ for (String elem : paramMap_TYPE) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append('"');
+ builder.append(elem);
+ builder.append('"');
+ }
+ builder.append("]");
+ if (!propGroupSet.isEmpty()) {
+ builder.append(", propGroupSet: [");
+ first = true;
+ for (String elem : propGroupSet) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append('"');
+ builder.append(elem);
+ builder.append('"');
+ }
+ builder.append("]");
+ }
+ if (propValue_vector != null && propValue_vector.size() > 1) {
+ builder.append(", propValue_vector size: ");
+ builder.append(propValue_vector.size());
+ }
+ if (propValue_bytes != null) {
+ builder.append(", propValue_bytes size: ");
+ builder.append(propValue_bytes.length);
+ }
+ builder.append(", propValue: \"");
+ builder.append(propValue);
+ builder.append("\"");
+ return builder.toString();
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
new file mode 100644
index 0000000..cfdd074
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
@@ -0,0 +1,386 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/* package */ class PropertyNodesVerifier extends VNodeBuilder {
+ private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
+ private final AndroidTestCase mAndroidTestCase;
+ private int mIndex;
+
+ public PropertyNodesVerifier(AndroidTestCase testCase) {
+ mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
+ mAndroidTestCase = testCase;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
+ mPropertyNodesVerifierElemList.add(elem);
+ return elem;
+ }
+
+ public void verify(int resId, int vCardType)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
+ }
+
+ public void verify(int resId, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
+ vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType) throws IOException, VCardException {
+ final VCardParser vCardParser;
+ if (VCardConfig.isV30(vCardType)) {
+ vCardParser = new VCardParser_V30(true); // Use StrictParsing.
+ } else {
+ vCardParser = new VCardParser_V21();
+ }
+ verify(is, vCardType, vCardParser);
+ }
+
+ public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
+ throws IOException, VCardException {
+ try {
+ vCardParser.parse(is, this);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public void endEntry() {
+ super.endEntry();
+ mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
+ mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
+ mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
+ mIndex++;
+ }
+}
+
+/**
+ * Utility class which verifies input VNode.
+ *
+ * This class first checks whether each propertyNode in the VNode is in the
+ * "ordered expected property list".
+ * If the node does not exist in the "ordered list", the class refers to
+ * "unorderd expected property set" and checks the node is expected somewhere.
+ */
+/* package */ class PropertyNodesVerifierElem {
+ public static class TypeSet extends HashSet<String> {
+ public TypeSet(String ... array) {
+ super(Arrays.asList(array));
+ }
+ }
+
+ public static class GroupSet extends HashSet<String> {
+ public GroupSet(String ... array) {
+ super(Arrays.asList(array));
+ }
+ }
+
+ private final HashMap<String, List<PropertyNode>> mOrderedNodeMap;
+ // Intentionally use ArrayList instead of Set, assuming there may be more than one
+ // exactly same objects.
+ private final ArrayList<PropertyNode> mUnorderedNodeList;
+ private final TestCase mTestCase;
+
+ public PropertyNodesVerifierElem(TestCase testCase) {
+ mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
+ mUnorderedNodeList = new ArrayList<PropertyNode>();
+ mTestCase = testCase;
+ }
+
+ // WithOrder
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue) {
+ return addExpectedNodeWithOrder(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, String propValue, ContentValues contentValues) {
+ return addExpectedNodeWithOrder(propName, propValue, null,
+ null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, List<String> propValueList, ContentValues contentValues) {
+ return addExpectedNodeWithOrder(propName, null, propValueList,
+ null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, String propValue, List<String> propValueList) {
+ return addExpectedNodeWithOrder(propName, propValue, propValueList, null,
+ null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(
+ String propName, List<String> propValueList) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addExpectedNodeWithOrder(propName, propValue.toString(), propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, null,
+ null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, null, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ ContentValues paramMap, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, null, null,
+ paramMap, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNodeWithOrder(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ if (propValue == null && propValueList != null) {
+ propValue = concatinateListWithSemiColon(propValueList);
+ }
+ PropertyNode propertyNode = new PropertyNode(propName,
+ propValue, propValueList, propValue_bytes,
+ paramMap, paramMap_TYPE, propGroupSet);
+ List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+ if (expectedNodeList == null) {
+ expectedNodeList = new ArrayList<PropertyNode>();
+ mOrderedNodeMap.put(propName, expectedNodeList);
+ }
+ expectedNodeList.add(propertyNode);
+ return this;
+ }
+
+ // WithoutOrder
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue) {
+ return addExpectedNode(propName, propValue, null, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ ContentValues contentValues) {
+ return addExpectedNode(propName, propValue, null, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList, ContentValues contentValues) {
+ return addExpectedNode(propName, null,
+ propValueList, null, contentValues, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList) {
+ return addExpectedNode(propName, propValue, propValueList, null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList) {
+ return addExpectedNode(propName, null, propValueList,
+ null, null, null, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, null, null, null, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ final String propValue = concatinateListWithSemiColon(propValueList);
+ return addExpectedNode(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList, TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, propValueList, null, null,
+ paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ ContentValues paramMap, TypeSet paramMap_TYPE) {
+ return addExpectedNode(propName, propValue, null, null,
+ paramMap, paramMap_TYPE, null);
+ }
+
+ public PropertyNodesVerifierElem addExpectedNode(String propName, String propValue,
+ List<String> propValueList, byte[] propValue_bytes,
+ ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
+ if (propValue == null && propValueList != null) {
+ propValue = concatinateListWithSemiColon(propValueList);
+ }
+ mUnorderedNodeList.add(new PropertyNode(propName, propValue,
+ propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet));
+ return this;
+ }
+
+ public void verify(VNode vnode) {
+ for (PropertyNode actualNode : vnode.propList) {
+ verifyNode(actualNode.propName, actualNode);
+ }
+ if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
+ List<String> expectedProps = new ArrayList<String>();
+ for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
+ for (PropertyNode node : nodes) {
+ if (!expectedProps.contains(node.propName)) {
+ expectedProps.add(node.propName);
+ }
+ }
+ }
+ for (PropertyNode node : mUnorderedNodeList) {
+ if (!expectedProps.contains(node.propName)) {
+ expectedProps.add(node.propName);
+ }
+ }
+ mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ + " was not found.");
+ }
+ }
+
+ private void verifyNode(final String propName, final PropertyNode actualNode) {
+ List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
+ final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
+ if (size > 0) {
+ for (int i = 0; i < size; i++) {
+ PropertyNode expectedNode = expectedNodeList.get(i);
+ List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
+ if (expectedNode.propName.equals(propName)) {
+ if (expectedNode.equals(actualNode)) {
+ expectedNodeList.remove(i);
+ if (expectedNodeList.size() == 0) {
+ mOrderedNodeMap.remove(propName);
+ }
+ return;
+ } else {
+ expectedButDifferentValueList.add(expectedNode);
+ }
+ }
+
+ // "actualNode" is not in ordered expected list.
+ // Try looking over unordered expected list.
+ if (tryFoundExpectedNodeFromUnorderedList(actualNode,
+ expectedButDifferentValueList)) {
+ return;
+ }
+
+ if (!expectedButDifferentValueList.isEmpty()) {
+ // Same propName exists but with different value(s).
+ failWithExpectedNodeList(propName, actualNode,
+ expectedButDifferentValueList);
+ } else {
+ // There's no expected node with same propName.
+ mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ }
+ }
+ } else {
+ List<PropertyNode> expectedButDifferentValueList =
+ new ArrayList<PropertyNode>();
+ if (tryFoundExpectedNodeFromUnorderedList(actualNode, expectedButDifferentValueList)) {
+ return;
+ } else {
+ if (!expectedButDifferentValueList.isEmpty()) {
+ // Same propName exists but with different value(s).
+ failWithExpectedNodeList(propName, actualNode,
+ expectedButDifferentValueList);
+ } else {
+ // There's no expected node with same propName.
+ mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ }
+ }
+ }
+ }
+
+ private String concatinateListWithSemiColon(List<String> array) {
+ StringBuffer buffer = new StringBuffer();
+ boolean first = true;
+ for (String propValueElem : array) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(';');
+ }
+ buffer.append(propValueElem);
+ }
+
+ return buffer.toString();
+ }
+
+ private boolean tryFoundExpectedNodeFromUnorderedList(PropertyNode actualNode,
+ List<PropertyNode> expectedButDifferentValueList) {
+ final String propName = actualNode.propName;
+ int unorderedListSize = mUnorderedNodeList.size();
+ for (int i = 0; i < unorderedListSize; i++) {
+ PropertyNode unorderedExpectedNode = mUnorderedNodeList.get(i);
+ if (unorderedExpectedNode.propName.equals(propName)) {
+ if (unorderedExpectedNode.equals(actualNode)) {
+ mUnorderedNodeList.remove(i);
+ return true;
+ }
+ expectedButDifferentValueList.add(unorderedExpectedNode);
+ }
+ }
+ return false;
+ }
+
+ private void failWithExpectedNodeList(String propName, PropertyNode actualNode,
+ List<PropertyNode> expectedNodeList) {
+ StringBuilder builder = new StringBuilder();
+ for (PropertyNode expectedNode : expectedNodeList) {
+ builder.append("expected: ");
+ builder.append(expectedNode.toString());
+ builder.append("\n");
+ }
+ mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ + builder.toString()
+ + " actual: " + actualNode.toString());
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
new file mode 100644
index 0000000..004a197
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
@@ -0,0 +1,959 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+/**
+ * Tests for the code related to vCard exporter, inculding vCard composer.
+ * This test class depends on vCard importer code, so if tests for vCard importer fail,
+ * the result of this class will not be reliable.
+ */
+public class VCardExporterTests extends VCardTestsBase {
+ private static final byte[] sPhotoByteArray =
+ VCardImporterTests.sPhotoByteArrayForComplicatedCase;
+
+ public void testSimpleV21() {
+ mVerifier.initForExportTest(V21);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Roid Ando")
+ .addExpectedNode("N", "Ando;Roid;;;",
+ Arrays.asList("Ando", "Roid", "", "", ""));
+ }
+
+ private void testStructuredNameBasic(int vcardType) {
+ final boolean isV30 = VCardConfig.isV30(vcardType);
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameBasicV21() {
+ testStructuredNameBasic(V21);
+ }
+
+ public void testStructuredNameBasicV30() {
+ testStructuredNameBasic(V30);
+ }
+
+ /**
+ * Test that only "primary" StructuredName is emitted, so that our vCard file
+ * will not confuse the external importer, assuming there may be some importer
+ * which presume that there's only one property toward each of "N", "FN", etc.
+ * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
+ */
+ private void testStructuredNameUsePrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+ // With "IS_PRIMARY=1". This is what we should use.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ + "AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameUsePrimaryV21() {
+ testStructuredNameUsePrimaryCommon(V21);
+ }
+
+ public void testStructuredNameUsePrimaryV30() {
+ testStructuredNameUsePrimaryCommon(V30);
+ }
+
+ /**
+ * Tests that only "super primary" StructuredName is emitted.
+ * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
+ */
+ private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
+ final boolean isV30 = (vcardType == V30);
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+
+ // With "IS_PRIMARY=1", but we should ignore this time.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ // With "IS_SUPER_PRIMARY=1". This is what we should use.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
+ .put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
+ .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
+ .put(StructuredName.PREFIX, "AppropriatePrefix")
+ .put(StructuredName.SUFFIX, "AppropriateSuffix")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+ .put(StructuredName.IS_SUPER_PRIMARY, 1);
+
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
+ .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
+ .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
+ .put(StructuredName.PREFIX, "DoNotEmitPrefix3")
+ .put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily3")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven3")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
+ .put(StructuredName.IS_PRIMARY, 1);
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N",
+ "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ + "AppropriatePrefix;AppropriateSuffix",
+ Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
+ "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
+ .addExpectedNodeWithOrder("FN",
+ "AppropriatePrefix AppropriateGivenName "
+ + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
+ .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
+
+ if (isV30) {
+ elem.addExpectedNode("SORT-STRING",
+ "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ + " AppropriatePhoneticFamily");
+ }
+ }
+
+ public void testStructuredNameUseSuperPrimaryV21() {
+ testStructuredNameUseSuperPrimaryCommon(V21);
+ }
+
+ public void testStructuredNameUseSuperPrimaryV30() {
+ testStructuredNameUseSuperPrimaryCommon(V30);
+ }
+
+ public void testNickNameV30() {
+ mVerifier.initForExportTest(V30);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("NICKNAME", "Nicky");
+ }
+
+ private void testPhoneBasicCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "1", new TypeSet("HOME"));
+ }
+
+ public void testPhoneBasicV21() {
+ testPhoneBasicCommon(V21);
+ }
+
+ public void testPhoneBasicV30() {
+ testPhoneBasicCommon(V30);
+ }
+
+ /**
+ * Tests that vCard composer emits corresponding type param which we expect.
+ */
+ private void testPhoneVariousTypeSupport(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "10")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "20")
+ .put(Phone.TYPE, Phone.TYPE_WORK);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "30")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "40")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "50")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "60")
+ .put(Phone.TYPE, Phone.TYPE_PAGER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "70")
+ .put(Phone.TYPE, Phone.TYPE_OTHER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "80")
+ .put(Phone.TYPE, Phone.TYPE_CAR);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "90")
+ .put(Phone.TYPE, Phone.TYPE_COMPANY_MAIN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "100")
+ .put(Phone.TYPE, Phone.TYPE_ISDN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "110")
+ .put(Phone.TYPE, Phone.TYPE_MAIN);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "120")
+ .put(Phone.TYPE, Phone.TYPE_OTHER_FAX);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "130")
+ .put(Phone.TYPE, Phone.TYPE_TELEX);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "140")
+ .put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "150")
+ .put(Phone.TYPE, Phone.TYPE_WORK_PAGER);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "160")
+ .put(Phone.TYPE, Phone.TYPE_MMS);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "10", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "20", new TypeSet("WORK"))
+ .addExpectedNode("TEL", "30", new TypeSet("HOME", "FAX"))
+ .addExpectedNode("TEL", "40", new TypeSet("WORK", "FAX"))
+ .addExpectedNode("TEL", "50", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "60", new TypeSet("PAGER"))
+ .addExpectedNode("TEL", "70", new TypeSet("VOICE"))
+ .addExpectedNode("TEL", "80", new TypeSet("CAR"))
+ .addExpectedNode("TEL", "90", new TypeSet("WORK", "PREF"))
+ .addExpectedNode("TEL", "100", new TypeSet("ISDN"))
+ .addExpectedNode("TEL", "110", new TypeSet("PREF"))
+ .addExpectedNode("TEL", "120", new TypeSet("FAX"))
+ .addExpectedNode("TEL", "130", new TypeSet("TLX"))
+ .addExpectedNode("TEL", "140", new TypeSet("WORK", "CELL"))
+ .addExpectedNode("TEL", "150", new TypeSet("WORK", "PAGER"))
+ .addExpectedNode("TEL", "160", new TypeSet("MSG"));
+ }
+
+ public void testPhoneVariousTypeSupportV21() {
+ testPhoneVariousTypeSupport(V21);
+ }
+
+ public void testPhoneVariousTypeSupportV30() {
+ testPhoneVariousTypeSupport(V30);
+ }
+
+ /**
+ * Tests that "PREF"s are emitted appropriately.
+ */
+ private void testPhonePrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+ .put(Phone.IS_PRIMARY, 1);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "4")
+ .put(Phone.TYPE, Phone.TYPE_FAX_WORK);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "4", new TypeSet("WORK", "FAX"))
+ .addExpectedNode("TEL", "3", new TypeSet("HOME", "FAX", "PREF"))
+ .addExpectedNode("TEL", "2", new TypeSet("WORK", "PREF"))
+ .addExpectedNode("TEL", "1", new TypeSet("HOME"));
+ }
+
+ public void testPhonePrefHandlingV21() {
+ testPhonePrefHandlingCommon(V21);
+ }
+
+ public void testPhonePrefHandlingV30() {
+ testPhonePrefHandlingCommon(V30);
+ }
+
+ private void testMiscPhoneTypeHandling(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "Modem");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "MSG");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "BBS");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "4")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VIDEO");
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "5")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "6")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "_AUTO_CELL"); // The old indicator for the type mobile.
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "7")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "\u643A\u5E2F"); // Mobile phone in Japanese Kanji
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "8")
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "invalid");
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ elem.addExpectedNode("TEL", "1", new TypeSet("MODEM"))
+ .addExpectedNode("TEL", "2", new TypeSet("MSG"))
+ .addExpectedNode("TEL", "3", new TypeSet("BBS"))
+ .addExpectedNode("TEL", "4", new TypeSet("VIDEO"))
+ .addExpectedNode("TEL", "5", new TypeSet("VOICE"))
+ .addExpectedNode("TEL", "6", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "7", new TypeSet("CELL"))
+ .addExpectedNode("TEL", "8", new TypeSet("X-invalid"));
+ }
+
+ public void testPhoneTypeHandlingV21() {
+ testMiscPhoneTypeHandling(V21);
+ }
+
+ public void testPhoneTypeHandlingV30() {
+ testMiscPhoneTypeHandling(V30);
+ }
+
+ private void testEmailBasicCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "sample@example.com");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "sample@example.com");
+ }
+
+ public void testEmailBasicV21() {
+ testEmailBasicCommon(V21);
+ }
+
+ public void testEmailBasicV30() {
+ testEmailBasicCommon(V30);
+ }
+
+ private void testEmailVariousTypeSupportCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_work@example.com")
+ .put(Email.TYPE, Email.TYPE_WORK);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_mobile@example.com")
+ .put(Email.TYPE, Email.TYPE_MOBILE);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_other@example.com")
+ .put(Email.TYPE, Email.TYPE_OTHER);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "type_work@example.com", new TypeSet("WORK"))
+ .addExpectedNode("EMAIL", "type_mobile@example.com", new TypeSet("CELL"))
+ .addExpectedNode("EMAIL", "type_other@example.com");
+ }
+
+ public void testEmailVariousTypeSupportV21() {
+ testEmailVariousTypeSupportCommon(V21);
+ }
+
+ public void testEmailVariousTypeSupportV30() {
+ testEmailVariousTypeSupportCommon(V30);
+ }
+
+ private void testEmailPrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_home@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME)
+ .put(Email.IS_PRIMARY, 1);
+ entry.addContentValues(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "type_notype@example.com")
+ .put(Email.IS_PRIMARY, 1);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "type_notype@example.com", new TypeSet("PREF"))
+ .addExpectedNode("EMAIL", "type_home@example.com", new TypeSet("HOME", "PREF"));
+ }
+
+ public void testEmailPrefHandlingV21() {
+ testEmailPrefHandlingCommon(V21);
+ }
+
+ public void testEmailPrefHandlingV30() {
+ testEmailPrefHandlingCommon(V30);
+ }
+
+ private void testPostalAddressCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "Pobox")
+ .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood")
+ .put(StructuredPostal.STREET, "Street")
+ .put(StructuredPostal.CITY, "City")
+ .put(StructuredPostal.REGION, "Region")
+ .put(StructuredPostal.POSTCODE, "100")
+ .put(StructuredPostal.COUNTRY, "Country")
+ .put(StructuredPostal.FORMATTED_ADDRESS, "Formatted Address")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK);
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal Code,
+ // ; Country Name
+ //
+ // The NEIGHBORHOOD field is appended after the CITY field.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("Pobox", "", "Street", "City Neighborhood",
+ "Region", "100", "Country"), new TypeSet("WORK"));
+ }
+
+ public void testPostalAddressV21() {
+ testPostalAddressCommon(V21);
+ }
+
+ public void testPostalAddressV30() {
+ testPostalAddressCommon(V30);
+ }
+
+ private void testPostalAddressNonNeighborhood(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.CITY, "City");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("", "", "", "City", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAddressNonNeighborhoodV21() {
+ testPostalAddressNonNeighborhood(V21);
+ }
+
+ public void testPostalAddressNonNeighborhoodV30() {
+ testPostalAddressNonNeighborhood(V30);
+ }
+
+ private void testPostalAddressNonCity(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.NEIGHBORHOOD, "Neighborhood");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR",
+ Arrays.asList("", "", "", "Neighborhood", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAddressNonCityV21() {
+ testPostalAddressNonCity(V21);
+ }
+
+ public void testPostalAddressNonCityV30() {
+ testPostalAddressNonCity(V30);
+ }
+
+ private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.REGION, "") // Must be ignored.
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "Formatted address CA 123-334 United Statue");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("ADR", ";Formatted address CA 123-334 United Statue;;;;;",
+ Arrays.asList("", "Formatted address CA 123-334 United Statue",
+ "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalOnlyWithFormattedAddressV21() {
+ testPostalOnlyWithFormattedAddressCommon(V21);
+ }
+
+ public void testPostalOnlyWithFormattedAddressV30() {
+ testPostalOnlyWithFormattedAddressCommon(V30);
+ }
+
+ /**
+ * Tests that the vCard composer honors formatted data when it is available
+ * even when it is partial.
+ */
+ private void testPostalWithBothStructuredAndFormattedCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "Pobox")
+ .put(StructuredPostal.COUNTRY, "Country")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "Formatted address CA 123-334 United Statue"); // Should be ignored
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("ADR", "Pobox;;;;;;Country",
+ Arrays.asList("Pobox", "", "", "", "", "", "Country"),
+ new TypeSet("HOME"));
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV21() {
+ testPostalWithBothStructuredAndFormattedCommon(V21);
+ }
+
+ public void testPostalWithBothStructuredAndFormattedV30() {
+ testPostalWithBothStructuredAndFormattedCommon(V30);
+ }
+
+ private void testOrganizationCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "CompanyX")
+ .put(Organization.DEPARTMENT, "DepartmentY")
+ .put(Organization.TITLE, "TitleZ")
+ .put(Organization.JOB_DESCRIPTION, "Description Rambda") // Ignored.
+ .put(Organization.OFFICE_LOCATION, "Mountain View") // Ignored.
+ .put(Organization.PHONETIC_NAME, "PhoneticName!") // Ignored
+ .put(Organization.SYMBOL, "(^o^)/~~"); // Ignore him (her).
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .putNull(Organization.COMPANY)
+ .put(Organization.DEPARTMENT, "DepartmentXX")
+ .putNull(Organization.TITLE);
+ entry.addContentValues(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "CompanyXYZ")
+ .putNull(Organization.DEPARTMENT)
+ .put(Organization.TITLE, "TitleXYZYX");
+ // Currently we do not use group but depend on the order.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("ORG", "CompanyX;DepartmentY",
+ Arrays.asList("CompanyX", "DepartmentY"))
+ .addExpectedNodeWithOrder("TITLE", "TitleZ")
+ .addExpectedNodeWithOrder("ORG", "DepartmentXX")
+ .addExpectedNodeWithOrder("ORG", "CompanyXYZ")
+ .addExpectedNodeWithOrder("TITLE", "TitleXYZYX");
+ }
+
+ public void testOrganizationV21() {
+ testOrganizationCommon(V21);
+ }
+
+ public void testOrganizationV30() {
+ testOrganizationCommon(V30);
+ }
+
+ private void testImVariousTypeSupportCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_MSN)
+ .put(Im.DATA, "msn");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_YAHOO)
+ .put(Im.DATA, "yahoo");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_SKYPE)
+ .put(Im.DATA, "skype");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_QQ)
+ .put(Im.DATA, "qq");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
+ .put(Im.DATA, "google talk");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_ICQ)
+ .put(Im.DATA, "icq");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_JABBER)
+ .put(Im.DATA, "jabber");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_NETMEETING)
+ .put(Im.DATA, "netmeeting");
+
+ // No determined way to express unknown type...
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-JABBER", "jabber")
+ .addExpectedNode("X-ICQ", "icq")
+ .addExpectedNode("X-GOOGLE-TALK", "google talk")
+ .addExpectedNode("X-QQ", "qq")
+ .addExpectedNode("X-SKYPE-USERNAME", "skype")
+ .addExpectedNode("X-YAHOO", "yahoo")
+ .addExpectedNode("X-MSN", "msn")
+ .addExpectedNode("X-NETMEETING", "netmeeting")
+ .addExpectedNode("X-AIM", "aim");
+ }
+
+ public void testImBasiV21() {
+ testImVariousTypeSupportCommon(V21);
+ }
+
+ public void testImBasicV30() {
+ testImVariousTypeSupportCommon(V30);
+ }
+
+ private void testImPrefHandlingCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim1");
+ entry.addContentValues(Im.CONTENT_ITEM_TYPE)
+ .put(Im.PROTOCOL, Im.PROTOCOL_AIM)
+ .put(Im.DATA, "aim2")
+ .put(Im.TYPE, Im.TYPE_HOME)
+ .put(Im.IS_PRIMARY, 1);
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-AIM", "aim1")
+ .addExpectedNode("X-AIM", "aim2", new TypeSet("HOME", "PREF"));
+ }
+
+ public void testImPrefHandlingV21() {
+ testImPrefHandlingCommon(V21);
+ }
+
+ public void testImPrefHandlingV30() {
+ testImPrefHandlingCommon(V30);
+ }
+
+ private void testWebsiteCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "http://website.example.android.com/index.html")
+ .put(Website.TYPE, Website.TYPE_BLOG);
+ entry.addContentValues(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "ftp://ftp.example.android.com/index.html")
+ .put(Website.TYPE, Website.TYPE_FTP);
+
+ // We drop TYPE information since vCard (especially 3.0) does not allow us to emit it.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("URL", "ftp://ftp.example.android.com/index.html")
+ .addExpectedNode("URL", "http://website.example.android.com/index.html");
+ }
+
+ public void testWebsiteV21() {
+ testWebsiteCommon(V21);
+ }
+
+ public void testWebsiteV30() {
+ testWebsiteCommon(V30);
+ }
+
+ private String getAndroidPropValue(final String mimeType, String value, Integer type) {
+ return getAndroidPropValue(mimeType, value, type, null);
+ }
+
+ private String getAndroidPropValue(final String mimeType, String value,
+ Integer type, String label) {
+ return (mimeType + ";" + value + ";"
+ + (type != null ? type : "") + ";"
+ + (label != null ? label : "") + ";;;;;;;;;;;;");
+ }
+
+ private void testEventCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_ANNIVERSARY)
+ .put(Event.START_DATE, "1982-06-16");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2008-10-22");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_OTHER)
+ .put(Event.START_DATE, "2018-03-12");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_CUSTOM)
+ .put(Event.LABEL, "The last day")
+ .put(Event.START_DATE, "When the Tower of Hanoi with 64 rings is completed.");
+ entry.addContentValues(Event.CONTENT_ITEM_TYPE)
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY)
+ .put(Event.START_DATE, "2009-05-19"); // Should be ignored.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("BDAY", "2008-10-22")
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "1982-06-16", Event.TYPE_ANNIVERSARY))
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE, "2018-03-12", Event.TYPE_OTHER))
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ getAndroidPropValue(
+ Event.CONTENT_ITEM_TYPE,
+ "When the Tower of Hanoi with 64 rings is completed.",
+ Event.TYPE_CUSTOM, "The last day"));
+ }
+
+ public void testEventV21() {
+ testEventCommon(V21);
+ }
+
+ public void testEventV30() {
+ testEventCommon(V30);
+ }
+
+ private void testNoteCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note1");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note2")
+ .put(Note.IS_PRIMARY, 1); // Just ignored.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNodeWithOrder("NOTE", "note1")
+ .addExpectedNodeWithOrder("NOTE", "note2");
+ }
+
+ public void testNoteV21() {
+ testNoteCommon(V21);
+ }
+
+ public void testNoteV30() {
+ testNoteCommon(V30);
+ }
+
+ private void testPhotoCommon(int vcardType) {
+ final boolean isV30 = vcardType == V30;
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "PhotoTest");
+ entry.addContentValues(Photo.CONTENT_ITEM_TYPE)
+ .put(Photo.PHOTO, sPhotoByteArray);
+
+ ContentValues contentValuesForPhoto = new ContentValues();
+ contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "PhotoTest")
+ .addExpectedNode("N", "PhotoTest;;;;",
+ Arrays.asList("PhotoTest", "", "", "", ""))
+ .addExpectedNodeWithOrder("PHOTO", null, null, sPhotoByteArray,
+ contentValuesForPhoto, new TypeSet("JPEG"), null);
+ }
+
+ public void testPhotoV21() {
+ testPhotoCommon(V21);
+ }
+
+ public void testPhotoV30() {
+ testPhotoCommon(V30);
+ }
+
+ private void testRelationCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+ mVerifier.addContentValuesVerifierElem().addExpected(Relation.CONTENT_ITEM_TYPE)
+ .put(Relation.TYPE, Relation.TYPE_MOTHER)
+ .put(Relation.NAME, "Ms. Mother");
+ }
+
+ public void testRelationV21() {
+ testRelationCommon(V21);
+ }
+
+ public void testRelationV30() {
+ testRelationCommon(V30);
+ }
+
+ public void testV30HandleEscape() {
+ mVerifier.initForExportTest(V30);
+ mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\\")
+ .put(StructuredName.GIVEN_NAME, ";")
+ .put(StructuredName.MIDDLE_NAME, ",")
+ .put(StructuredName.PREFIX, "\n")
+ .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
+ // Verifies the vCard String correctly escapes each character which must be escaped.
+ mVerifier.addLineVerifierElem()
+ .addExpected("N:\\\\;\\;;\\,;\\n;")
+ .addExpected("FN:[<{Unescaped:Asciis}>]");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "[<{Unescaped:Asciis}>]")
+ .addExpectedNode("N", Arrays.asList("\\", ";", ",", "\n", ""));
+ }
+
+ /**
+ * There's no "NICKNAME" property in vCard 2.1, while there is in vCard 3.0.
+ * We use Android-specific "X-ANDROID-CUSTOM" property.
+ * This test verifies the functionality.
+ */
+ public void testNickNameV21() {
+ mVerifier.initForExportTest(V21);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ Nickname.CONTENT_ITEM_TYPE + ";Nicky;;;;;;;;;;;;;;");
+ mVerifier.addContentValuesVerifierElem().addExpected(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "Nicky");
+ }
+
+ public void testTolerateBrokenPhoneNumberEntryV21() {
+ mVerifier.initForExportTest(V21);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_HOME)
+ .put(Phone.NUMBER, "111-222-3333 (Miami)\n444-5555-666 (Tokyo);"
+ + "777-888-9999 (Chicago);111-222-3333 (Miami)");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "111-222-3333", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "444-555-5666", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "777-888-9999", new TypeSet("HOME"));
+ }
+
+ private void testPickUpNonEmptyContentValuesCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1)
+ .put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want.
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.IS_PRIMARY, 1)
+ .put(StructuredName.FAMILY_NAME, "family3");
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "family4");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
+ .addExpectedNode("FN", "family2");
+ }
+
+ public void testPickUpNonEmptyContentValuesV21() {
+ testPickUpNonEmptyContentValuesCommon(V21);
+ }
+
+ public void testPickUpNonEmptyContentValuesV30() {
+ testPickUpNonEmptyContentValuesCommon(V30);
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
new file mode 100644
index 0000000..21f2254
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
@@ -0,0 +1,1011 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.frameworks.coretests.R;
+import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardImporterTests extends VCardTestsBase {
+ // Push data into int array at first since values like 0x80 are
+ // interpreted as int by the compiler and casting all of them is
+ // cumbersome...
+ private static final int[] sPhotoIntArrayForComplicatedCase = {
+ 0xff, 0xd8, 0xff, 0xe1, 0x0a, 0x0f, 0x45, 0x78, 0x69, 0x66, 0x00,
+ 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0d,
+ 0x01, 0x0e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
+ 0xaa, 0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0xba, 0x01, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0xc2, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0xc8, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x28, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd8, 0x01, 0x32, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x13,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x82,
+ 0x98, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xfa,
+ 0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01,
+ 0x84, 0xc4, 0xa5, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
+ 0x01, 0x08, 0x00, 0x00, 0x04, 0x1e, 0x32, 0x30, 0x30, 0x38, 0x31,
+ 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31, 0x00, 0x00,
+ 0x44, 0x6f, 0x43, 0x6f, 0x4d, 0x6f, 0x00, 0x00, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x44, 0x39, 0x30,
+ 0x35, 0x69, 0x20, 0x56, 0x65, 0x72, 0x31, 0x2e, 0x30, 0x30, 0x00,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x00, 0x50, 0x72, 0x69, 0x6e, 0x74, 0x49, 0x4d, 0x00, 0x30, 0x33,
+ 0x30, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x14, 0x00,
+ 0x14, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x34, 0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x11, 0x09, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x0f, 0x0b, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x05, 0x97, 0x00, 0x00, 0x27, 0x10,
+ 0x00, 0x00, 0x08, 0xb0, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1c,
+ 0x01, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x02, 0x5e, 0x00, 0x00,
+ 0x27, 0x10, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x27, 0x10, 0x00,
+ 0x00, 0x03, 0xcb, 0x00, 0x00, 0x27, 0x10, 0x00, 0x00, 0x1b, 0xe5,
+ 0x00, 0x00, 0x27, 0x10, 0x00, 0x28, 0x82, 0x9a, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x6a, 0x82, 0x9d, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x72, 0x88, 0x22, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x90, 0x00,
+ 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30, 0x32, 0x32, 0x30, 0x90,
+ 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03, 0x7a,
+ 0x90, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x03,
+ 0x8e, 0x91, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02,
+ 0x03, 0x00, 0x91, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x03, 0xa2, 0x92, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x03, 0xaa, 0x92, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x03, 0xb2, 0x92, 0x04, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xba, 0x92, 0x05, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xc2, 0x92, 0x07, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x92, 0x08, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92, 0x09,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x92,
+ 0x0a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xca,
+ 0x92, 0x7c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x92, 0x86, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00,
+ 0x03, 0xd2, 0xa0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x30,
+ 0x31, 0x30, 0x30, 0xa0, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x60, 0x00, 0x00, 0xa0, 0x03, 0x00, 0x03, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x48, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x00, 0xa2, 0x0e, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xe8, 0xa2, 0x0f, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xf0, 0xa2, 0x10,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xa2,
+ 0x17, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00,
+ 0xa3, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00,
+ 0x00, 0xa3, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0xa4, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x04, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x03, 0xf8, 0xa4, 0x05, 0x00, 0x03, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0xa4, 0x06, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x07, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x08,
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa4,
+ 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0xa4, 0x0a, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0xa4, 0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x00,
+ 0x00, 0x27, 0x10, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64,
+ 0x32, 0x30, 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20,
+ 0x31, 0x33, 0x3a, 0x35, 0x35, 0x3a, 0x33, 0x31, 0x00, 0x32, 0x30,
+ 0x30, 0x38, 0x3a, 0x31, 0x30, 0x3a, 0x32, 0x39, 0x20, 0x31, 0x33,
+ 0x3a, 0x35, 0x35, 0x3a, 0x34, 0x37, 0x00, 0x00, 0x00, 0x29, 0x88,
+ 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x02, 0xb2, 0x00, 0x00, 0x00,
+ 0x64, 0x00, 0x00, 0x01, 0x5e, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x25, 0x00,
+ 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0e, 0x92, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x30,
+ 0x38, 0x31, 0x30, 0x32, 0x39, 0x31, 0x33, 0x35, 0x35, 0x33, 0x31,
+ 0x00, 0x00, 0x20, 0x2a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x2a,
+ 0xe2, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x52, 0x39, 0x38, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00,
+ 0x00, 0x04, 0x30, 0x31, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x06, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06,
+ 0x00, 0x00, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x04, 0x6c, 0x01, 0x1b, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x04, 0x74, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x04, 0x7c, 0x02, 0x02, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x05, 0x8b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x48, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84,
+ 0x00, 0x20, 0x16, 0x18, 0x1c, 0x18, 0x14, 0x20, 0x1c, 0x1a, 0x1c,
+ 0x24, 0x22, 0x20, 0x26, 0x30, 0x50, 0x34, 0x30, 0x2c, 0x2c, 0x30,
+ 0x62, 0x46, 0x4a, 0x3a, 0x50, 0x74, 0x66, 0x7a, 0x78, 0x72, 0x66,
+ 0x70, 0x6e, 0x80, 0x90, 0xb8, 0x9c, 0x80, 0x88, 0xae, 0x8a, 0x6e,
+ 0x70, 0xa0, 0xda, 0xa2, 0xae, 0xbe, 0xc4, 0xce, 0xd0, 0xce, 0x7c,
+ 0x9a, 0xe2, 0xf2, 0xe0, 0xc8, 0xf0, 0xb8, 0xca, 0xce, 0xc6, 0x01,
+ 0x22, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34, 0x34, 0x5e, 0xc6,
+ 0x84, 0x70, 0x84, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xff, 0xc0,
+ 0x00, 0x11, 0x08, 0x00, 0x78, 0x00, 0xa0, 0x03, 0x01, 0x21, 0x00,
+ 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00,
+ 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03,
+ 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01,
+ 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31,
+ 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+ 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19,
+ 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92,
+ 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+ 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
+ 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+ 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
+ 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00,
+ 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04,
+ 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77,
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12,
+ 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14,
+ 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15,
+ 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17,
+ 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37,
+ 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+ 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+ 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
+ 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+ 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5,
+ 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+ 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+ 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+ 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00,
+ 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00,
+ 0x14, 0x54, 0xaa, 0x2a, 0x46, 0x48, 0xa2, 0xa4, 0x55, 0xa6, 0x04,
+ 0x8a, 0x29, 0xe0, 0x53, 0x10, 0xe0, 0x29, 0xc0, 0x50, 0x03, 0xb1,
+ 0x46, 0x29, 0x80, 0x84, 0x52, 0x11, 0x40, 0x0d, 0x22, 0x9a, 0x45,
+ 0x20, 0x23, 0x61, 0x51, 0x30, 0xa0, 0x08, 0xc8, 0xa8, 0xd8, 0x52,
+ 0x02, 0x26, 0x15, 0x0b, 0x0a, 0x00, 0xb4, 0xa2, 0xa5, 0x5a, 0x00,
+ 0x91, 0x45, 0x4a, 0xa2, 0x81, 0x92, 0x01, 0x4e, 0x02, 0x98, 0x87,
+ 0x0a, 0x70, 0xa0, 0x07, 0x62, 0x8c, 0x50, 0x21, 0x0d, 0x25, 0x00,
+ 0x34, 0x8a, 0x61, 0x14, 0x0c, 0x63, 0x0a, 0x89, 0x85, 0x00, 0x46,
+ 0xd5, 0x1b, 0x52, 0x02, 0x16, 0xa8, 0x98, 0x50, 0x05, 0x94, 0xa9,
+ 0x16, 0x80, 0x25, 0x5a, 0x95, 0x68, 0x18, 0xf1, 0x4f, 0x14, 0xc4,
+ 0x3b, 0xb5, 0x22, 0xb6, 0x38, 0x34, 0x00, 0xe3, 0x22, 0x8e, 0xf4,
+ 0x79, 0x8a, 0x7b, 0xd1, 0x71, 0x03, 0x30, 0xc7, 0x14, 0x83, 0xa5,
+ 0x00, 0x06, 0x98, 0x68, 0x01, 0x8d, 0x51, 0x35, 0x03, 0x22, 0x6a,
+ 0x8d, 0xa9, 0x01, 0x13, 0x54, 0x4d, 0x40, 0x13, 0xa5, 0x4a, 0x28,
+ 0x02, 0x45, 0x35, 0x2a, 0x9a, 0x00, 0x78, 0x34, 0xf0, 0x69, 0x80,
+ 0x34, 0x81, 0x45, 0x40, 0xce, 0x58, 0xe6, 0xa2, 0x4c, 0x06, 0xe4,
+ 0xfa, 0xd1, 0x93, 0x50, 0x21, 0xca, 0xe4, 0x55, 0x84, 0x90, 0x30,
+ 0xab, 0x8b, 0x18, 0xa6, 0x9a, 0x6a, 0xc4, 0x31, 0xaa, 0x26, 0xa0,
+ 0x64, 0x4d, 0x51, 0xb5, 0x20, 0x23, 0x6a, 0x89, 0xa8, 0x02, 0x44,
+ 0x35, 0x2a, 0x9a, 0x00, 0x95, 0x4d, 0x48, 0xa6, 0x80, 0x24, 0x53,
+ 0x4e, 0xce, 0x05, 0x30, 0x2b, 0x3b, 0xee, 0x6a, 0x91, 0x5d, 0x76,
+ 0x63, 0xbd, 0x65, 0x7d, 0x40, 0x66, 0x68, 0xa9, 0x02, 0x45, 0x2b,
+ 0xb3, 0x9e, 0xb4, 0xc5, 0x6d, 0xad, 0x9a, 0xa0, 0x2c, 0x06, 0xc8,
+ 0xcd, 0x04, 0xd6, 0xa2, 0x23, 0x63, 0x51, 0xb1, 0xa0, 0x64, 0x4d,
+ 0x51, 0x93, 0x48, 0x08, 0xda, 0xa2, 0x6a, 0x00, 0x72, 0x1a, 0x99,
+ 0x4d, 0x00, 0x48, 0xa6, 0xa4, 0x53, 0x4c, 0x07, 0x86, 0x03, 0xbd,
+ 0x2b, 0x9c, 0xa7, 0x14, 0x98, 0x10, 0x85, 0x34, 0xe0, 0xa6, 0xb3,
+ 0xb0, 0x0b, 0xb5, 0xa8, 0x0a, 0xd4, 0x58, 0x42, 0xed, 0x3e, 0x94,
+ 0xd2, 0xa6, 0x8b, 0x01, 0x34, 0x44, 0xed, 0xe6, 0x9c, 0x4d, 0x6a,
+ 0x80, 0x8d, 0x8d, 0x46, 0xc6, 0x80, 0x23, 0x63, 0x51, 0x9a, 0x06,
+ 0x46, 0xd5, 0x13, 0x52, 0x01, 0x54, 0xd4, 0xaa, 0x68, 0x02, 0x40,
+ 0x6a, 0x40, 0x78, 0xa0, 0x08, 0x59, 0xce, 0xee, 0xb5, 0x2a, 0x39,
+ 0xd9, 0x59, 0xa7, 0xa8, 0x00, 0x73, 0xeb, 0x4e, 0x0e, 0x7d, 0x69,
+ 0x5c, 0x05, 0xf3, 0x0f, 0xad, 0x1e, 0x61, 0xf5, 0xa7, 0x71, 0x0b,
+ 0xe6, 0x35, 0x21, 0x90, 0xd3, 0xb8, 0x0e, 0x32, 0x10, 0x95, 0x10,
+ 0x91, 0xb3, 0xd6, 0x9b, 0x60, 0x4b, 0x9c, 0x8a, 0x63, 0x1a, 0xb0,
+ 0x18, 0x4d, 0x46, 0xc6, 0x80, 0x22, 0x6a, 0x61, 0xa4, 0x31, 0xaa,
+ 0x6a, 0x55, 0x34, 0x01, 0x2a, 0x9a, 0x7e, 0x78, 0xa0, 0x08, 0x09,
+ 0xf9, 0xaa, 0x58, 0xcf, 0xca, 0x6b, 0x3e, 0xa0, 0x00, 0xd3, 0x81,
+ 0xa9, 0x01, 0x73, 0x46, 0x69, 0x80, 0xb9, 0xa4, 0xcd, 0x00, 0x2b,
+ 0x1f, 0x92, 0xa3, 0x07, 0x9a, 0x6f, 0x70, 0x26, 0xcf, 0x14, 0xd2,
+ 0x6b, 0x51, 0x0c, 0x63, 0x51, 0xb1, 0xa0, 0x08, 0xda, 0x98, 0x69,
+ 0x0c, 0x8d, 0x4d, 0x4a, 0xa6, 0x80, 0x24, 0x53, 0x52, 0x03, 0xc5,
+ 0x02, 0x21, 0x27, 0xe6, 0xa9, 0x23, 0x3f, 0x29, 0xac, 0xfa, 0x8c,
+ 0x01, 0xe6, 0x9c, 0x0d, 0x48, 0x0a, 0x0d, 0x2e, 0x68, 0x01, 0x73,
+ 0x49, 0x9a, 0x60, 0x2b, 0x1f, 0x92, 0x98, 0x3a, 0xd3, 0x7b, 0x81,
+ 0x36, 0x78, 0xa6, 0x93, 0x5a, 0x88, 0x8c, 0x9a, 0x63, 0x1a, 0x00,
+ 0x8c, 0xd3, 0x0d, 0x21, 0x91, 0x29, 0xa9, 0x14, 0xd0, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x11, 0x1b, 0x1e, 0x6a, 0x48, 0xcf, 0xca, 0x6b,
+ 0x3e, 0xa3, 0x10, 0x1a, 0x70, 0x35, 0x20, 0x38, 0x1a, 0x5c, 0xd2,
+ 0x01, 0x73, 0x49, 0x9a, 0x60, 0x39, 0x8f, 0xca, 0x29, 0x8b, 0xf7,
+ 0xaa, 0xba, 0x88, 0x96, 0x9a, 0x6b, 0x40, 0x18, 0xc6, 0xa3, 0x26,
+ 0x80, 0x18, 0x69, 0xa6, 0x90, 0xc8, 0x14, 0xd4, 0x8a, 0x69, 0x80,
+ 0xf0, 0x6a, 0x40, 0x68, 0x10, 0xbb, 0x41, 0xa7, 0xe3, 0x0b, 0xc5,
+ 0x2b, 0x01, 0x10, 0xa7, 0x03, 0x59, 0x0c, 0x76, 0x69, 0x73, 0x40,
+ 0x0b, 0x9a, 0x28, 0x11, 0x28, 0x19, 0x5e, 0x69, 0x02, 0x81, 0x5a,
+ 0xd8, 0x00, 0xd3, 0x4d, 0x50, 0x0c, 0x6a, 0x8c, 0xd2, 0x01, 0xa6,
+ 0x98, 0x69, 0x0c, 0xae, 0xa6, 0xa4, 0x06, 0x80, 0x1e, 0xa6, 0x9e,
+ 0x0d, 0x31, 0x12, 0x03, 0x4f, 0x06, 0x80, 0x13, 0x60, 0x34, 0xd3,
+ 0xc1, 0xa8, 0x92, 0x01, 0xf1, 0x8d, 0xdd, 0x69, 0xcc, 0xa1, 0x69,
+ 0x5b, 0x4b, 0x80, 0x83, 0x93, 0x52, 0x04, 0x14, 0xe2, 0xae, 0x03,
+ 0xa9, 0x0d, 0x68, 0x03, 0x4d, 0x34, 0xd0, 0x03, 0x0d, 0x30, 0xd2,
+ 0x01, 0x86, 0x9a, 0x68, 0x19, 0x58, 0x1a, 0x78, 0xa4, 0x04, 0x8a,
+ 0x69, 0xe0, 0xd3, 0x10, 0xe0, 0x69, 0xe0, 0xd0, 0x03, 0xc1, 0xa8,
+ 0xdb, 0xad, 0x4c, 0x81, 0x12, 0x45, 0xd6, 0x9d, 0x25, 0x1d, 0x00,
+ 0x6a, 0xf5, 0xa9, 0xe8, 0x80, 0x31, 0x29, 0x0d, 0x58, 0x08, 0x69,
+ 0x86, 0x80, 0x1a, 0x69, 0x86, 0x90, 0x0c, 0x34, 0xd3, 0x48, 0x65,
+ 0x51, 0x4f, 0x06, 0x98, 0x0f, 0x14, 0xf0, 0x68, 0x10, 0xf0, 0x69,
+ 0xe0, 0xd0, 0x03, 0x81, 0xa5, 0x2b, 0x9a, 0x1a, 0xb8, 0x87, 0xa8,
+ 0xdb, 0x4a, 0x46, 0x68, 0xb6, 0x80, 0x2a, 0xa8, 0x14, 0xea, 0x12,
+ 0xb0, 0x05, 0x21, 0xa6, 0x02, 0x1a, 0x61, 0xa0, 0x06, 0x9a, 0x61,
+ 0xa4, 0x31, 0x86, 0x9a, 0x69, 0x0c, 0xa8, 0x0d, 0x3c, 0x53, 0x01,
+ 0xe2, 0x9e, 0x28, 0x10, 0xf1, 0x4e, 0x06, 0x98, 0x0f, 0x06, 0x9e,
+ 0x0d, 0x02, 0x1c, 0x29, 0xc2, 0x80, 0x16, 0x96, 0x80, 0x0a, 0x4a,
+ 0x00, 0x43, 0x4d, 0x34, 0x0c, 0x61, 0xa6, 0x1a, 0x40, 0x34, 0xd3,
+ 0x4d, 0x21, 0x80, 0xff, 0xd9, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0a,
+ 0x07, 0x07, 0x08, 0x07, 0x06, 0x0a, 0x08, 0x08, 0x08, 0x0b, 0x0a,
+ 0x0a, 0x0b, 0x0e, 0x18, 0x10, 0x0e, 0x0d, 0x0d, 0x0e, 0x1d, 0x15,
+ 0x16, 0x11, 0x18, 0x23, 0x1f, 0x25, 0x24, 0x22, 0x1f, 0x22, 0x21,
+ 0x26, 0x2b, 0x37, 0x2f, 0x26, 0x29, 0x34, 0x29, 0x21, 0x22, 0x30,
+ 0x41, 0x31, 0x34, 0x39, 0x3b, 0x3e, 0x3e, 0x3e, 0x25, 0x2e, 0x44,
+ 0x49, 0x43, 0x3c, 0x48, 0x37, 0x3d, 0x3e, 0x3b, 0x01, 0x0a, 0x0b,
+ 0x0b, 0x0e, 0x0d, 0x0e, 0x1c, 0x10, 0x10, 0x1c, 0x3b, 0x28, 0x22,
+ 0x28, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0xff, 0xc0, 0x00, 0x11,
+ 0x08, 0x00, 0x48, 0x00, 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11,
+ 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x01, 0xa2, 0x00, 0x00, 0x01,
+ 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+ 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, 0x01,
+ 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+ 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1,
+ 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33,
+ 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+ 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
+ 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8,
+ 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+ 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3,
+ 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x01, 0x00, 0x03, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03,
+ 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+ 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51,
+ 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+ 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72,
+ 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19,
+ 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54,
+ 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+ 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
+ 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+ 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+ 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2,
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+ 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03,
+ 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0x9e, 0xd2,
+ 0x2e, 0x07, 0x15, 0xaf, 0x6d, 0x08, 0xe2, 0xb3, 0x45, 0x1a, 0xf6,
+ 0xd0, 0x00, 0x01, 0xc5, 0x68, 0x45, 0x17, 0x4a, 0xb4, 0x22, 0xe4,
+ 0x70, 0x8c, 0x74, 0xa9, 0x3c, 0xa1, 0x8e, 0x95, 0x48, 0x96, 0x31,
+ 0xe2, 0x18, 0xe9, 0x55, 0xa5, 0x8c, 0x7a, 0x50, 0x05, 0x0b, 0x88,
+ 0x86, 0x0f, 0x15, 0x8f, 0x75, 0x1f, 0x26, 0x93, 0x19, 0x91, 0x77,
+ 0x18, 0xc1, 0xac, 0x4b, 0xc8, 0xfa, 0xd6, 0x63, 0x37, 0x6d, 0x31,
+ 0xb4, 0x73, 0x5b, 0x36, 0xa0, 0x1c, 0x50, 0x80, 0xd7, 0x83, 0xa0,
+ 0xab, 0xd1, 0x62, 0xad, 0x09, 0x8f, 0x17, 0x29, 0x03, 0xb2, 0xcc,
+ 0xe0, 0x77, 0x14, 0xa3, 0x56, 0xb3, 0x27, 0x1e, 0x67, 0xe9, 0x52,
+ 0xea, 0xc6, 0x3a, 0x36, 0x48, 0xef, 0x3d, 0x27, 0x70, 0x22, 0x60,
+ 0x47, 0x52, 0x69, 0xb2, 0xe2, 0xad, 0x3b, 0xea, 0x80, 0xa3, 0x38,
+ 0xe0, 0xd6, 0x3d, 0xd8, 0x1c, 0xd0, 0xca, 0x46, 0x3d, 0xd0, 0x18,
+ 0x35, 0x89, 0x78, 0xa3, 0x9a, 0xcd, 0x8c, 0xd2, 0xb3, 0x93, 0x2a,
+ 0x2b, 0x66, 0xd5, 0xf1, 0x8a, 0x10, 0x1a, 0xd6, 0xf2, 0x03, 0x8a,
+ 0x9e, 0xe6, 0xf4, 0x5a, 0xdb, 0xef, 0xfe, 0x23, 0xc0, 0xa7, 0x27,
+ 0xcb, 0x16, 0xc4, 0xcc, 0xdd, 0xe2, 0x78, 0x9a, 0x69, 0x66, 0xcc,
+ 0x99, 0xe1, 0x4d, 0x47, 0xba, 0xbc, 0xd9, 0x6a, 0xee, 0x26, 0x59,
+ 0x59, 0x4d, 0xac, 0x69, 0x34, 0x52, 0xe5, 0x8f, 0x55, 0xad, 0x58,
+ 0xae, 0x85, 0xc4, 0x22, 0x41, 0xdf, 0xad, 0x76, 0x61, 0xe5, 0x6f,
+ 0x74, 0x45, 0x69, 0xdc, 0x00, 0x79, 0xac, 0x8b, 0xa6, 0xc9, 0x35,
+ 0xd4, 0x34, 0x64, 0xdc, 0x37, 0x06, 0xb1, 0xae, 0x88, 0xc1, 0xac,
+ 0xd8, 0xc9, 0x2c, 0xa6, 0xe0, 0x73, 0x5b, 0x36, 0xf3, 0x74, 0xe6,
+ 0x84, 0x05, 0xe3, 0xa9, 0x47, 0x6a, 0x14, 0xb6, 0x49, 0x3d, 0x85,
+ 0x3a, 0xee, 0xee, 0x2b, 0xa8, 0xe2, 0x6f, 0x30, 0x81, 0xe9, 0x8a,
+ 0xca, 0xa4, 0xe2, 0xd3, 0x8b, 0x01, 0xb1, 0xf9, 0x04, 0x7f, 0xaf,
+ 0x23, 0xf0, 0xa9, 0x54, 0x41, 0x9c, 0xfd, 0xa3, 0xf4, 0xae, 0x65,
+ 0x18, 0xf7, 0x25, 0x8a, 0xe2, 0x02, 0x38, 0xb8, 0xfd, 0x2a, 0x7b,
+ 0x5b, 0xa8, 0x6d, 0x6d, 0x5d, 0x9a, 0x5d, 0xcb, 0xbb, 0xd2, 0xb6,
+ 0xa6, 0xa3, 0x19, 0x5e, 0xe2, 0x03, 0x7b, 0x1d, 0xc2, 0x17, 0x8d,
+ 0xb8, 0xac, 0xfb, 0x89, 0x39, 0x35, 0xd6, 0x9a, 0x6a, 0xe8, 0x66,
+ 0x55, 0xcb, 0xf5, 0xac, 0x7b, 0x96, 0xeb, 0x50, 0xc6, 0x88, 0x6d,
+ 0x66, 0xe9, 0xcd, 0x6c, 0xdb, 0x4f, 0xd3, 0x9a, 0x00, 0x2f, 0xe6,
+ 0xf9, 0xa3, 0xe7, 0xb5, 0x4a, 0x93, 0x7f, 0xa2, 0xc6, 0x73, 0xdc,
+ 0xd7, 0x15, 0x55, 0xef, 0x48, 0x7d, 0x09, 0x52, 0x6e, 0x3a, 0xd4,
+ 0xab, 0x2f, 0xbd, 0x61, 0x16, 0x0c, 0x73, 0x49, 0xc5, 0x24, 0x92,
+ 0x7f, 0xa2, 0x63, 0xfd, 0xaa, 0xd6, 0x2f, 0x71, 0x0e, 0xb1, 0x93,
+ 0xf7, 0x2d, 0xf5, 0xa4, 0x9e, 0x4e, 0xb5, 0xdd, 0x4b, 0xf8, 0x68,
+ 0x4c, 0xcb, 0xb9, 0x93, 0xad, 0x65, 0xce, 0xd9, 0x26, 0xa9, 0x8d,
+ 0x19, 0xf6, 0xf2, 0xf4, 0xe6, 0xb5, 0xad, 0xe7, 0xc6, 0x39, 0xa0,
+ 0x18, 0xeb, 0xc9, 0x77, 0x6c, 0x35, 0x2a, 0x4b, 0xfe, 0x8a, 0x9c,
+ 0xff, 0x00, 0x11, 0xae, 0x3a, 0x8b, 0xde, 0x61, 0xd0, 0x9e, 0x39,
+ 0xb8, 0xeb, 0x53, 0xac, 0xb9, 0xae, 0x5b, 0x00, 0xf3, 0x27, 0x14,
+ 0x92, 0xc9, 0xfe, 0x8a, 0x3f, 0xde, 0x35, 0xac, 0x3a, 0x88, 0x92,
+ 0xcd, 0xb1, 0x6e, 0x7d, 0xcd, 0x32, 0x67, 0xeb, 0xcd, 0x7a, 0x14,
+ 0xfe, 0x04, 0x26, 0x66, 0xce, 0xf9, 0x26, 0xb3, 0xe6, 0x6e, 0xb4,
+ 0xd9, 0x48, 0xc8, 0x82, 0x4e, 0x07, 0x35, 0xa7, 0x6f, 0x2f, 0x02,
+ 0x9a, 0x06, 0x5f, 0x8c, 0xa4, 0x83, 0x0e, 0x32, 0x2a, 0x69, 0xe3,
+ 0xdd, 0x12, 0x08, 0x97, 0x85, 0xec, 0x2a, 0x2a, 0x42, 0xf1, 0x76,
+ 0x26, 0xe4, 0x6a, 0x59, 0x0e, 0x18, 0x10, 0x6a, 0xd2, 0x89, 0x02,
+ 0x6e, 0x2a, 0x71, 0xeb, 0x5c, 0x1c, 0x8c, 0xa6, 0x48, 0xbb, 0xdc,
+ 0x61, 0x41, 0x35, 0x72, 0x28, 0x87, 0xd9, 0xf6, 0x4a, 0xb9, 0xe7,
+ 0x38, 0xae, 0x8c, 0x3d, 0x36, 0xdd, 0xde, 0xc4, 0xb0, 0x21, 0x51,
+ 0x76, 0xa8, 0xc0, 0xaa, 0x93, 0x31, 0xe6, 0xbb, 0x2d, 0x65, 0x61,
+ 0x19, 0xd3, 0x1e, 0xb5, 0x46, 0x5a, 0x96, 0x5a, 0x30, 0xa0, 0x7e,
+ 0x05, 0x69, 0x5b, 0xc9, 0xc6, 0x28, 0x40, 0xcd, 0x08, 0x64, 0x3c,
+ 0x73, 0x57, 0xe1, 0x94, 0xf1, 0xcd, 0x5a, 0x21, 0x8c, 0xb9, 0x63,
+ 0xe7, 0x67, 0x1d, 0xab, 0x40, 0xb1, 0xfb, 0x00, 0x1d, 0xf0, 0x2b,
+ 0x99, 0x2d, 0x66, 0x3e, 0x88, 0x75, 0x81, 0x3f, 0x31, 0xf6, 0xab,
+ 0x64, 0xd6, 0xb4, 0x17, 0xee, 0xd0, 0x9e, 0xe4, 0x32, 0x1a, 0xa7,
+ 0x31, 0xad, 0x18, 0x14, 0x26, 0xef, 0x54, 0xa5, 0xa8, 0x65, 0xa3,
+ 0x9c, 0x81, 0xfa, 0x56, 0x8c, 0x2d, 0xce, 0x68, 0x40, 0xcb, 0xf1,
+ 0x37, 0xbd, 0x5e, 0x85, 0xea, 0xd1, 0x0c, 0xbb, 0x19, 0x56, 0x23,
+ 0x20, 0x1f, 0xad, 0x5c, 0x42, 0x08, 0x03, 0xb5, 0x55, 0x91, 0x04,
+ 0xc9, 0x80, 0x38, 0x00, 0x0a, 0x71, 0x34, 0x6c, 0x32, 0x27, 0xe9,
+ 0x55, 0x25, 0x15, 0x2c, 0x68, 0xa3, 0x30, 0xeb, 0x54, 0xa5, 0x15,
+ 0x0c, 0xd1, 0x00, 0xff, 0xd9};
+
+ /* package */ static final byte[] sPhotoByteArrayForComplicatedCase;
+
+ static {
+ final int length = sPhotoIntArrayForComplicatedCase.length;
+ sPhotoByteArrayForComplicatedCase = new byte[length];
+ for (int i = 0; i < length; i++) {
+ sPhotoByteArrayForComplicatedCase[i] = (byte)sPhotoIntArrayForComplicatedCase[i];
+ }
+ }
+
+ public void testV21SimpleCase1_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_simple_1);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
+ }
+
+ public void testV21SimpleCase1_Type_Generic() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+ }
+
+ public void testV21SimpleCase1_Type_Japanese() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ // If name-related strings only contains printable Ascii,
+ // the order is remained to be US's:
+ // "Prefix Given Middle Family Suffix"
+ .put(StructuredName.DISPLAY_NAME, "Roid Ando");
+ }
+
+ public void testV21SimpleCase2() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+ }
+
+ public void testV21SimpleCase3() {
+ mVerifier.initForImportTest(V21, R.raw.v21_simple_3);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Ando")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ // "FN" field should be prefered since it should contain the original
+ // order intended by the author of the file.
+ .put(StructuredName.DISPLAY_NAME, "Ando Roid");
+ }
+
+ /**
+ * Tests ';' is properly handled by VCardParser implementation.
+ */
+ public void testV21BackslashCase_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_backslash);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
+ Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
+ .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
+
+ }
+
+ /**
+ * Tests ContactStruct correctly ignores redundant fields in "N" property values and
+ * inserts name related data.
+ */
+ public void testV21BackslashCase() {
+ mVerifier.initForImportTest(V21, R.raw.v21_backslash);
+ mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ // FAMILY_NAME is empty and removed in this test...
+ .put(StructuredName.GIVEN_NAME, "A;B\\")
+ .put(StructuredName.MIDDLE_NAME, "C\\;")
+ .put(StructuredName.PREFIX, "D")
+ .put(StructuredName.SUFFIX, ":E")
+ .put(StructuredName.DISPLAY_NAME, "A;B\\C\\;D:E\\\\");
+ }
+
+ public void testOrgBeforTitle() {
+ mVerifier.initForImportTest(V21, R.raw.v21_org_before_title);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Normal Guy");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Company")
+ .put(Organization.DEPARTMENT, "Organization Devision Room Sheet No.")
+ .put(Organization.TITLE, "Excellent Janitor")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ public void testTitleBeforOrg() {
+ mVerifier.initForImportTest(V21, R.raw.v21_title_before_org);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Nice Guy");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Marverous")
+ .put(Organization.DEPARTMENT, "Perfect Great Good Bad Poor")
+ .put(Organization.TITLE, "Cool Title")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ /**
+ * Verifies that vCard importer correctly interpret "PREF" attribute to IS_PRIMARY.
+ * The data contain three cases: one "PREF", no "PREF" and multiple "PREF", in each type.
+ */
+ public void testV21PrefToIsPrimary() {
+ mVerifier.initForImportTest(V21, R.raw.v21_pref_handling);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.DISPLAY_NAME, "Smith");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "1")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "2")
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ .put(Phone.IS_PRIMARY, 1);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "3")
+ .put(Phone.TYPE, Phone.TYPE_ISDN);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "test@example.com")
+ .put(Email.TYPE, Email.TYPE_HOME)
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.DATA, "test2@examination.com")
+ .put(Email.TYPE, Email.TYPE_MOBILE)
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Company")
+ .put(Organization.TITLE, "Engineer")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Mystery")
+ .put(Organization.TITLE, "Blogger")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Poetry")
+ .put(Organization.TITLE, "Poet")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ }
+
+ /**
+ * Tests all the properties in a complicated vCard are correctly parsed by the VCardParser.
+ */
+ public void testV21ComplicatedCase_Parsing() {
+ mVerifier.initForImportTest(V21, R.raw.v21_complicated);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
+ Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
+ .addExpectedNodeWithOrder("FN", "Joe Due")
+ .addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
+ Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
+ .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!")
+ .addExpectedNodeWithOrder("TITLE", "Shrimp Man")
+ .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("TEL", "(111) 555-1212", new TypeSet("WORK", "VOICE"))
+ .addExpectedNodeWithOrder("TEL", "(404) 555-1212", new TypeSet("HOME", "VOICE"))
+ .addExpectedNodeWithOrder("TEL", "0311111111", new TypeSet("CELL"))
+ .addExpectedNodeWithOrder("TEL", "0322222222", new TypeSet("VIDEO"))
+ .addExpectedNodeWithOrder("TEL", "0333333333", new TypeSet("VOICE"))
+ .addExpectedNodeWithOrder("ADR",
+ ";;100 Waters Edge;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "100 Waters Edge", "Baytown",
+ "LA", "30314", "United States of America"),
+ null, null, new TypeSet("WORK"), null)
+ .addExpectedNodeWithOrder("LABEL",
+ "100 Waters Edge\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP, new TypeSet("WORK"), null)
+ .addExpectedNodeWithOrder("ADR",
+ ";;42 Plantation St.;Baytown;LA;30314;United States of America",
+ Arrays.asList("", "", "42 Plantation St.", "Baytown",
+ "LA", "30314", "United States of America"), null, null,
+ new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("LABEL",
+ "42 Plantation St.\r\nBaytown, LA 30314\r\nUnited States of America",
+ null, null, mContentValuesForQP,
+ new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
+ new TypeSet("PREF", "INTERNET"))
+ .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
+ .addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.")
+ .addExpectedNodeWithOrder("NOTE",
+ "Now's the time for all folk to come to the aid of their country.",
+ null, null, mContentValuesForQP, null, null)
+ .addExpectedNodeWithOrder("PHOTO", null,
+ null, sPhotoByteArrayForComplicatedCase, mContentValuesForBase64V21,
+ new TypeSet("JPEG"), null)
+ .addExpectedNodeWithOrder("X-ATTRIBUTE", "Some String")
+ .addExpectedNodeWithOrder("BDAY", "19800101")
+ .addExpectedNodeWithOrder("GEO", "35.6563854,139.6994233")
+ .addExpectedNodeWithOrder("URL", "http://www.example.com/")
+ .addExpectedNodeWithOrder("REV", "20080424T195243Z");
+ }
+
+ /**
+ * Checks ContactStruct correctly inserts values in a complicated vCard
+ * into ContentResolver.
+ */
+ public void testV21ComplicatedCase() {
+ mVerifier.initForImportTest(V21, R.raw.v21_complicated);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Gump")
+ .put(StructuredName.GIVEN_NAME, "Forrest")
+ .put(StructuredName.MIDDLE_NAME, "Hoge")
+ .put(StructuredName.PREFIX, "Pos")
+ .put(StructuredName.SUFFIX, "Tao")
+ .put(StructuredName.DISPLAY_NAME, "Joe Due");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.TYPE, Organization.TYPE_WORK)
+ .put(Organization.COMPANY, "Gump Shrimp Co.")
+ .put(Organization.DEPARTMENT, "Sales Dept.;Manager Fish keeper")
+ .put(Organization.TITLE, "Shrimp Man");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_WORK)
+ // Phone number is expected to be formated with NAMP format in default.
+ .put(Phone.NUMBER, "111-555-1212");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_HOME)
+ .put(Phone.NUMBER, "404-555-1212");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_MOBILE)
+ .put(Phone.NUMBER, "031-111-1111");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VIDEO")
+ .put(Phone.NUMBER, "032-222-2222");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.NUMBER, "033-333-3333");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.COUNTRY, "United States of America")
+ .put(StructuredPostal.POSTCODE, "30314")
+ .put(StructuredPostal.REGION, "LA")
+ .put(StructuredPostal.CITY, "Baytown")
+ .put(StructuredPostal.STREET, "100 Waters Edge")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "100 Waters Edge Baytown LA 30314 United States of America");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.COUNTRY, "United States of America")
+ .put(StructuredPostal.POSTCODE, "30314")
+ .put(StructuredPostal.REGION, "LA")
+ .put(StructuredPostal.CITY, "Baytown")
+ .put(StructuredPostal.STREET, "42 Plantation St.")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "42 Plantation St. Baytown LA 30314 United States of America");
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ // "TYPE=INTERNET" -> TYPE_CUSTOM + the label "INTERNET"
+ .put(Email.TYPE, Email.TYPE_CUSTOM)
+ .put(Email.LABEL, "INTERNET")
+ .put(Email.DATA, "forrestgump@walladalla.com")
+ .put(Email.IS_PRIMARY, 1);
+ elem.addExpected(Email.CONTENT_ITEM_TYPE)
+ .put(Email.TYPE, Email.TYPE_MOBILE)
+ .put(Email.DATA, "cell@example.com");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "The following note is the example from RFC 2045.");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE,
+ "Now's the time for all folk to come to the aid of their country.");
+ elem.addExpected(Photo.CONTENT_ITEM_TYPE)
+ // No information about its image format can be inserted.
+ .put(Photo.PHOTO, sPhotoByteArrayForComplicatedCase);
+ elem.addExpected(Event.CONTENT_ITEM_TYPE)
+ .put(Event.START_DATE, "19800101")
+ .put(Event.TYPE, Event.TYPE_BIRTHDAY);
+ elem.addExpected(Website.CONTENT_ITEM_TYPE)
+ .put(Website.URL, "http://www.example.com/")
+ .put(Website.TYPE, Website.TYPE_HOMEPAGE);
+ }
+
+ public void testV30Simple_Parsing() {
+ mVerifier.initForImportTest(V30, R.raw.v30_simple);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "3.0")
+ .addExpectedNodeWithOrder("FN", "And Roid")
+ .addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", ""))
+ .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance",
+ Arrays.asList("Open", "Handset", " Alliance"))
+ .addExpectedNodeWithOrder("SORT-STRING", "android")
+ .addExpectedNodeWithOrder("TEL", "0300000000", new TypeSet("PREF", "VOICE"))
+ .addExpectedNodeWithOrder("CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("X-GNO", "0")
+ .addExpectedNodeWithOrder("X-GN", "group0")
+ .addExpectedNodeWithOrder("X-REDUCTION", "0")
+ .addExpectedNodeWithOrder("REV", "20081031T065854Z");
+ }
+
+ public void testV30Simple() {
+ mVerifier.initForImportTest(V30, R.raw.v30_simple);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "And")
+ .put(StructuredName.GIVEN_NAME, "Roid")
+ .put(StructuredName.DISPLAY_NAME, "And Roid")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "android");
+ elem.addExpected(Organization.CONTENT_ITEM_TYPE)
+ .put(Organization.COMPANY, "Open")
+ .put(Organization.DEPARTMENT, "Handset Alliance")
+ .put(Organization.TYPE, Organization.TYPE_WORK);
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.NUMBER, "030-000-0000")
+ .put(Phone.IS_PRIMARY, 1);
+ }
+
+ public void testV21Japanese1_Parsing() {
+ // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
+ // vCard 2.1/3.0 specification does not allow multiple values.
+ // Do not need to handle it as multiple values.
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
+ R.raw.v21_japanese_1);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "0300000000", null, null, null,
+ new TypeSet("VOICE", "PREF"), null);
+ }
+
+ private void testV21Japanese1Common(int resId, int vcardType, boolean japanese) {
+ mVerifier.initForImportTest(vcardType, resId);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9")
+ // While vCard parser does not split "SOUND" property values,
+ // ContactStruct care it.
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ // Phone number formatting is different.
+ .put(Phone.NUMBER, (japanese ? "03-0000-0000" : "030-000-0000"))
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "VOICE")
+ .put(Phone.IS_PRIMARY, 1);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}.
+ */
+ public void testV21Japanese1_Type_Generic_Utf8() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}.
+ */
+ public void testV21Japanese1_Type_Japanese_Sjis() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true);
+ }
+
+ /**
+ * Verifies vCard with Japanese can be parsed correctly with
+ * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}.
+ * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
+ */
+ public void testV21Japanese1_Type_Japanese_Utf8() {
+ testV21Japanese1Common(
+ R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true);
+ }
+
+ public void testV21Japanese2_Parsing() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
+ R.raw.v21_japanese_2);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
+ Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
+ "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031",
+ null, null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73;\uFF9B\uFF72\uFF84\uFF9E\u0031;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("ADR",
+ ";\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC\u0036" +
+ "\u968E;;;;150-8512;",
+ Arrays.asList("",
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E", "", "", "", "150-8512", ""),
+ null, mContentValuesForQPAndSJis, new TypeSet("HOME"), null)
+ .addExpectedNodeWithOrder("NOTE", "\u30E1\u30E2", null, null,
+ mContentValuesForQPAndSJis, null, null);
+ }
+
+ public void testV21Japanese2_Type_Generic_Utf8() {
+ mVerifier.initForImportTest(V21, R.raw.v21_japanese_2);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
+ .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9\u0031")
+ .put(StructuredName.DISPLAY_NAME,
+ "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9\u0020\u0031")
+ // ContactStruct should correctly split "SOUND" property into several elements,
+ // even though VCardParser side does not care it.
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF71\uFF9D\uFF84\uFF9E\uFF73")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF9B\uFF72\uFF84\uFF9E\u0031");
+ elem.addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POSTCODE, "150-8512")
+ .put(StructuredPostal.STREET,
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u6771\u4EAC\u90FD\u6E0B\u8C37\u533A\u685C" +
+ "\u4E18\u753A\u0032\u0036\u002D\u0031\u30BB" +
+ "\u30EB\u30EA\u30A2\u30F3\u30BF\u30EF\u30FC" +
+ "\u0036\u968E 150-8512")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "\u30E1\u30E2");
+ }
+
+ public void testV21MultipleEntryCase_Parse() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
+ R.raw.v21_multiple_entry);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "9", new TypeSet("X-NEC-SECRET"))
+ .addExpectedNodeWithOrder("TEL", "10", new TypeSet("X-NEC-HOTEL"))
+ .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
+ .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "13", new TypeSet("MODEM"))
+ .addExpectedNodeWithOrder("TEL", "14", new TypeSet("PAGER"))
+ .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
+ .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
+ Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNodeWithOrder("SOUND",
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035;;;;",
+ null, null, mContentValuesForSJis,
+ new TypeSet("X-IRMC-N"), null)
+ .addExpectedNodeWithOrder("TEL", "17", new TypeSet("X-NEC-BOY"))
+ .addExpectedNodeWithOrder("TEL", "18", new TypeSet("X-NEC-FRIEND"))
+ .addExpectedNodeWithOrder("TEL", "19", new TypeSet("X-NEC-PHS"))
+ .addExpectedNodeWithOrder("TEL", "20", new TypeSet("X-NEC-RESTAURANT"));
+ }
+
+ public void testV21MultipleEntryCase() {
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
+ R.raw.v21_multiple_entry);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0033");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-SECRET")
+ .put(Phone.NUMBER, "9");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-HOTEL")
+ .put(Phone.NUMBER, "10");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-SCHOOL")
+ .put(Phone.NUMBER, "11");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_FAX_HOME)
+ .put(Phone.NUMBER, "12");
+
+ elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0034");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "MODEM")
+ .put(Phone.NUMBER, "13");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_PAGER)
+ .put(Phone.NUMBER, "14");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-FAMILY")
+ .put(Phone.NUMBER, "15");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-GIRL")
+ .put(Phone.NUMBER, "16");
+
+ elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+ .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035")
+ .put(StructuredName.PHONETIC_GIVEN_NAME,
+ "\uFF71\uFF9D\uFF84\uFF9E\uFF73\uFF9B\uFF72\uFF84\uFF9E\u0035");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-BOY")
+ .put(Phone.NUMBER, "17");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-FRIEND")
+ .put(Phone.NUMBER, "18");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-PHS")
+ .put(Phone.NUMBER, "19");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_CUSTOM)
+ .put(Phone.LABEL, "NEC-RESTAURANT")
+ .put(Phone.NUMBER, "20");
+ }
+
+ public void testIgnoreAgentV21_Parse() {
+ mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
+ ContentValues contentValuesForValue = new ContentValues();
+ contentValuesForValue.put("VALUE", "DATE");
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "2.1")
+ .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
+ .addExpectedNodeWithOrder("FN", "Example")
+ .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
+ .addExpectedNodeWithOrder("AGENT", "")
+ .addExpectedNodeWithOrder("X-CLASS", "PUBLIC")
+ .addExpectedNodeWithOrder("X-REDUCTION", "")
+ .addExpectedNodeWithOrder("X-NO", "");
+ }
+
+ public void testIgnoreAgentV21() {
+ mVerifier.initForImportTest(V21, R.raw.v21_winmo_65);
+ ContentValuesVerifier verifier = new ContentValuesVerifier();
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "Example")
+ .put(StructuredName.DISPLAY_NAME, "Example");
+ }
+
+ public void testTolerateInvalidCommentLikeLineV21() {
+ mVerifier.initForImportTest(V21, R.raw.v21_invalid_comment_line);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.GIVEN_NAME, "Conference Call")
+ .put(StructuredName.DISPLAY_NAME, "Conference Call");
+ elem.addExpected(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "This is an (sharp ->#<- sharp) example. "
+ + "This message must NOT be ignored.");
+ }
+
+ public void testPagerV30_Parse() {
+ mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNodeWithOrder("VERSION", "3.0")
+ .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
+ .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com",
+ new TypeSet("WORK", "MSG", "PAGER"));
+ }
+
+ public void testPagerV30() {
+ mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
+ elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "F")
+ .put(StructuredName.MIDDLE_NAME, "M")
+ .put(StructuredName.GIVEN_NAME, "G")
+ .put(StructuredName.DISPLAY_NAME, "G M F");
+ elem.addExpected(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.TYPE, Phone.TYPE_PAGER)
+ .put(Phone.NUMBER, "6101231234@pagersample.com");
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
new file mode 100644
index 0000000..5b60342
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
@@ -0,0 +1,434 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+
+import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
+
+import java.util.Arrays;
+
+public class VCardJapanizationTests extends VCardTestsBase {
+ private void testNameUtf8Common(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+ ContentValues contentValues =
+ (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ contentValues)
+ .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+ Arrays.asList(
+ "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+ null, contentValues, null, null);
+ }
+
+ public void testNameUtf8V21() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ }
+
+ public void testNameUtf8V30() {
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ }
+
+ public void testNameShiftJis() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
+ mContentValuesForSJis)
+ .addExpectedNode("N", "\u3075\u308B\u3069;\u3091\u308A\u304B;B;Dr.;Ph.D",
+ Arrays.asList(
+ "\u3075\u308B\u3069", "\u3091\u308A\u304B", "B", "Dr.", "Ph.D"),
+ null, mContentValuesForSJis, null, null);
+ }
+
+ /**
+ * DoCoMo phones require all name elements should be in "family name" field.
+ */
+ public void testNameDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
+ .put(StructuredName.GIVEN_NAME, "\u3091\u308A\u304B")
+ .put(StructuredName.MIDDLE_NAME, "B")
+ .put(StructuredName.PREFIX, "Dr.")
+ .put(StructuredName.SUFFIX, "Ph.D");
+
+ final String fullName = "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D";
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("N", fullName + ";;;;",
+ Arrays.asList(fullName, "", "", "", ""),
+ null, mContentValuesForSJis, null, null)
+ .addExpectedNode("FN", fullName, mContentValuesForSJis)
+ .addExpectedNode("SOUND", ";;;;", new TypeSet("X-IRMC-N"))
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "");
+ }
+
+ private void testPhoneticNameCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ final ContentValues contentValues =
+ (VCardConfig.usesShiftJis(vcardType) ?
+ (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+ mContentValuesForQPAndSJis) :
+ (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
+ contentValues)
+ .addExpectedNode("X-PHONETIC-MIDDLE-NAME",
+ "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0",
+ contentValues)
+ .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
+ contentValues);
+ if (VCardConfig.isV30(vcardType)) {
+ elem.addExpectedNode("SORT-STRING",
+ "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
+ contentValues);
+ }
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046")
+ .put(StructuredName.DISPLAY_NAME,
+ "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 " +
+ "\u305F\u308D\u3046");
+ }
+
+ public void testPhoneticNameForJapaneseV21Utf8() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ }
+
+ public void testPhoneticNameForJapaneseV21Sjis() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ public void testPhoneticNameForJapaneseV30Utf8() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ }
+
+ public void testPhoneticNameForJapaneseV30SJis() {
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ }
+
+ public void testPhoneticNameForMobileV21_1() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME, "\u30DF\u30C9\u30EB\u30CD\u30FC\u30E0")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("SOUND",
+ "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+ "\uFF80\uFF9B\uFF73;;;;",
+ mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+ .put(StructuredName.PHONETIC_MIDDLE_NAME,
+ "\uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+ .put(StructuredName.DISPLAY_NAME,
+ "\uFF94\uFF8F\uFF80\uFF9E \uFF90\uFF84\uFF9E\uFF99\uFF88\uFF70\uFF91 " +
+ "\uFF80\uFF9B\uFF73");
+ }
+
+ public void testPhoneticNameForMobileV21_2() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
+ .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
+
+ mVerifier.addPropertyNodesVerifierElem()
+ .addExpectedNode("SOUND", "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73;;;;",
+ mContentValuesForSJis, new TypeSet("X-IRMC-N"));
+ ContentValuesBuilder builder = mVerifier.addContentValuesVerifierElem()
+ .addExpected(StructuredName.CONTENT_ITEM_TYPE);
+ builder.put(StructuredName.PHONETIC_FAMILY_NAME, "\uFF94\uFF8F\uFF80\uFF9E")
+ .put(StructuredName.PHONETIC_GIVEN_NAME, "\uFF80\uFF9B\uFF73")
+ .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
+ }
+
+ private void testPostalAddressWithJapaneseCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+ .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+ .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+ .put(StructuredPostal.POSTCODE, "494-1313")
+ .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u3053\u3093\u306A\u3068\u3053\u308D\u3092\u898B"
+ + "\u308B\u306A\u3093\u3066\u6687\u4EBA\u3067\u3059\u304B\uFF1F")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
+
+ ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
+ (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
+ mContentValuesForQPAndSJis) :
+ (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
+ mContentValuesForQPAndUtf8));
+
+ PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
+ // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
+ // same as that in vCard 3.0, which can be changed in the future.
+ elem.addExpectedNode("ADR", Arrays.asList("\u79C1\u66F8\u7BB107",
+ "", "\u96DB\u898B\u6CA2\u6751", "\u9E7F\u9AA8\u5E02", "\u00D7\u00D7\u770C",
+ "494-1313", "\u65E5\u672C"),
+ contentValues);
+ mVerifier.addContentValuesVerifierElem().addExpected(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.STREET, "\u96DB\u898B\u6CA2\u6751")
+ .put(StructuredPostal.CITY, "\u9E7F\u9AA8\u5E02")
+ .put(StructuredPostal.REGION, "\u00D7\u00D7\u770C")
+ .put(StructuredPostal.POSTCODE, "494-1313")
+ .put(StructuredPostal.COUNTRY, "\u65E5\u672C")
+ .put(StructuredPostal.FORMATTED_ADDRESS,
+ "\u65E5\u672C 494-1313 \u00D7\u00D7\u770C \u9E7F\u9AA8\u5E02 " +
+ "\u96DB\u898B\u6CA2\u6751 " + "\u79C1\u66F8\u7BB107")
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
+ }
+ public void testPostalAddresswithJapaneseV21() {
+ testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ }
+
+ /**
+ * Verifies that only one address field is emitted toward DoCoMo phones.
+ * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
+ */
+ public void testPostalAdrressForDoCoMo_1() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.POBOX, "3");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom")
+ .put(StructuredPostal.POBOX, "4");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ public void testPostalAdrressForDoCoMo_2() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom")
+ .put(StructuredPostal.POBOX, "3");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("2", "", "", "", "", "", ""), new TypeSet("WORK"));
+ }
+
+ public void testPostalAdrressForDoCoMo_3() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom1")
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
+ .put(StructuredPostal.LABEL, "custom2")
+ .put(StructuredPostal.POBOX, "3");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", Arrays.asList("2", "", "", "", "", "", ""));
+ }
+
+ /**
+ * Verifies the vCard exporter tolerates null TYPE.
+ */
+ public void testPostalAdrressForDoCoMo_4() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "1");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
+ .put(StructuredPostal.POBOX, "2");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME)
+ .put(StructuredPostal.POBOX, "3");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
+ .put(StructuredPostal.POBOX, "4");
+ entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
+ .put(StructuredPostal.POBOX, "5");
+
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR",
+ Arrays.asList("3", "", "", "", "", "", ""), new TypeSet("HOME"));
+ }
+
+ private void testJapanesePhoneNumberCommon(int vcardType) {
+ mVerifier.initForExportTest(vcardType);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "0312341234")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "09012341234")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
+ }
+
+ public void testJapanesePhoneNumberV21_1() {
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ }
+
+ public void testJapanesePhoneNumberV30() {
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ }
+
+ public void testJapanesePhoneNumberDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "0312341234")
+ .put(Phone.TYPE, Phone.TYPE_HOME);
+ entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
+ .put(Phone.NUMBER, "09012341234")
+ .put(Phone.TYPE, Phone.TYPE_MOBILE);
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "03-1234-1234", new TypeSet("HOME"))
+ .addExpectedNode("TEL", "090-1234-1234", new TypeSet("CELL"));
+ }
+
+ public void testNoteDoCoMo() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ ContactEntry entry = mVerifier.addInputEntry();
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note1");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note2");
+ entry.addContentValues(Note.CONTENT_ITEM_TYPE)
+ .put(Note.NOTE, "note3");
+
+ // More than one note fields must be aggregated into one note.
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("TEL", "", new TypeSet("HOME"))
+ .addExpectedNode("EMAIL", "", new TypeSet("HOME"))
+ .addExpectedNode("X-CLASS", "PUBLIC")
+ .addExpectedNode("X-REDUCTION", "")
+ .addExpectedNode("X-NO", "")
+ .addExpectedNode("X-DCM-HMN-MODE", "")
+ .addExpectedNode("ADR", "", new TypeSet("HOME"))
+ .addExpectedNode("NOTE", "note1\nnote2\nnote3", mContentValuesForQP);
+ }
+
+ public void testAndroidCustomV21() {
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
+ .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
+ mVerifier.addPropertyNodesVerifierElemWithEmptyName()
+ .addExpectedNode("X-ANDROID-CUSTOM",
+ Arrays.asList(Nickname.CONTENT_ITEM_TYPE,
+ "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC",
+ "", "", "", "", "", "", "", "", "", "", "", "", "", ""),
+ mContentValuesForQPAndUtf8);
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
new file mode 100644
index 0000000..0857e0c
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
@@ -0,0 +1,169 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.EntityIterator;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.IBulkCursor;
+import android.database.IContentObserver;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.pim.vcard.VCardConfig;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this
+ * class extends ContentProvider, not implementing IContentProvider,
+ * so that MockContentResolver is able to accept this class :(
+ */
+class MockContentProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public int bulkInsert(Uri url, ContentValues[] initialValues) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @SuppressWarnings("unused")
+ public IBulkCursor bulkQuery(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, IContentObserver observer,
+ CursorWindow window) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ public int delete(Uri url, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public String getType(Uri url) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri url, String mode) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public AssetFileDescriptor openAssetFile(Uri uri, String mode) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+}
+
+/**
+ * BaseClass for vCard unit tests with utility classes.
+ * Please do not add each unit test here.
+ */
+/* package */ class VCardTestsBase extends AndroidTestCase {
+ public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8;
+ public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+
+ // Do not modify these during tests.
+ protected final ContentValues mContentValuesForQP;
+ protected final ContentValues mContentValuesForSJis;
+ protected final ContentValues mContentValuesForUtf8;
+ protected final ContentValues mContentValuesForQPAndSJis;
+ protected final ContentValues mContentValuesForQPAndUtf8;
+ protected final ContentValues mContentValuesForBase64V21;
+ protected final ContentValues mContentValuesForBase64V30;
+
+ protected VCardVerifier mVerifier;
+ private boolean mSkipVerification;
+
+ public VCardTestsBase() {
+ super();
+ mContentValuesForQP = new ContentValues();
+ mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForSJis = new ContentValues();
+ mContentValuesForSJis.put("CHARSET", "SHIFT_JIS");
+ mContentValuesForUtf8 = new ContentValues();
+ mContentValuesForUtf8.put("CHARSET", "UTF-8");
+ mContentValuesForQPAndSJis = new ContentValues();
+ mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS");
+ mContentValuesForQPAndUtf8 = new ContentValues();
+ mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE");
+ mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8");
+ mContentValuesForBase64V21 = new ContentValues();
+ mContentValuesForBase64V21.put("ENCODING", "BASE64");
+ mContentValuesForBase64V30 = new ContentValues();
+ mContentValuesForBase64V30.put("ENCODING", "b");
+ }
+
+ @Override
+ public void testAndroidTestCaseSetupProperly() {
+ super.testAndroidTestCaseSetupProperly();
+ mSkipVerification = true;
+ }
+
+ @Override
+ public void setUp() throws Exception{
+ super.setUp();
+ mVerifier = new VCardVerifier(this);
+ mSkipVerification = false;
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ if (!mSkipVerification) {
+ mVerifier.verify();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
new file mode 100644
index 0000000..59299f9
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
@@ -0,0 +1,85 @@
+/*
+ * 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.pim.vcard;
+
+import android.pim.vcard.VCardUtils;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+public class VCardUtilsTests extends TestCase {
+ public void testContainsOnlyPrintableAscii() {
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((String)null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((String[])null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii(""));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0x20; i < 0x7F; i++) {
+ builder.append((char)i);
+ }
+ assertTrue(VCardUtils.containsOnlyPrintableAscii(builder.toString()));
+ assertTrue(VCardUtils.containsOnlyPrintableAscii("\r\n"));
+ assertFalse(VCardUtils.containsOnlyPrintableAscii("\u0019"));
+ assertFalse(VCardUtils.containsOnlyPrintableAscii("\u007F"));
+ }
+
+ public void testContainsOnlyNonCrLfPrintableAscii() {
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String)null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((String[])null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(""));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0x20; i < 0x7F; i++) {
+ builder.append((char)i);
+ }
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii(builder.toString()));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u0019"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\u007F"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\r"));
+ assertFalse(VCardUtils.containsOnlyNonCrLfPrintableAscii("\n"));
+ }
+
+ public void testContainsOnlyAlphaDigitHyphen() {
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String)null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((String[])null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen((List<String>)null));
+ assertTrue(VCardUtils.containsOnlyAlphaDigitHyphen(""));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("abcdefghijklmnopqrstuvwxyz"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
+ assertTrue(VCardUtils.containsOnlyNonCrLfPrintableAscii("0123456789-"));
+ for (int i = 0; i < 0x30; i++) {
+ if (i == 0x2D) { // -
+ continue;
+ }
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x3A; i < 0x41; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x5B; i < 0x61; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ for (int i = 0x7B; i < 0x100; i++) {
+ assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i)));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
new file mode 100644
index 0000000..bfc3158
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
@@ -0,0 +1,306 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.EntityIterator;
+import android.net.Uri;
+import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntryConstructor;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardInterpreterCollection;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContext;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/* package */ class CustomMockContext extends MockContext {
+ final ContentResolver mResolver;
+ public CustomMockContext(ContentResolver resolver) {
+ mResolver = resolver;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+}
+
+/* package */ class VCardVerifier {
+ private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
+ public boolean onInit(Context context) {
+ return true;
+ }
+ public boolean onEntryCreated(String vcard) {
+ verifyOneVCard(vcard);
+ return true;
+ }
+ public void onTerminate() {
+ }
+ }
+
+ private final AndroidTestCase mTestCase;
+ private final VCardVerifierInternal mVCardVerifierInternal;
+ private int mVCardType;
+ private boolean mIsV30;
+ private boolean mIsDoCoMo;
+
+ // Only one of them must be non-empty.
+ private ExportTestResolver mExportTestResolver;
+ private InputStream mInputStream;
+
+ // To allow duplication, use list instead of set.
+ // When null, we don't need to do the verification.
+ private PropertyNodesVerifier mPropertyNodesVerifier;
+ private LineVerifier mLineVerifier;
+ private ContentValuesVerifier mContentValuesVerifier;
+ private boolean mInitialized;
+ private boolean mVerified = false;
+
+ public VCardVerifier(AndroidTestCase androidTestCase) {
+ mTestCase = androidTestCase;
+ mVCardVerifierInternal = new VCardVerifierInternal();
+ mExportTestResolver = null;
+ mInputStream = null;
+ mInitialized = false;
+ mVerified = false;
+ }
+
+ public void initForExportTest(int vcardType) {
+ if (mInitialized) {
+ mTestCase.fail("Already initialized");
+ }
+ mExportTestResolver = new ExportTestResolver(mTestCase);
+ mVCardType = vcardType;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ mInitialized = true;
+ }
+
+ public void initForImportTest(int vcardType, int resId) {
+ if (mInitialized) {
+ mTestCase.fail("Already initialized");
+ }
+ mVCardType = vcardType;
+ mIsV30 = VCardConfig.isV30(vcardType);
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ setInputResourceId(resId);
+ mInitialized = true;
+ }
+
+ private void setInputResourceId(int resId) {
+ InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
+ if (inputStream == null) {
+ mTestCase.fail("Wrong resId: " + resId);
+ }
+ setInputStream(inputStream);
+ }
+
+ private void setInputStream(InputStream inputStream) {
+ if (mExportTestResolver != null) {
+ mTestCase.fail("addInputEntry() is called.");
+ } else if (mInputStream != null) {
+ mTestCase.fail("InputStream is already set");
+ }
+ mInputStream = inputStream;
+ }
+
+ public ContactEntry addInputEntry() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mInputStream != null) {
+ mTestCase.fail("setInputStream is called");
+ }
+ return mExportTestResolver.addInputContactEntry();
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mPropertyNodesVerifier == null) {
+ mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
+ }
+ PropertyNodesVerifierElem elem =
+ mPropertyNodesVerifier.addPropertyNodesVerifierElem();
+ elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
+
+ return elem;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+ if (mIsV30) {
+ elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
+ } else if (mIsDoCoMo) {
+ elem.addExpectedNodeWithOrder("N", "");
+ }
+ return elem;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mLineVerifier == null) {
+ mLineVerifier = new LineVerifier(mTestCase, mVCardType);
+ }
+ return mLineVerifier.addLineVerifierElem();
+ }
+
+ public ContentValuesVerifierElem addContentValuesVerifierElem() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized");
+ }
+ if (mContentValuesVerifier == null) {
+ mContentValuesVerifier = new ContentValuesVerifier();
+ }
+
+ return mContentValuesVerifier.addElem(mTestCase);
+ }
+
+ private void verifyOneVCard(final String vcard) {
+ // Log.d("@@@", vcard);
+ final VCardInterpreter builder;
+ if (mContentValuesVerifier != null) {
+ final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
+ final VCardEntryConstructor vcardDataBuilder =
+ new VCardEntryConstructor(mVCardType);
+ vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
+ if (mPropertyNodesVerifier != null) {
+ builder = new VCardInterpreterCollection(Arrays.asList(
+ mPropertyNodesVerifier, vcardDataBuilder));
+ } else {
+ builder = vnodeBuilder;
+ }
+ } else {
+ if (mPropertyNodesVerifier != null) {
+ builder = mPropertyNodesVerifier;
+ } else {
+ return;
+ }
+ }
+
+ final VCardParser parser =
+ (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
+ InputStream is = null;
+ try {
+ String charset =
+ (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
+ is = new ByteArrayInputStream(vcard.getBytes(charset));
+ mTestCase.assertEquals(true, parser.parse(is, null, builder));
+ } catch (IOException e) {
+ mTestCase.fail("Unexpected IOException: " + e.getMessage());
+ } catch (VCardException e) {
+ mTestCase.fail("Unexpected VCardException: " + e.getMessage());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public void verify() {
+ if (!mInitialized) {
+ mTestCase.fail("Not initialized.");
+ }
+ if (mVerified) {
+ mTestCase.fail("verify() was called twice.");
+ }
+ if (mInputStream != null) {
+ try {
+ verifyForImportTest();
+ } catch (IOException e) {
+ mTestCase.fail("IOException was thrown: " + e.getMessage());
+ } catch (VCardException e) {
+ mTestCase.fail("VCardException was thrown: " + e.getMessage());
+ }
+ } else if (mExportTestResolver != null){
+ verifyForExportTest();
+ } else {
+ mTestCase.fail("No input is determined");
+ }
+ mVerified = true;
+ }
+
+ private void verifyForImportTest() throws IOException, VCardException {
+ if (mLineVerifier != null) {
+ mTestCase.fail("Not supported now.");
+ }
+ if (mContentValuesVerifier != null) {
+ mContentValuesVerifier.verify(mInputStream, mVCardType);
+ }
+ }
+
+ public static EntityIterator mockGetEntityIteratorMethod(
+ final ContentResolver resolver,
+ final Uri uri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ final ContentProvider provider =
+ resolver.acquireContentProviderClient(uri).getLocalContentProvider();
+ return ((ExportTestProvider)provider).queryEntities(
+ uri, selection, selectionArgs, sortOrder);
+ }
+
+ private Method getMockGetEntityIteratorMethod()
+ throws SecurityException, NoSuchMethodException {
+ return this.getClass().getMethod("mockGetEntityIteratorMethod",
+ ContentResolver.class, Uri.class, String.class, String[].class, String.class);
+ }
+
+ private void verifyForExportTest() {
+ final VCardComposer composer =
+ new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
+ composer.addHandler(mLineVerifier);
+ composer.addHandler(mVCardVerifierInternal);
+ if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+ mTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
+ }
+ mTestCase.assertFalse(composer.isAfterLast());
+ try {
+ while (!composer.isAfterLast()) {
+ try {
+ final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
+ mTestCase.assertTrue(
+ composer.createOneEntry(getMockGetEntityIteratorMethod()));
+ } catch (Exception e) {
+ e.printStackTrace();
+ mTestCase.fail();
+ }
+ }
+ } finally {
+ composer.terminate();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/VNode.java
new file mode 100644
index 0000000..79f10dc
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VNode.java
@@ -0,0 +1,30 @@
+/*
+ * 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.pim.vcard;
+
+import java.util.ArrayList;
+
+/**
+ * Previously used in main vCard handling code but now exists only for testing.
+ */
+public class VNode {
+ public String VName;
+
+ public ArrayList<PropertyNode> propList = new ArrayList<PropertyNode>();
+
+ /** 0:parse over. 1:parsing. */
+ public int parseStatus = 1;
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
new file mode 100644
index 0000000..0e6c325
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
@@ -0,0 +1,314 @@
+/*
+ * 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.pim.vcard;
+
+import android.content.ContentValues;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardConfig;
+import android.util.CharsetUtils;
+import android.util.Log;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Store the parse result to custom datastruct: VNode, PropertyNode
+ * Maybe several vcard instance, so use vNodeList to store.
+ * VNode: standy by a vcard instance.
+ * PropertyNode: standy by a property line of a card.
+ *
+ * Previously used in main vCard handling code but now exists only for testing.
+ */
+public class VNodeBuilder implements VCardInterpreter {
+ static private String LOG_TAG = "VNodeBuilder";
+
+ /**
+ * If there's no other information available, this class uses this charset for encoding
+ * byte arrays.
+ */
+ static public String TARGET_CHARSET = "UTF-8";
+
+ /** type=VNode */
+ public List<VNode> vNodeList = new ArrayList<VNode>();
+ private int mNodeListPos = 0;
+ private VNode mCurrentVNode;
+ private PropertyNode mCurrentPropNode;
+ private String mCurrentParamType;
+
+ /**
+ * The charset using which VParser parses the text.
+ */
+ private String mSourceCharset;
+
+ /**
+ * The charset with which byte array is encoded to String.
+ */
+ private String mTargetCharset;
+
+ private boolean mStrictLineBreakParsing;
+
+ public VNodeBuilder() {
+ this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
+ }
+
+ public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
+ this(null, charset, strictLineBreakParsing);
+ }
+
+ /**
+ * @hide sourceCharset is temporal.
+ */
+ public VNodeBuilder(String sourceCharset, String targetCharset,
+ boolean strictLineBreakParsing) {
+ if (sourceCharset != null) {
+ mSourceCharset = sourceCharset;
+ } else {
+ mSourceCharset = VCardConfig.DEFAULT_CHARSET;
+ }
+ if (targetCharset != null) {
+ mTargetCharset = targetCharset;
+ } else {
+ mTargetCharset = TARGET_CHARSET;
+ }
+ mStrictLineBreakParsing = strictLineBreakParsing;
+ }
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ // Note: I guess that this code assumes the Record may nest like this:
+ // START:VPOS
+ // ...
+ // START:VPOS2
+ // ...
+ // END:VPOS2
+ // ...
+ // END:VPOS
+ //
+ // However the following code has a bug.
+ // When error occurs after calling startRecord(), the entry which is probably
+ // the cause of the error remains to be in vNodeList, while endRecord() is not called.
+ //
+ // I leave this code as is since I'm not familiar with vcalendar specification.
+ // But I believe we should refactor this code in the future.
+ // Until this, the last entry has to be removed when some error occurs.
+ public void startEntry() {
+ VNode vnode = new VNode();
+ vnode.parseStatus = 1;
+ vnode.VName = "VCARD";
+ // I feel this should be done in endRecord(), but it cannot be done because of
+ // the reason above.
+ vNodeList.add(vnode);
+ mNodeListPos = vNodeList.size() - 1;
+ mCurrentVNode = vNodeList.get(mNodeListPos);
+ }
+
+ public void endEntry() {
+ VNode endNode = vNodeList.get(mNodeListPos);
+ endNode.parseStatus = 0;
+ while(mNodeListPos > 0){
+ mNodeListPos--;
+ if((vNodeList.get(mNodeListPos)).parseStatus == 1)
+ break;
+ }
+ mCurrentVNode = vNodeList.get(mNodeListPos);
+ }
+
+ public void startProperty() {
+ mCurrentPropNode = new PropertyNode();
+ }
+
+ public void endProperty() {
+ mCurrentVNode.propList.add(mCurrentPropNode);
+ }
+
+ public void propertyName(String name) {
+ mCurrentPropNode.propName = name;
+ }
+
+ // Used only in VCard.
+ public void propertyGroup(String group) {
+ mCurrentPropNode.propGroupSet.add(group);
+ }
+
+ public void propertyParamType(String type) {
+ mCurrentParamType = type;
+ }
+
+ public void propertyParamValue(String value) {
+ if (mCurrentParamType == null ||
+ mCurrentParamType.equalsIgnoreCase("TYPE")) {
+ mCurrentPropNode.paramMap_TYPE.add(value);
+ } else {
+ mCurrentPropNode.paramMap.put(mCurrentParamType, value);
+ }
+
+ mCurrentParamType = null;
+ }
+
+ private String encodeString(String originalString, String targetCharset) {
+ if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ Charset charset = Charset.forName(mSourceCharset);
+ ByteBuffer byteBuffer = charset.encode(originalString);
+ // byteBuffer.array() "may" return byte array which is larger than
+ // byteBuffer.remaining(). Here, we keep on the safe side.
+ byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ try {
+ return new String(bytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return null;
+ }
+ }
+
+ private String handleOneValue(String value, String targetCharset, String encoding) {
+ if (encoding != null) {
+ encoding = encoding.toUpperCase();
+ if (encoding.equals("BASE64") || encoding.equals("B")) {
+ // Assume BASE64 is used only when the number of values is 1.
+ mCurrentPropNode.propValue_bytes =
+ Base64.decodeBase64(value.getBytes());
+ return value;
+ } else if (encoding.equals("QUOTED-PRINTABLE")) {
+ String quotedPrintable = value
+ .replaceAll("= ", " ").replaceAll("=\t", "\t");
+ String[] lines;
+ if (mStrictLineBreakParsing) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ int length = quotedPrintable.length();
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ char ch = quotedPrintable.charAt(i);
+ if (ch == '\n') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else if (ch == '\r') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ if (i < length - 1) {
+ char nextCh = quotedPrintable.charAt(i + 1);
+ if (nextCh == '\n') {
+ i++;
+ }
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ String finalLine = builder.toString();
+ if (finalLine.length() > 0) {
+ list.add(finalLine);
+ }
+ lines = list.toArray(new String[0]);
+ }
+ StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ if (line.endsWith("=")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ builder.append(line);
+ }
+ byte[] bytes;
+ try {
+ bytes = builder.toString().getBytes(mSourceCharset);
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+ bytes = builder.toString().getBytes();
+ }
+
+ try {
+ bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
+ } catch (DecoderException e) {
+ Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
+ return "";
+ }
+
+ try {
+ return new String(bytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
+ }
+ }
+ // Unknown encoding. Fall back to default.
+ }
+ return encodeString(value, targetCharset);
+ }
+
+ public void propertyValues(List<String> values) {
+ if (values == null || values.size() == 0) {
+ mCurrentPropNode.propValue_bytes = null;
+ mCurrentPropNode.propValue_vector.clear();
+ mCurrentPropNode.propValue_vector.add("");
+ mCurrentPropNode.propValue = "";
+ return;
+ }
+
+ ContentValues paramMap = mCurrentPropNode.paramMap;
+
+ String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
+ String encoding = paramMap.getAsString("ENCODING");
+
+ if (targetCharset == null || targetCharset.length() == 0) {
+ targetCharset = mTargetCharset;
+ }
+
+ for (String value : values) {
+ mCurrentPropNode.propValue_vector.add(
+ handleOneValue(value, targetCharset, encoding));
+ }
+
+ mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
+ }
+
+ private String listToString(List<String> list){
+ int size = list.size();
+ if (size > 1) {
+ StringBuilder typeListB = new StringBuilder();
+ for (String type : list) {
+ typeListB.append(type).append(";");
+ }
+ int len = typeListB.length();
+ if (len > 0 && typeListB.charAt(len - 1) == ';') {
+ return typeListB.substring(0, len - 1);
+ }
+ return typeListB.toString();
+ } else if (size == 1) {
+ return list.get(0);
+ } else {
+ return "";
+ }
+ }
+
+ public String getResult(){
+ return null;
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
new file mode 100644
index 0000000..370ae78
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/** Unit test for SettingsProvider. */
+public class SettingsProviderTest extends AndroidTestCase {
+ @MediumTest
+ public void testNameValueCache() {
+ ContentResolver r = getContext().getContentResolver();
+ Settings.Secure.putString(r, "test_service", "Value");
+ assertEquals("Value", Settings.Secure.getString(r, "test_service"));
+
+ // Make sure the value can be overwritten.
+ Settings.Secure.putString(r, "test_service", "New");
+ assertEquals("New", Settings.Secure.getString(r, "test_service"));
+
+ // Also that delete works.
+ assertEquals(1, r.delete(Settings.Secure.getUriFor("test_service"), null, null));
+ assertEquals(null, Settings.Secure.getString(r, "test_service"));
+
+ // Try all the same things in the System table
+ Settings.System.putString(r, "test_setting", "Value");
+ assertEquals("Value", Settings.System.getString(r, "test_setting"));
+
+ Settings.System.putString(r, "test_setting", "New");
+ assertEquals("New", Settings.System.getString(r, "test_setting"));
+
+ assertEquals(1, r.delete(Settings.System.getUriFor("test_setting"), null, null));
+ assertEquals(null, Settings.System.getString(r, "test_setting"));
+ }
+
+ @MediumTest
+ public void testRowNameContentUri() {
+ ContentResolver r = getContext().getContentResolver();
+
+ assertEquals("content://settings/system/test_setting",
+ Settings.System.getUriFor("test_setting").toString());
+ assertEquals("content://settings/secure/test_service",
+ Settings.Secure.getUriFor("test_service").toString());
+
+ // These tables use the row name (not ID) as their content URI.
+ Uri tables[] = { Settings.System.CONTENT_URI, Settings.Secure.CONTENT_URI };
+ for (Uri table : tables) {
+ ContentValues v = new ContentValues();
+ v.put(Settings.System.NAME, "test_key");
+ v.put(Settings.System.VALUE, "Test");
+ Uri uri = r.insert(table, v);
+ assertEquals(table.toString() + "/test_key", uri.toString());
+
+ // Query with a specific URI and no WHERE clause succeeds.
+ Cursor c = r.query(uri, null, null, null, null);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME)));
+ assertEquals("Test", c.getString(c.getColumnIndex(Settings.System.VALUE)));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ // Query with a specific URI and a WHERE clause fails.
+ try {
+ r.query(uri, null, "1", null, null);
+ fail("UnsupportedOperationException expected");
+ } catch (UnsupportedOperationException e) {
+ if (!e.toString().contains("WHERE clause")) throw e;
+ }
+
+ // Query with a tablewide URI and a WHERE clause succeeds.
+ c = r.query(table, null, "name='test_key'", null, null);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME)));
+ assertEquals("Test", c.getString(c.getColumnIndex(Settings.System.VALUE)));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ v = new ContentValues();
+ v.put(Settings.System.VALUE, "Toast");
+ assertEquals(1, r.update(uri, v, null, null));
+
+ c = r.query(uri, null, null, null, null);
+ try {
+ assertTrue(c.moveToNext());
+ assertEquals("test_key", c.getString(c.getColumnIndex(Settings.System.NAME)));
+ assertEquals("Toast", c.getString(c.getColumnIndex(Settings.System.VALUE)));
+ assertFalse(c.moveToNext());
+ } finally {
+ c.close();
+ }
+
+ assertEquals(1, r.delete(uri, null, null));
+ }
+
+ assertEquals(null, Settings.System.getString(r, "test_key"));
+ assertEquals(null, Settings.Secure.getString(r, "test_key"));
+ }
+
+ @MediumTest
+ public void testRowNumberContentUri() {
+ ContentResolver r = getContext().getContentResolver();
+
+ // The bookmarks table (and everything else) uses standard row number content URIs.
+ Uri uri = Settings.Bookmarks.add(r, new Intent("TEST"),
+ "Test Title", "Test Folder", '*', 123);
+
+ assertTrue(ContentUris.parseId(uri) > 0);
+
+ assertEquals("TEST", Settings.Bookmarks.getIntentForShortcut(r, '*').getAction());
+
+ ContentValues v = new ContentValues();
+ v.put(Settings.Bookmarks.INTENT, "#Intent;action=TOAST;end");
+ assertEquals(1, r.update(uri, v, null, null));
+
+ assertEquals("TOAST", Settings.Bookmarks.getIntentForShortcut(r, '*').getAction());
+
+ assertEquals(1, r.delete(uri, null, null));
+
+ assertEquals(null, Settings.Bookmarks.getIntentForShortcut(r, '*'));
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SmsProviderTest.java b/core/tests/coretests/src/android/provider/SmsProviderTest.java
new file mode 100644
index 0000000..c8ed728
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SmsProviderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.provider;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony.Sms;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.GregorianCalendar;
+
+public class SmsProviderTest extends AndroidTestCase {
+
+ @LargeTest
+ public void testProvider() throws Exception {
+ // This test does the following
+ // 1. Insert 10 messages from the same number at different times.
+ //
+ // . Delete the messages and make sure that they were deleted.
+
+ long now = System.currentTimeMillis();
+
+ Uri[] urls = new Uri[10];
+ String[] dates = new String[]{
+ Long.toString(new GregorianCalendar(1970, 1, 1, 0, 0, 0).getTimeInMillis()),
+ Long.toString(new GregorianCalendar(1971, 2, 13, 16, 35, 3).getTimeInMillis()),
+ Long.toString(new GregorianCalendar(1978, 10, 22, 0, 1, 0).getTimeInMillis()),
+ Long.toString(new GregorianCalendar(1980, 1, 11, 10, 22, 30).getTimeInMillis()),
+ Long.toString(now - (5 * 24 * 60 * 60 * 1000)),
+ Long.toString(now - (2 * 24 * 60 * 60 * 1000)),
+ Long.toString(now - (5 * 60 * 60 * 1000)),
+ Long.toString(now - (30 * 60 * 1000)),
+ Long.toString(now - (5 * 60 * 1000)),
+ Long.toString(now)
+ };
+
+ ContentValues map = new ContentValues();
+ map.put("address", "+15045551337");
+ map.put("read", 0);
+
+ ContentResolver contentResolver = mContext.getContentResolver();
+
+ for (int i = 0; i < urls.length; i++) {
+ map.put("body", "Test " + i + " !");
+ map.put("date", dates[i]);
+ urls[i] = contentResolver.insert(Sms.Inbox.CONTENT_URI, map);
+ assertNotNull(urls[i]);
+ }
+
+ Cursor c = contentResolver.query(Sms.Inbox.CONTENT_URI, null, null, null, "date");
+
+ //DatabaseUtils.dumpCursor(c);
+
+ for (Uri url : urls) {
+ int count = contentResolver.delete(url, null, null);
+ assertEquals(1, count);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/HtmlTest.java b/core/tests/coretests/src/android/text/HtmlTest.java
new file mode 100644
index 0000000..c07d212
--- /dev/null
+++ b/core/tests/coretests/src/android/text/HtmlTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.QuoteSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+
+import junit.framework.TestCase;
+
+public class HtmlTest extends TestCase {
+
+ @MediumTest
+ public void testSingleTagOnWhileString() {
+ Spanned spanned = Html.fromHtml("<b>hello</b>");
+ Object[] spans = spanned.getSpans(-1, 100, Object.class);
+ assertEquals(1, spans.length);
+ Object span = spans[0];
+ assertEquals(0, spanned.getSpanStart(span));
+ assertEquals(5, spanned.getSpanEnd(span));
+ }
+
+ @MediumTest
+ public void testEmptyFontTag() {
+ Spanned spanned = Html.fromHtml("Hello <font color=\"#ff00ff00\"></font>");
+ Object[] spans = spanned.getSpans(0, 100, Object.class);
+ // TODO: figure out what the spans should be after the crashes are fixed and assert them.
+ }
+
+ /** Tests that the parser can handle mal-formed HTML. */
+ @MediumTest
+ public void testBadHtml() {
+ Spanned spanned = Html.fromHtml("Hello <b>b<i>bi</b>i</i>");
+ Object[] spans = spanned.getSpans(0, 100, Object.class);
+ assertEquals(Typeface.ITALIC, ((StyleSpan) spans[0]).getStyle());
+ assertEquals(7, spanned.getSpanStart(spans[0]));
+ assertEquals(9, spanned.getSpanEnd(spans[0]));
+ assertEquals(Typeface.BOLD, ((StyleSpan) spans[1]).getStyle());
+ assertEquals(6, spanned.getSpanStart(spans[1]));
+ assertEquals(9, spanned.getSpanEnd(spans[1]));
+ assertEquals(Typeface.ITALIC, ((StyleSpan) spans[2]).getStyle());
+ assertEquals(9, spanned.getSpanStart(spans[2]));
+ assertEquals(10, spanned.getSpanEnd(spans[2]));
+ }
+
+ @MediumTest
+ public void testSymbols() {
+ String spanned = Html.fromHtml("&copy; &gt; &lt").toString();
+ assertEquals("\u00a9 > <", spanned);
+ }
+
+ @MediumTest
+ public void testColor() throws Exception {
+ Spanned s;
+ ForegroundColorSpan[] colors;
+
+ s = Html.fromHtml("<font color=\"#00FF00\">something</font>");
+ colors = s.getSpans(0, s.length(), ForegroundColorSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(0xFF00FF00, colors[0].getForegroundColor());
+
+ s = Html.fromHtml("<font color=\"navy\">something</font>");
+ colors = s.getSpans(0, s.length(), ForegroundColorSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(0xFF000080, colors[0].getForegroundColor());
+
+ s = Html.fromHtml("<font color=\"gibberish\">something</font>");
+ colors = s.getSpans(0, s.length(), ForegroundColorSpan.class);
+ assertEquals(0, colors.length);
+ }
+
+ @MediumTest
+ public void testResourceColor() throws Exception {
+ ColorStateList c =
+ Resources.getSystem().getColorStateList(android.R.color.primary_text_dark);
+ Spanned s;
+ TextAppearanceSpan[] colors;
+
+ s = Html.fromHtml("<font color=\"@android:color/primary_text_dark\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(c.toString(), colors[0].getTextColor().toString());
+
+ s = Html.fromHtml("<font color=\"@android:primary_text_dark\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(c.toString(), colors[0].getTextColor().toString());
+
+ s = Html.fromHtml("<font color=\"@color/primary_text_dark\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(c.toString(), colors[0].getTextColor().toString());
+
+ s = Html.fromHtml("<font color=\"@primary_text_dark\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(c.toString(), colors[0].getTextColor().toString());
+
+ s = Html.fromHtml("<font color=\"@" + android.R.color.primary_text_dark
+ + "\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(1, colors.length);
+ assertEquals(c.toString(), colors[0].getTextColor().toString());
+
+ s = Html.fromHtml("<font color=\"gibberish\">something</font>");
+ colors = s.getSpans(0, s.length(), TextAppearanceSpan.class);
+ assertEquals(colors.length, 0);
+ }
+
+ @SmallTest
+ public void testParagraphs() throws Exception {
+ SpannableString s;
+
+ s = new SpannableString("Hello world");
+ assertEquals(Html.toHtml(s), "<p>Hello world</p>\n");
+
+ s = new SpannableString("Hello world\nor something");
+ assertEquals(Html.toHtml(s), "<p>Hello world<br>\nor something</p>\n");
+
+ s = new SpannableString("Hello world\n\nor something");
+ assertEquals(Html.toHtml(s), "<p>Hello world</p>\n<p>or something</p>\n");
+
+ s = new SpannableString("Hello world\n\n\nor something");
+ assertEquals(Html.toHtml(s), "<p>Hello world<br></p>\n<p>or something</p>\n");
+
+ assertEquals("foo\nbar", Html.fromHtml("foo<br>bar").toString());
+ assertEquals("foo\nbar", Html.fromHtml("foo<br>\nbar").toString());
+ assertEquals("foo\nbar", Html.fromHtml("foo<br>\n \nbar").toString());
+ }
+
+ @SmallTest
+ public void testBlockquote() throws Exception {
+ SpannableString s;
+
+ s = new SpannableString("Hello world");
+ s.setSpan(new QuoteSpan(), 0, s.length(), Spannable.SPAN_PARAGRAPH);
+ assertEquals(Html.toHtml(s), "<blockquote><p>Hello world</p>\n</blockquote>\n");
+
+ s = new SpannableString("Hello\n\nworld");
+ s.setSpan(new QuoteSpan(), 0, 7, Spannable.SPAN_PARAGRAPH);
+ assertEquals(Html.toHtml(s), "<blockquote><p>Hello</p>\n</blockquote>\n<p>world</p>\n");
+ }
+
+ @SmallTest
+ public void testEntities() throws Exception {
+ SpannableString s;
+
+ s = new SpannableString("Hello <&> world");
+ assertEquals(Html.toHtml(s), "<p>Hello &lt;&amp;&gt; world</p>\n");
+
+ s = new SpannableString("Hello \u03D5 world");
+ assertEquals(Html.toHtml(s), "<p>Hello &#981; world</p>\n");
+
+ s = new SpannableString("Hello world");
+ assertEquals(Html.toHtml(s), "<p>Hello&nbsp; world</p>\n");
+ }
+
+ @SmallTest
+ public void testMarkup() throws Exception {
+ SpannableString s;
+
+ s = new SpannableString("Hello bold world");
+ s.setSpan(new StyleSpan(Typeface.BOLD), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <b>bold</b> world</p>\n");
+
+ s = new SpannableString("Hello italic world");
+ s.setSpan(new StyleSpan(Typeface.ITALIC), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <i>italic</i> world</p>\n");
+
+ s = new SpannableString("Hello monospace world");
+ s.setSpan(new TypefaceSpan("monospace"), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <tt>monospace</tt> world</p>\n");
+
+ s = new SpannableString("Hello superscript world");
+ s.setSpan(new SuperscriptSpan(), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <sup>superscript</sup> world</p>\n");
+
+ s = new SpannableString("Hello subscript world");
+ s.setSpan(new SubscriptSpan(), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <sub>subscript</sub> world</p>\n");
+
+ s = new SpannableString("Hello underline world");
+ s.setSpan(new UnderlineSpan(), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <u>underline</u> world</p>\n");
+
+ s = new SpannableString("Hello struck world");
+ s.setSpan(new StrikethroughSpan(), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s), "<p>Hello <strike>struck</strike> world</p>\n");
+
+ s = new SpannableString("Hello linky world");
+ s.setSpan(new URLSpan("http://www.google.com"), 6, s.length() - 6,
+ Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertEquals(Html.toHtml(s),
+ "<p>Hello <a href=\"http://www.google.com\">linky</a> world</p>\n");
+ }
+
+ @SmallTest
+ public void testImg() throws Exception {
+ Spanned s;
+
+ s = Html.fromHtml("yes<img src=\"http://example.com/foo.gif\">no");
+
+ assertEquals("<p>yes<img src=\"http://example.com/foo.gif\">no</p>\n",
+ Html.toHtml(s));
+ }
+
+ @SmallTest
+ public void testUtf8() throws Exception {
+ Spanned s;
+
+ s = Html.fromHtml("<p>\u0124\u00eb\u0142\u0142o, world!</p>");
+ assertEquals("<p>&#292;&#235;&#322;&#322;o, world!</p>\n", Html.toHtml(s));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
new file mode 100644
index 0000000..da920c9
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+
+public class SpannableStringBuilderTest extends SpannableTest {
+
+ protected Spannable newSpannableWithText(String text) {
+ return new SpannableStringBuilder(text);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/SpannableStringTest.java b/core/tests/coretests/src/android/text/SpannableStringTest.java
new file mode 100644
index 0000000..8dd1214
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannableStringTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.text.Spannable;
+import android.text.SpannableString;
+
+public class SpannableStringTest extends SpannableTest {
+
+ protected Spannable newSpannableWithText(String text) {
+ return new SpannableString(text);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
new file mode 100644
index 0000000..46be99d
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.Spannable;
+
+public abstract class SpannableTest extends InstrumentationTestCase {
+
+ protected abstract Spannable newSpannableWithText(String text);
+
+ @MediumTest
+ public void testGetSpans() {
+ Spannable spannable = newSpannableWithText("abcdef");
+ Object emptySpan = new Object();
+ spannable.setSpan(emptySpan, 1, 1, 0);
+ Object unemptySpan = new Object();
+ spannable.setSpan(unemptySpan, 1, 2, 0);
+
+ Object[] spans;
+
+ // Empty spans are included when they merely abut the query region
+ // but other spans are not, unless the query region is empty, in
+ // in which case any abutting spans are returned.
+ spans = spannable.getSpans(0, 1, Object.class);
+ MoreAsserts.assertEquals(new Object[]{emptySpan}, spans);
+ spans = spannable.getSpans(0, 2, Object.class);
+ MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans);
+ spans = spannable.getSpans(1, 2, Object.class);
+ MoreAsserts.assertEquals(new Object[]{emptySpan, unemptySpan}, spans);
+ spans = spannable.getSpans(2, 2, Object.class);
+ MoreAsserts.assertEquals(new Object[]{unemptySpan}, spans);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/SpannedTest.java b/core/tests/coretests/src/android/text/SpannedTest.java
new file mode 100644
index 0000000..1c22cf9
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpannedTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.graphics.Typeface;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.*;
+import android.text.style.*;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * SpannedTest tests some features of Spanned
+ */
+public class SpannedTest extends TestCase {
+ private int mExpect;
+
+ @SmallTest
+ public void testSpannableString() throws Exception {
+ checkPriority(new SpannableString("the quick brown fox"));
+ }
+
+ @SmallTest
+ public void testSpannableStringBuilder() throws Exception {
+ checkPriority2(new SpannableStringBuilder("the quick brown fox"));
+ }
+
+ @SmallTest
+ public void testAppend() throws Exception {
+ Object o = new Object();
+ SpannableString ss = new SpannableString("Test");
+ ss.setSpan(o, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ ssb.append(ss);
+ assertEquals(0, ssb.getSpanStart(o));
+ assertEquals(4, ssb.getSpanEnd(o));
+ assertEquals(1, ssb.getSpans(0, 4, Object.class).length);
+
+ ssb.insert(0, ss);
+ assertEquals(4, ssb.getSpanStart(o));
+ assertEquals(8, ssb.getSpanEnd(o));
+ assertEquals(0, ssb.getSpans(0, 4, Object.class).length);
+ assertEquals(1, ssb.getSpans(4, 8, Object.class).length);
+ }
+
+ @SmallTest
+ public void testWrapParcel() {
+ SpannableString s = new SpannableString("Hello there world");
+ CharacterStyle mark = new StyleSpan(Typeface.BOLD);
+ s.setSpan(mark, 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ s.setSpan(CharacterStyle.wrap(mark), 3, 7,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ s.setSpan(new TextAppearanceSpan("mono", 0, -1, null, null), 7, 8,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ s.setSpan(CharacterStyle.wrap(new TypefaceSpan("mono")), 8, 9,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ Parcel p = Parcel.obtain();
+ TextUtils.writeToParcel(s, p, 0);
+ p.setDataPosition(0);
+
+ Spanned s2 = (Spanned) TextUtils.CHAR_SEQUENCE_CREATOR.
+ createFromParcel(p);
+ StyleSpan[] style;
+
+ style = s2.getSpans(1, 2, StyleSpan.class);
+ assertEquals(1, style.length);
+ assertEquals(1, s2.getSpanStart(style[0]));
+ assertEquals(2, s2.getSpanEnd(style[0]));
+
+ style = s2.getSpans(3, 7, StyleSpan.class);
+ assertEquals(1, style.length);
+ assertEquals(3, s2.getSpanStart(style[0]));
+ assertEquals(7, s2.getSpanEnd(style[0]));
+
+ TextAppearanceSpan[] appearance = s2.getSpans(7, 8,
+ TextAppearanceSpan.class);
+ assertEquals(1, appearance.length);
+ assertEquals(7, s2.getSpanStart(appearance[0]));
+ assertEquals(8, s2.getSpanEnd(appearance[0]));
+
+ TypefaceSpan[] tf = s2.getSpans(8, 9, TypefaceSpan.class);
+ assertEquals(1, tf.length);
+ assertEquals(8, s2.getSpanStart(tf[0]));
+ assertEquals(9, s2.getSpanEnd(tf[0]));
+ }
+
+ private void checkPriority(Spannable s) {
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (5 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (10 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (0 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (15 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (3 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (6 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE |
+ (0 << Spannable.SPAN_PRIORITY_SHIFT));
+
+ Object[] spans = s.getSpans(0, s.length(), Object.class);
+
+ for (int i = 0; i < spans.length - 1; i++) {
+ assertEquals((s.getSpanFlags(spans[i]) & Spanned.SPAN_PRIORITY) >=
+ (s.getSpanFlags(spans[i + 1]) & Spanned.SPAN_PRIORITY),
+ true);
+ }
+
+ mExpect = 0;
+
+ s.setSpan(new Watcher(2), 0, s.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE |
+ (2 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Watcher(4), 0, s.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE |
+ (4 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Watcher(1), 0, s.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE |
+ (1 << Spannable.SPAN_PRIORITY_SHIFT));
+ s.setSpan(new Watcher(3), 0, s.length(),
+ Spannable.SPAN_INCLUSIVE_INCLUSIVE |
+ (3 << Spannable.SPAN_PRIORITY_SHIFT));
+
+ mExpect = 4;
+ s.setSpan(new Object(), 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertEquals(mExpect, 0);
+ }
+
+ private void checkPriority2(SpannableStringBuilder ssb) {
+ checkPriority(ssb);
+
+ mExpect = 4;
+ ssb.insert(3, "something");
+ assertEquals(mExpect, 0);
+ }
+
+ private class Watcher implements SpanWatcher, TextWatcher {
+ private int mSequence;
+
+ public Watcher(int sequence) {
+ mSequence = sequence;
+ }
+
+ public void onSpanChanged(Spannable b, Object o, int s, int e,
+ int st, int en) { }
+ public void onSpanRemoved(Spannable b, Object o, int s, int e) { }
+
+ public void onSpanAdded(Spannable b, Object o, int s, int e) {
+ if (mExpect != 0) {
+ assertEquals(mSequence, mExpect);
+ mExpect = mSequence - 1;
+ }
+ }
+
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) { }
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ if (mExpect != 0) {
+ assertEquals(mSequence, mExpect);
+ mExpect = mSequence - 1;
+ }
+ }
+
+ public void afterTextChanged(Editable s) { }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
new file mode 100644
index 0000000..8e7e63e
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.text;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StaticLayout bidi implementation.
+ */
+public class StaticLayoutBidiTest extends TestCase {
+
+ public static final int REQ_DL = 2; // Layout.DIR_REQUEST_DEFAULT_LTR;
+ public static final int REQ_DR = -2; // Layout.DIR_REQUEST_DEFAULT_RTL;
+ public static final int REQ_L = 1; // Layout.DIR_REQUEST_LTR;
+ public static final int REQ_R = -1; // Layout.DIR_REQUEST_RTL;
+ public static final int L = Layout.DIR_LEFT_TO_RIGHT;
+ public static final int R = Layout.DIR_RIGHT_TO_LEFT;
+
+ public static final String SP = " ";
+ public static final String ALEF = "\u05d0";
+ public static final String BET = "\u05d1";
+ public static final String GIMEL = "\u05d2";
+ public static final String DALET = "\u05d3";
+
+ //@SmallTest
+ public void testAllLtr() {
+ expectBidi(REQ_DL, "a test", "000000", L);
+ }
+
+ //@SmallTest
+ public void testLtrRtl() {
+ expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L);
+ }
+
+ //@SmallTest
+ public void testAllRtl() {
+ expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R);
+ }
+
+ //@SmallTest
+ public void testRtlLtr() {
+ expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R);
+ }
+
+ //@SmallTest
+ public void testRAllLtr() {
+ expectBidi(REQ_R, "a test", "000000", R);
+ }
+
+ //@SmallTest
+ public void testRLtrRtl() {
+ expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R);
+ }
+
+ //@SmallTest
+ public void testLAllRtl() {
+ expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L);
+ }
+
+ //@SmallTest
+ public void testLRtlLtr() {
+ expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L);
+ }
+
+ private void expectBidi(int dir, String text,
+ String expectedLevels, int expectedDir) {
+ char[] chs = text.toCharArray();
+ int n = chs.length;
+ byte[] chInfo = new byte[n];
+
+ int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false);
+
+ {
+ StringBuilder sb = new StringBuilder("info:");
+ for (int i = 0; i < n; ++i) {
+ sb.append(" ").append(String.valueOf(chInfo[i]));
+ }
+ Log.i("BIDI", sb.toString());
+ }
+
+ char[] resultLevelChars = new char[n];
+ for (int i = 0; i < n; ++i) {
+ resultLevelChars[i] = (char)('0' + chInfo[i]);
+ }
+ String resultLevels = new String(resultLevelChars);
+ assertEquals("direction", expectedDir, resultDir);
+ assertEquals("levels", expectedLevels, resultLevels);
+ }
+
+ //@SmallTest
+ public void testNativeBidi() {
+ // native bidi returns levels, not simply directions
+ expectNativeBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111222", R);
+ }
+
+ private void expectNativeBidi(int dir, String text,
+ String expectedLevels, int expectedDir) {
+ char[] chs = text.toCharArray();
+ int n = chs.length;
+ byte[] chInfo = new byte[n];
+
+ int resultDir = AndroidBidi.bidi(dir, chs, chInfo, n, false);
+
+ {
+ StringBuilder sb = new StringBuilder("info:");
+ for (int i = 0; i < n; ++i) {
+ sb.append(" ").append(String.valueOf(chInfo[i]));
+ }
+ Log.i("BIDI", sb.toString());
+ }
+
+ char[] resultLevelChars = new char[n];
+ for (int i = 0; i < n; ++i) {
+ resultLevelChars[i] = (char)('0' + chInfo[i]);
+ }
+ String resultLevels = new String(resultLevelChars);
+ assertEquals("direction", expectedDir, resultDir);
+ assertEquals("levels", expectedLevels, resultLevels);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
new file mode 100644
index 0000000..1f58a2c
--- /dev/null
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.text;
+
+import android.graphics.Paint.FontMetricsInt;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Layout.Alignment;
+import static android.text.Layout.Alignment.*;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ *
+ * Requires disabling access checks in the vm since this calls package-private
+ * APIs.
+ *
+ * @Suppress
+ */
+public class StaticLayoutTest extends TestCase {
+
+ /**
+ * Basic test showing expected behavior and relationship between font
+ * metrics and line metrics.
+ */
+ //@SmallTest
+ public void testGetters1() {
+ LayoutBuilder b = builder();
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ // check default paint
+ Log.i("TG1:paint", fmi.toString());
+
+ Layout l = b.build();
+ assertVertMetrics(l, 0, 0,
+ fmi.ascent, fmi.descent);
+
+ // other quick metrics
+ assertEquals(0, l.getLineStart(0));
+ assertEquals(Layout.DIR_LEFT_TO_RIGHT, l.getParagraphDirection(0));
+ assertEquals(false, l.getLineContainsTab(0));
+ assertEquals(Layout.DIRS_ALL_LEFT_TO_RIGHT, l.getLineDirections(0));
+ assertEquals(0, l.getEllipsisCount(0));
+ assertEquals(0, l.getEllipsisStart(0));
+ assertEquals(b.width, l.getEllipsizedWidth());
+ }
+
+ /**
+ * Basic test showing effect of includePad = true with 1 line.
+ * Top and bottom padding are affected, as is the line descent and height.
+ */
+ //@SmallTest
+ public void testGetters2() {
+ LayoutBuilder b = builder()
+ .setIncludePad(true);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 2 lines.
+ * Ascent of top line and descent of bottom line are affected.
+ */
+ //@SmallTest
+ public void testGetters3() {
+ LayoutBuilder b = builder()
+ .setIncludePad(true)
+ .setWidth(50);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 3 lines.
+ * First line ascent is top, bottom line descent is bottom.
+ */
+ //@SmallTest
+ public void testGetters4() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true wrapping to 3 lines and
+ * large text. See effect of leading. Currently, we don't expect there to
+ * even be non-zero leading.
+ */
+ //@SmallTest
+ public void testGetters5() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(150);
+ b.paint.setTextSize(36);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ if (fmi.leading == 0) { // nothing to test
+ Log.i("TG5", "leading is 0, skipping test");
+ return;
+ }
+
+ // So far, leading is not used, so this is the same as TG4. If we start
+ // using leading, this will fail.
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent,
+ fmi.ascent, fmi.descent,
+ fmi.ascent, fmi.bottom);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 2, wrapping
+ * to 3 lines.
+ */
+ //@SmallTest
+ public void testGetters6() {
+ int spacingAdd = 2; // int so expressions return int
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(spacingAdd);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + spacingAdd,
+ fmi.ascent, fmi.descent + spacingAdd,
+ fmi.ascent, fmi.bottom + spacingAdd);
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 2,
+ * spacingMult = 1.5, wrapping to 3 lines.
+ */
+ //@SmallTest
+ public void testGetters7() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(2)
+ .setSpacingMult(1.5f);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+ Scaler s = new Scaler(b.spacingMult, b.spacingAdd);
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ fmi.ascent, fmi.bottom + s.scale(fmi.bottom - fmi.ascent));
+ }
+
+ /**
+ * Basic test showing effect of includePad = true, spacingAdd = 0,
+ * spacingMult = 0.8 when wrapping to 3 lines.
+ */
+ //@SmallTest
+ public void testGetters8() {
+ LayoutBuilder b = builder()
+ .setText("This is a longer test")
+ .setIncludePad(true)
+ .setWidth(50)
+ .setSpacingAdd(2)
+ .setSpacingMult(.8f);
+ FontMetricsInt fmi = b.paint.getFontMetricsInt();
+ Scaler s = new Scaler(b.spacingMult, b.spacingAdd);
+
+ Layout l = b.build();
+ assertVertMetrics(l, fmi.top - fmi.ascent, fmi.bottom - fmi.descent,
+ fmi.top, fmi.descent + s.scale(fmi.descent - fmi.top),
+ fmi.ascent, fmi.descent + s.scale(fmi.descent - fmi.ascent),
+ fmi.ascent, fmi.bottom + s.scale(fmi.bottom - fmi.ascent));
+ }
+
+ // ----- test utility classes and methods -----
+
+ // Models the effect of the scale and add parameters. I think the current
+ // implementation misbehaves.
+ private static class Scaler {
+ private final float sMult;
+ private final float sAdd;
+
+ Scaler(float sMult, float sAdd) {
+ this.sMult = sMult - 1;
+ this.sAdd = sAdd;
+ }
+
+ public int scale(float height) {
+ int altVal = (int)(height * sMult + sAdd + 0.5);
+ int rndVal = Math.round(height * sMult + sAdd);
+ if (altVal != rndVal) {
+ Log.i("Scale", "expected scale: " + rndVal +
+ " != returned scale: " + altVal);
+ }
+ return rndVal;
+ }
+ }
+
+ private static LayoutBuilder builder() {
+ return new LayoutBuilder();
+ }
+
+ private static class LayoutBuilder {
+ String text = "This is a test";
+ TextPaint paint = new TextPaint(); // default
+ int width = 100;
+ Alignment align = ALIGN_NORMAL;
+ float spacingMult = 1;
+ float spacingAdd = 0;
+ boolean includePad = false;
+
+ LayoutBuilder setText(String text) {
+ this.text = text;
+ return this;
+ }
+
+ LayoutBuilder setPaint(TextPaint paint) {
+ this.paint = paint;
+ return this;
+ }
+
+ LayoutBuilder setWidth(int width) {
+ this.width = width;
+ return this;
+ }
+
+ LayoutBuilder setAlignment(Alignment align) {
+ this.align = align;
+ return this;
+ }
+
+ LayoutBuilder setSpacingMult(float spacingMult) {
+ this.spacingMult = spacingMult;
+ return this;
+ }
+
+ LayoutBuilder setSpacingAdd(float spacingAdd) {
+ this.spacingAdd = spacingAdd;
+ return this;
+ }
+
+ LayoutBuilder setIncludePad(boolean includePad) {
+ this.includePad = includePad;
+ return this;
+ }
+
+ Layout build() {
+ return new StaticLayout(text, paint, width, align, spacingMult,
+ spacingAdd, includePad);
+ }
+ }
+
+ private void assertVertMetrics(Layout l, int topPad, int botPad, int... values) {
+ assertTopBotPadding(l, topPad, botPad);
+ assertLinesMetrics(l, values);
+ }
+
+ private void assertLinesMetrics(Layout l, int... values) {
+ // sanity check
+ if ((values.length & 0x1) != 0) {
+ throw new IllegalArgumentException(String.valueOf(values.length));
+ }
+
+ int lines = values.length >> 1;
+ assertEquals(lines, l.getLineCount());
+
+ int t = 0;
+ for (int i = 0, n = 0; i < lines; ++i, n += 2) {
+ int a = values[n];
+ int d = values[n+1];
+ int h = -a + d;
+ assertLineMetrics(l, i, t, a, d, h);
+ t += h;
+ }
+
+ assertEquals(t, l.getHeight());
+ }
+
+ private void assertLineMetrics(Layout l, int line,
+ int top, int ascent, int descent, int height) {
+ String info = "line " + line;
+ assertEquals(info, top, l.getLineTop(line));
+ assertEquals(info, ascent, l.getLineAscent(line));
+ assertEquals(info, descent, l.getLineDescent(line));
+ assertEquals(info, height, l.getLineBottom(line) - top);
+ }
+
+ private void assertTopBotPadding(Layout l, int topPad, int botPad) {
+ assertEquals(topPad, l.getTopPadding());
+ assertEquals(botPad, l.getBottomPadding());
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextLayoutTest.java b/core/tests/coretests/src/android/text/TextLayoutTest.java
new file mode 100644
index 0000000..6cf3000
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLayoutTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.text;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.DynamicLayout;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import junit.framework.TestCase;
+
+
+public class TextLayoutTest extends TestCase {
+
+ protected String mString;
+ protected TextPaint mPaint;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mString = "The quick brown fox";
+ mPaint = new TextPaint();
+ }
+
+ @SmallTest
+ public void testStaticLayout() throws Exception {
+ Layout l = new StaticLayout(mString, mPaint, 200,
+ Layout.Alignment.ALIGN_NORMAL, 1, 0,
+ true);
+ }
+
+ @SmallTest
+ public void testDynamicLayoutTest() throws Exception {
+ Layout l = new DynamicLayout(mString, mPaint, 200,
+ Layout.Alignment.ALIGN_NORMAL, 1, 0,
+ true);
+ }
+}
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
new file mode 100644
index 0000000..5b427be
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.graphics.Paint;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.test.MoreAsserts;
+
+import com.android.common.Rfc822Validator;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TextUtilsTest tests {@link TextUtils}.
+ */
+public class TextUtilsTest extends TestCase {
+
+ @SmallTest
+ public void testBasic() throws Exception {
+ assertEquals("", TextUtils.concat());
+ assertEquals("foo", TextUtils.concat("foo"));
+ assertEquals("foobar", TextUtils.concat("foo", "bar"));
+ assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz"));
+
+ SpannableString foo = new SpannableString("foo");
+ foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+
+ SpannableString bar = new SpannableString("bar");
+ bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+
+ SpannableString baz = new SpannableString("baz");
+ baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
+
+ assertEquals("foo", TextUtils.concat(foo).toString());
+ assertEquals("foobar", TextUtils.concat(foo, bar).toString());
+ assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString());
+
+ assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo"));
+
+ assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo"));
+ assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar"));
+
+ assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo"));
+ assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar"));
+ assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz"));
+
+ assertTrue(TextUtils.concat("foo", "bar") instanceof String);
+ assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString);
+ }
+
+ @SmallTest
+ public void testTemplateString() throws Exception {
+ CharSequence result;
+
+ result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.",
+ "test", "emergency", "system");
+ assertEquals("This is a test of the emergency broadcast system.",
+ result.toString());
+
+ result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c",
+ "one", "two", "three");
+ assertEquals("^one^twothree^aone^b^^c",
+ result.toString());
+
+ result = TextUtils.expandTemplate("^");
+ assertEquals("^", result.toString());
+
+ result = TextUtils.expandTemplate("^^");
+ assertEquals("^", result.toString());
+
+ result = TextUtils.expandTemplate("^^^");
+ assertEquals("^^", result.toString());
+
+ result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", "");
+ assertEquals("shorter a values .", result.toString());
+
+ try {
+ TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ result = TextUtils.expandTemplate("^1 value given, and ^9 used.",
+ "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine");
+ assertEquals("one value given, and nine used.", result.toString());
+
+ try {
+ TextUtils.expandTemplate("^1 value given, and ^10 used.",
+ "one", "two", "three", "four", "five",
+ "six", "seven", "eight", "nine", "ten");
+ fail();
+ } catch (IllegalArgumentException e) {
+ }
+
+ // putting carets in the values: expansion is not recursive.
+
+ result = TextUtils.expandTemplate("^2", "foo", "^^");
+ assertEquals("^^", result.toString());
+
+ result = TextUtils.expandTemplate("^^2", "foo", "1");
+ assertEquals("^2", result.toString());
+
+ result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo");
+ assertEquals("value with ^2 in it", result.toString());
+ }
+
+ /** Fail unless text+spans contains a span 'spanName' with the given start and end. */
+ private void checkContains(Spanned text, String[] spans, String spanName,
+ int start, int end) throws Exception {
+ for (String i: spans) {
+ if (i.equals(spanName)) {
+ assertEquals(start, text.getSpanStart(i));
+ assertEquals(end, text.getSpanEnd(i));
+ return;
+ }
+ }
+ fail();
+ }
+
+ @SmallTest
+ public void testTemplateSpan() throws Exception {
+ SpannableString template;
+ Spanned result;
+ String[] spans;
+
+ // ordinary replacement
+
+ template = new SpannableString("a^1b");
+ template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ result = (Spanned) TextUtils.expandTemplate(template, "foo");
+ assertEquals(5, result.length());
+ spans = result.getSpans(0, result.length(), String.class);
+
+ // value is one character longer, so span endpoints should change.
+ assertEquals(4, spans.length);
+ checkContains(result, spans, "before", 0, 1);
+ checkContains(result, spans, "during", 1, 4);
+ checkContains(result, spans, "after", 4, 5);
+ checkContains(result, spans, "during+after", 1, 5);
+
+
+ // replacement with empty string
+
+ result = (Spanned) TextUtils.expandTemplate(template, "");
+ assertEquals(2, result.length());
+ spans = result.getSpans(0, result.length(), String.class);
+
+ // the "during" span should disappear.
+ assertEquals(3, spans.length);
+ checkContains(result, spans, "before", 0, 1);
+ checkContains(result, spans, "after", 1, 2);
+ checkContains(result, spans, "during+after", 1, 2);
+ }
+
+ @SmallTest
+ public void testStringSplitterSimple() {
+ stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"});
+ }
+
+ @SmallTest
+ public void testStringSplitterEmpty() {
+ stringSplitterTestHelper("", new String[] {});
+ }
+
+ @SmallTest
+ public void testStringSplitterWithLeadingEmptyString() {
+ stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"});
+ }
+
+ @SmallTest
+ public void testStringSplitterWithInternalEmptyString() {
+ stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"});
+ }
+
+ @SmallTest
+ public void testStringSplitterWithTrailingEmptyString() {
+ // A single trailing emtpy string should be ignored.
+ stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"});
+ }
+
+ private void stringSplitterTestHelper(String string, String[] expectedStrings) {
+ TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
+ splitter.setString(string);
+ List<String> strings = Lists.newArrayList();
+ for (String s : splitter) {
+ strings.add(s);
+ }
+ MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{}));
+ }
+
+ @SmallTest
+ public void testTrim() {
+ String[] strings = { "abc", " abc", " abc", "abc ", "abc ",
+ " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" };
+
+ for (String s : strings) {
+ assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s));
+ }
+ }
+
+ //==============================================================================================
+ // Email validator
+ //==============================================================================================
+
+ @SmallTest
+ public void testEmailValidator() {
+ Rfc822Validator validator = new Rfc822Validator("gmail.com");
+ String[] validEmails = new String[] {
+ "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info",
+ };
+
+ for (String email : validEmails) {
+ assertTrue(email + " should be a valid email address", validator.isValid(email));
+ }
+
+ String[] invalidEmails = new String[] {
+ "a", "a@b", "a b", "a@b.12"
+ };
+
+ for (String email : invalidEmails) {
+ assertFalse(email + " should not be a valid email address", validator.isValid(email));
+ }
+
+ Map<String, String> fixes = Maps.newHashMap();
+ fixes.put("a", "<a@gmail.com>");
+ fixes.put("a b", "<ab@gmail.com>");
+ fixes.put("a@b", "<a@b>");
+
+ for (Map.Entry<String, String> e : fixes.entrySet()) {
+ assertEquals(e.getValue(), validator.fixText(e.getKey()).toString());
+ }
+ }
+
+ @LargeTest
+ public void testEllipsize() {
+ CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog.";
+ CharSequence s2 = new Wrapper(s1);
+ Spannable s3 = new SpannableString(s1);
+ s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ TextPaint p = new TextPaint();
+ p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
+
+ for (int i = 0; i < 100; i++) {
+ for (int j = 0; j < 3; j++) {
+ TextUtils.TruncateAt kind = null;
+
+ switch (j) {
+ case 0:
+ kind = TextUtils.TruncateAt.START;
+ break;
+
+ case 1:
+ kind = TextUtils.TruncateAt.END;
+ break;
+
+ case 2:
+ kind = TextUtils.TruncateAt.MIDDLE;
+ break;
+ }
+
+ String out1 = TextUtils.ellipsize(s1, p, i, kind).toString();
+ String out2 = TextUtils.ellipsize(s2, p, i, kind).toString();
+ String out3 = TextUtils.ellipsize(s3, p, i, kind).toString();
+
+ String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString();
+ String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString();
+ String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString();
+
+ String trim1 = keep1.replace("\uFEFF", "");
+
+ // Are all normal output strings identical?
+ assertEquals("wid " + i + " pass " + j, out1, out2);
+ assertEquals("wid " + i + " pass " + j, out2, out3);
+
+ // Are preserved output strings identical?
+ assertEquals("wid " + i + " pass " + j, keep1, keep2);
+ assertEquals("wid " + i + " pass " + j, keep2, keep3);
+
+ // Does trimming padding from preserved yield normal?
+ assertEquals("wid " + i + " pass " + j, out1, trim1);
+
+ // Did preserved output strings preserve length?
+ assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length());
+
+ // Does the output string actually fit in the space?
+ assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i);
+
+ // Is the padded output the same width as trimmed output?
+ assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1));
+ }
+ }
+ }
+
+ /**
+ * CharSequence wrapper for testing the cases where text is copied into
+ * a char array instead of working from a String or a Spanned.
+ */
+ private static class Wrapper implements CharSequence {
+ private CharSequence mString;
+
+ public Wrapper(CharSequence s) {
+ mString = s;
+ }
+
+ public int length() {
+ return mString.length();
+ }
+
+ public char charAt(int off) {
+ return mString.charAt(off);
+ }
+
+ public String toString() {
+ return mString.toString();
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return new Wrapper(mString.subSequence(start, end));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java
new file mode 100644
index 0000000..489f58b
--- /dev/null
+++ b/core/tests/coretests/src/android/text/format/TimeTest.java
@@ -0,0 +1,608 @@
+/*
+ * 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.text.format;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
+
+import junit.framework.TestCase;
+
+public class TimeTest extends TestCase {
+
+ @SmallTest
+ public void testNormalize0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.parse("20060432T010203");
+ t.normalize(false /* use isDst */);
+// System.out.println("got: " + t.year + '-'
+// + t.month + '-' + t.monthDay
+// + ' ' + t.hour + ':' + t.minute
+// + ':' + t.second
+// + "( " + t.isDst + ',' + t.gmtoff
+// + ',' + t.weekDay
+// + ',' + t.yearDay + ')');
+ }
+
+ private static class DateTest {
+ public int year1;
+ public int month1;
+ public int day1;
+ public int hour1;
+ public int minute1;
+ public int dst1;
+
+ public int offset;
+
+ public int year2;
+ public int month2;
+ public int day2;
+ public int hour2;
+ public int minute2;
+ public int dst2;
+
+ public DateTest(int year1, int month1, int day1, int hour1, int minute1, int dst1,
+ int offset, int year2, int month2, int day2, int hour2, int minute2,
+ int dst2) {
+ this.year1 = year1;
+ this.month1 = month1;
+ this.day1 = day1;
+ this.hour1 = hour1;
+ this.minute1 = minute1;
+ this.dst1 = dst1;
+ this.offset = offset;
+ this.year2 = year2;
+ this.month2 = month2;
+ this.day2 = day2;
+ this.hour2 = hour2;
+ this.minute2 = minute2;
+ this.dst2 = dst2;
+ }
+
+ public DateTest(int year1, int month1, int day1, int hour1, int minute1,
+ int offset, int year2, int month2, int day2, int hour2, int minute2) {
+ this.year1 = year1;
+ this.month1 = month1;
+ this.day1 = day1;
+ this.hour1 = hour1;
+ this.minute1 = minute1;
+ this.dst1 = -1;
+ this.offset = offset;
+ this.year2 = year2;
+ this.month2 = month2;
+ this.day2 = day2;
+ this.hour2 = hour2;
+ this.minute2 = minute2;
+ this.dst2 = -1;
+ }
+ }
+
+ // These tests assume that DST changes on Nov 4, 2007 at 2am (to 1am).
+
+ // The "offset" field in "dayTests" represents days.
+ // Use normalize(true) with these tests to change the date by 1 day.
+ private DateTest[] dayTests = {
+ // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
+
+ // Nov 4, 12am + 0 day = Nov 4, 12am
+ // Nov 5, 12am + 0 day = Nov 5, 12am
+ new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
+ new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
+
+ // Nov 3, 12am + 1 day = Nov 4, 12am
+ // Nov 4, 12am + 1 day = Nov 5, 12am
+ // Nov 5, 12am + 1 day = Nov 6, 12am
+ new DateTest(2007, 10, 3, 0, 0, 1, 2007, 10, 4, 0, 0),
+ new DateTest(2007, 10, 4, 0, 0, 1, 2007, 10, 5, 0, 0),
+ new DateTest(2007, 10, 5, 0, 0, 1, 2007, 10, 6, 0, 0),
+
+ // Nov 3, 1am + 1 day = Nov 4, 1am
+ // Nov 4, 1am + 1 day = Nov 5, 1am
+ // Nov 5, 1am + 1 day = Nov 6, 1am
+ new DateTest(2007, 10, 3, 1, 0, 1, 2007, 10, 4, 1, 0),
+ new DateTest(2007, 10, 4, 1, 0, 1, 2007, 10, 5, 1, 0),
+ new DateTest(2007, 10, 5, 1, 0, 1, 2007, 10, 6, 1, 0),
+
+ // Nov 3, 2am + 1 day = Nov 4, 2am
+ // Nov 4, 2am + 1 day = Nov 5, 2am
+ // Nov 5, 2am + 1 day = Nov 6, 2am
+ new DateTest(2007, 10, 3, 2, 0, 1, 2007, 10, 4, 2, 0),
+ new DateTest(2007, 10, 4, 2, 0, 1, 2007, 10, 5, 2, 0),
+ new DateTest(2007, 10, 5, 2, 0, 1, 2007, 10, 6, 2, 0),
+ };
+
+ // The "offset" field in "minuteTests" represents minutes.
+ // Use normalize(false) with these tests.
+ private DateTest[] minuteTests = {
+ // The month numbers are 0-relative, so Jan=0, Feb=1,...Dec=11
+
+ // Nov 4, 12am + 0 minutes = Nov 4, 12am
+ // Nov 5, 12am + 0 minutes = Nov 5, 12am
+ new DateTest(2007, 10, 4, 0, 0, 0, 2007, 10, 4, 0, 0),
+ new DateTest(2007, 10, 5, 0, 0, 0, 2007, 10, 5, 0, 0),
+
+ // Nov 3, 12am + 60 minutes = Nov 3, 1am
+ // Nov 4, 12am + 60 minutes = Nov 4, 1am
+ // Nov 5, 12am + 60 minutes = Nov 5, 1am
+ new DateTest(2007, 10, 3, 0, 0, 60, 2007, 10, 3, 1, 0),
+ new DateTest(2007, 10, 4, 0, 0, 60, 2007, 10, 4, 1, 0),
+ new DateTest(2007, 10, 5, 0, 0, 60, 2007, 10, 5, 1, 0),
+
+ // Nov 3, 1am + 60 minutes = Nov 3, 2am
+ // Nov 4, 1am (PDT) + 30 minutes = Nov 4, 1:30am (PDT)
+ // Nov 4, 1am (PDT) + 60 minutes = Nov 4, 1am (PST)
+ new DateTest(2007, 10, 3, 1, 0, 60, 2007, 10, 3, 2, 0),
+ new DateTest(2007, 10, 4, 1, 0, 1, 30, 2007, 10, 4, 1, 30, 1),
+ new DateTest(2007, 10, 4, 1, 0, 1, 60, 2007, 10, 4, 1, 0, 0),
+
+ // Nov 4, 1:30am (PDT) + 15 minutes = Nov 4, 1:45am (PDT)
+ // Nov 4, 1:30am (PDT) + 30 minutes = Nov 4, 1:00am (PST)
+ // Nov 4, 1:30am (PDT) + 60 minutes = Nov 4, 1:30am (PST)
+ new DateTest(2007, 10, 4, 1, 30, 1, 15, 2007, 10, 4, 1, 45, 1),
+ new DateTest(2007, 10, 4, 1, 30, 1, 30, 2007, 10, 4, 1, 0, 0),
+ new DateTest(2007, 10, 4, 1, 30, 1, 60, 2007, 10, 4, 1, 30, 0),
+
+ // Nov 4, 1:30am (PST) + 15 minutes = Nov 4, 1:45am (PST)
+ // Nov 4, 1:30am (PST) + 30 minutes = Nov 4, 2:00am (PST)
+ // Nov 5, 1am + 60 minutes = Nov 5, 2am
+ new DateTest(2007, 10, 4, 1, 30, 0, 15, 2007, 10, 4, 1, 45, 0),
+ new DateTest(2007, 10, 4, 1, 30, 0, 30, 2007, 10, 4, 2, 0, 0),
+ new DateTest(2007, 10, 5, 1, 0, 60, 2007, 10, 5, 2, 0),
+
+ // Nov 3, 2am + 60 minutes = Nov 3, 3am
+ // Nov 4, 2am + 30 minutes = Nov 4, 2:30am
+ // Nov 4, 2am + 60 minutes = Nov 4, 3am
+ // Nov 5, 2am + 60 minutes = Nov 5, 3am
+ new DateTest(2007, 10, 3, 2, 0, 60, 2007, 10, 3, 3, 0),
+ new DateTest(2007, 10, 4, 2, 0, 30, 2007, 10, 4, 2, 30),
+ new DateTest(2007, 10, 4, 2, 0, 60, 2007, 10, 4, 3, 0),
+ new DateTest(2007, 10, 5, 2, 0, 60, 2007, 10, 5, 3, 0),
+ };
+
+ @SmallTest
+ public void testNormalize1() throws Exception {
+ Time local = new Time("America/Los_Angeles");
+
+ int len = dayTests.length;
+ for (int index = 0; index < len; index++) {
+ DateTest test = dayTests[index];
+ local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
+ // call normalize() to make sure that isDst is set
+ local.normalize(false /* use isDst */);
+ local.monthDay += test.offset;
+ local.normalize(true /* ignore isDst */);
+ if (local.year != test.year2 || local.month != test.month2
+ || local.monthDay != test.day2 || local.hour != test.hour2
+ || local.minute != test.minute2) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d",
+ local.year, local.month, local.monthDay, local.hour, local.minute);
+ throw new RuntimeException(
+ "day test index " + index + ", normalize(): expected local " + expectedTime
+ + " got: " + actualTime);
+ }
+
+ local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
+ // call normalize() to make sure that isDst is set
+ local.normalize(false /* use isDst */);
+ local.monthDay += test.offset;
+ long millis = local.toMillis(true /* ignore isDst */);
+ local.set(millis);
+ if (local.year != test.year2 || local.month != test.month2
+ || local.monthDay != test.day2 || local.hour != test.hour2
+ || local.minute != test.minute2) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d",
+ local.year, local.month, local.monthDay, local.hour, local.minute);
+ throw new RuntimeException(
+ "day test index " + index + ", toMillis(): expected local " + expectedTime
+ + " got: " + actualTime);
+ }
+ }
+
+ len = minuteTests.length;
+ for (int index = 0; index < len; index++) {
+ DateTest test = minuteTests[index];
+ local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
+ local.isDst = test.dst1;
+ // call normalize() to make sure that isDst is set
+ local.normalize(false /* use isDst */);
+ if (test.dst2 == -1) test.dst2 = local.isDst;
+ local.minute += test.offset;
+ local.normalize(false /* use isDst */);
+ if (local.year != test.year2 || local.month != test.month2
+ || local.monthDay != test.day2 || local.hour != test.hour2
+ || local.minute != test.minute2 || local.isDst != test.dst2) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2,
+ test.dst2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
+ local.year, local.month, local.monthDay, local.hour, local.minute,
+ local.isDst);
+ throw new RuntimeException(
+ "minute test index " + index + ", normalize(): expected local " + expectedTime
+ + " got: " + actualTime);
+ }
+
+ local.set(0, test.minute1, test.hour1, test.day1, test.month1, test.year1);
+ local.isDst = test.dst1;
+ // call normalize() to make sure that isDst is set
+ local.normalize(false /* use isDst */);
+ if (test.dst2 == -1) test.dst2 = local.isDst;
+ local.minute += test.offset;
+ long millis = local.toMillis(false /* use isDst */);
+ local.set(millis);
+ if (local.year != test.year2 || local.month != test.month2
+ || local.monthDay != test.day2 || local.hour != test.hour2
+ || local.minute != test.minute2 || local.isDst != test.dst2) {
+ String expectedTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
+ test.year2, test.month2, test.day2, test.hour2, test.minute2,
+ test.dst2);
+ String actualTime = String.format("%d-%02d-%02d %02d:%02d isDst: %d",
+ local.year, local.month, local.monthDay, local.hour, local.minute,
+ local.isDst);
+ throw new RuntimeException(
+ "minute test index " + index + ", toMillis(): expected local " + expectedTime
+ + " got: " + actualTime);
+ }
+ }
+ }
+
+ @SmallTest
+ public void testSwitchTimezone0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.parse("20061005T120000");
+ t.switchTimezone("America/Los_Angeles");
+ // System.out.println("got: " + t);
+ }
+
+ @SmallTest
+ public void testCtor0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ assertEquals(Time.TIMEZONE_UTC, t.timezone);
+ }
+
+ @SmallTest
+ public void testGetActualMaximum0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ int r = t.getActualMaximum(Time.SECOND);
+ // System.out.println("r=" + r);
+ }
+
+ @SmallTest
+ public void testClear0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.clear(Time.TIMEZONE_UTC);
+ }
+
+ @SmallTest
+ public void testCompare0() throws Exception {
+ Time a = new Time(Time.TIMEZONE_UTC);
+ Time b = new Time("America/Los_Angeles");
+ int r = Time.compare(a, b);
+ // System.out.println("r=" + r);
+ }
+
+ @SmallTest
+ public void testFormat0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ String r = t.format("%Y%m%dT%H%M%S");
+ // System.out.println("r='" + r + "'");
+ }
+
+ @SmallTest
+ public void testToString0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ String r = t.toString();
+ // System.out.println("r='" + r + "'");
+ }
+
+ @SmallTest
+ public void testGetCurrentTimezone0() throws Exception {
+ String r = Time.getCurrentTimezone();
+ // System.out.println("r='" + r + "'");
+ }
+
+ @SmallTest
+ public void testSetToNow0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.setToNow();
+ // System.out.println("t=" + t);
+ }
+
+ @SmallTest
+ public void testMillis0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(0, 0, 0, 1, 1, 2006);
+ long r = t.toMillis(true /* ignore isDst */);
+ // System.out.println("r=" + r);
+ t.set(1, 0, 0, 1, 1, 2006);
+ r = t.toMillis(true /* ignore isDst */);
+ // System.out.println("r=" + r);
+ }
+
+ @SmallTest
+ public void testMillis1() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(1, 0, 0, 1, 0, 1970);
+ long r = t.toMillis(true /* ignore isDst */);
+ // System.out.println("r=" + r);
+ }
+
+ @SmallTest
+ public void testParse0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.parse("12345678T901234");
+ // System.out.println("t=" + t);
+ }
+
+ @SmallTest
+ public void testParse33390() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+
+ t.parse3339("1980-05-23");
+ if (!t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23) {
+ fail("Did not parse all-day date correctly");
+ }
+
+ t.parse3339("1980-05-23T09:50:50");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 9 || t.minute != 50 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse timezone-offset-less date correctly");
+ }
+
+ t.parse3339("1980-05-23T09:50:50Z");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 9 || t.minute != 50 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse UTC date correctly");
+ }
+
+ t.parse3339("1980-05-23T09:50:50.0Z");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 9 || t.minute != 50 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse UTC date correctly");
+ }
+
+ t.parse3339("1980-05-23T09:50:50.12Z");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 9 || t.minute != 50 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse UTC date correctly");
+ }
+
+ t.parse3339("1980-05-23T09:50:50.123Z");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 9 || t.minute != 50 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse UTC date correctly");
+ }
+
+ // The time should be normalized to UTC
+ t.parse3339("1980-05-23T09:50:50-01:05");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 10 || t.minute != 55 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse timezone-offset date correctly");
+ }
+
+ // The time should be normalized to UTC
+ t.parse3339("1980-05-23T09:50:50.123-01:05");
+ if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 ||
+ t.hour != 10 || t.minute != 55 || t.second != 50 ||
+ t.gmtoff != 0) {
+ fail("Did not parse timezone-offset date correctly");
+ }
+
+ try {
+ t.parse3339("1980");
+ fail("Did not throw error on truncated input length");
+ } catch (TimeFormatException e) {
+ // Successful
+ }
+
+ try {
+ t.parse3339("1980-05-23T09:50:50.123+");
+ fail("Did not throw error on truncated timezone offset");
+ } catch (TimeFormatException e1) {
+ // Successful
+ }
+
+ try {
+ t.parse3339("1980-05-23T09:50:50.123+05:0");
+ fail("Did not throw error on truncated timezone offset");
+ } catch (TimeFormatException e1) {
+ // Successful
+ }
+ }
+
+ @SmallTest
+ public void testSet0() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(1000L);
+ // System.out.println("t.year=" + t.year);
+ // System.out.println("t=" + t);
+ t.set(2000L);
+ // System.out.println("t=" + t);
+ t.set(1000L * 60);
+ // System.out.println("t=" + t);
+ t.set((1000L * 60 * 60 * 24) + 1000L);
+ // System.out.println("t=" + t);
+ }
+
+ @SmallTest
+ public void testSet1() throws Exception {
+ Time t = new Time(Time.TIMEZONE_UTC);
+ t.set(1, 2, 3, 4, 5, 6);
+ // System.out.println("t=" + t);
+ }
+
+ // Timezones that cover the world. Some GMT offsets occur more than
+ // once in case some cities decide to change their GMT offset.
+ private static final String[] mTimeZones = {
+ "Pacific/Kiritimati",
+ "Pacific/Enderbury",
+ "Pacific/Fiji",
+ "Antarctica/South_Pole",
+ "Pacific/Norfolk",
+ "Pacific/Ponape",
+ "Asia/Magadan",
+ "Australia/Lord_Howe",
+ "Australia/Sydney",
+ "Australia/Adelaide",
+ "Asia/Tokyo",
+ "Asia/Seoul",
+ "Asia/Taipei",
+ "Asia/Singapore",
+ "Asia/Hong_Kong",
+ "Asia/Saigon",
+ "Asia/Bangkok",
+ "Indian/Cocos",
+ "Asia/Rangoon",
+ "Asia/Omsk",
+ "Antarctica/Mawson",
+ "Asia/Colombo",
+ "Asia/Calcutta",
+ "Asia/Oral",
+ "Asia/Kabul",
+ "Asia/Dubai",
+ "Asia/Tehran",
+ "Europe/Moscow",
+ "Asia/Baghdad",
+ "Africa/Mogadishu",
+ "Europe/Athens",
+ "Africa/Cairo",
+ "Europe/Rome",
+ "Europe/Berlin",
+ "Europe/Amsterdam",
+ "Africa/Tunis",
+ "Europe/London",
+ "Europe/Dublin",
+ "Atlantic/St_Helena",
+ "Africa/Monrovia",
+ "Africa/Accra",
+ "Atlantic/Azores",
+ "Atlantic/South_Georgia",
+ "America/Noronha",
+ "America/Sao_Paulo",
+ "America/Cayenne",
+ "America/St_Johns",
+ "America/Puerto_Rico",
+ "America/Aruba",
+ "America/New_York",
+ "America/Chicago",
+ "America/Denver",
+ "America/Los_Angeles",
+ "America/Anchorage",
+ "Pacific/Marquesas",
+ "America/Adak",
+ "Pacific/Honolulu",
+ "Pacific/Midway",
+ };
+
+ @Suppress
+ public void disableTestGetJulianDay() throws Exception {
+ Time time = new Time();
+
+ // For each day of the year, and for each timezone, get the Julian
+ // day for 12am and then check that if we change the time we get the
+ // same Julian day.
+ for (int monthDay = 1; monthDay <= 366; monthDay++) {
+ for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) {
+ // We leave the "month" as zero because we are changing the
+ // "monthDay" from 1 to 366. The call to normalize() will
+ // then change the "month" (but we don't really care).
+ time.set(0, 0, 0, monthDay, 0, 2008);
+ time.timezone = mTimeZones[zoneIndex];
+ long millis = time.normalize(true);
+ if (zoneIndex == 0) {
+ Log.i("TimeTest", time.format("%B %d, %Y"));
+ }
+
+ // This is the Julian day for 12am for this day of the year
+ int julianDay = Time.getJulianDay(millis, time.gmtoff);
+
+ // Change the time during the day and check that we get the same
+ // Julian day.
+ for (int hour = 0; hour < 24; hour++) {
+ for (int minute = 0; minute < 60; minute += 15) {
+ time.set(0, minute, hour, monthDay, 0, 2008);
+ millis = time.normalize(true);
+ int day = Time.getJulianDay(millis, time.gmtoff);
+ if (day != julianDay) {
+ Log.e("TimeTest", "Julian day: " + day + " at time "
+ + time.hour + ":" + time.minute
+ + " != today's Julian day: " + julianDay
+ + " timezone: " + time.timezone);
+ }
+ assertEquals(day, julianDay);
+ }
+ }
+ }
+ }
+ }
+
+ @Suppress
+ public void disableTestSetJulianDay() throws Exception {
+ Time time = new Time();
+
+ // For each day of the year in 2008, and for each timezone,
+ // test that we can set the Julian day correctly.
+ for (int monthDay = 1; monthDay <= 366; monthDay++) {
+ for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) {
+ // We leave the "month" as zero because we are changing the
+ // "monthDay" from 1 to 366. The call to normalize() will
+ // then change the "month" (but we don't really care).
+ time.set(0, 0, 0, monthDay, 0, 2008);
+ time.timezone = mTimeZones[zoneIndex];
+ long millis = time.normalize(true);
+ if (zoneIndex == 0) {
+ Log.i("TimeTest", time.format("%B %d, %Y"));
+ }
+ int julianDay = Time.getJulianDay(millis, time.gmtoff);
+
+ time.setJulianDay(julianDay);
+
+ // Some places change daylight saving time at 12am and so there
+ // is no 12am on some days in some timezones. In those cases,
+ // the time is set to 1am.
+ // Examples: Africa/Cairo on April 25, 2008
+ // America/Sao_Paulo on October 12, 2008
+ // Atlantic/Azores on March 30, 2008
+ assertTrue(time.hour == 0 || time.hour == 1);
+ assertEquals(0, time.minute);
+ assertEquals(0, time.second);
+
+ millis = time.toMillis(false);
+ int day = Time.getJulianDay(millis, time.gmtoff);
+ if (day != julianDay) {
+ Log.i("TimeTest", "Error: gmtoff " + (time.gmtoff / 3600.0)
+ + " day " + julianDay
+ + " millis " + millis
+ + " " + time.format("%B %d, %Y") + " " + time.timezone);
+ }
+ assertEquals(day, julianDay);
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
new file mode 100644
index 0000000..99c6501
--- /dev/null
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.util;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.method.LinkMovementMethod;
+import android.text.util.Linkify;
+import android.widget.TextView;
+
+/**
+ * LinkifyTest tests {@link Linkify}.
+ */
+public class LinkifyTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testNothing() throws Exception {
+ TextView tv;
+
+ tv = new TextView(getContext());
+ tv.setText("Hey, foo@google.com, call 415-555-1212.");
+
+ assertFalse(tv.getMovementMethod() instanceof LinkMovementMethod);
+ assertTrue(tv.getUrls().length == 0);
+ }
+
+ @MediumTest
+ public void testNormal() throws Exception {
+ TextView tv;
+
+ tv = new TextView(getContext());
+ tv.setAutoLinkMask(Linkify.ALL);
+ tv.setText("Hey, foo@google.com, call 415-555-1212.");
+
+ assertTrue(tv.getMovementMethod() instanceof LinkMovementMethod);
+ assertTrue(tv.getUrls().length == 2);
+ }
+
+ @SmallTest
+ public void testUnclickable() throws Exception {
+ TextView tv;
+
+ tv = new TextView(getContext());
+ tv.setAutoLinkMask(Linkify.ALL);
+ tv.setLinksClickable(false);
+ tv.setText("Hey, foo@google.com, call 415-555-1212.");
+
+ assertFalse(tv.getMovementMethod() instanceof LinkMovementMethod);
+ assertTrue(tv.getUrls().length == 2);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
new file mode 100644
index 0000000..0f5b090
--- /dev/null
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -0,0 +1,520 @@
+/*
+ * 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 junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Random;
+
+public class Base64Test extends TestCase {
+ private static final String TAG = "Base64Test";
+
+ /** Decodes a string, returning a string. */
+ private String decodeString(String in) throws Exception {
+ byte[] out = Base64.decode(in, 0);
+ return new String(out);
+ }
+
+ /**
+ * Encodes the string 'in' using 'flags'. Asserts that decoding
+ * gives the same string. Returns the encoded string.
+ */
+ private String encodeToString(String in, int flags) throws Exception {
+ String b64 = Base64.encodeToString(in.getBytes(), flags);
+ String dec = decodeString(b64);
+ assertEquals(in, dec);
+ return b64;
+ }
+
+ /** Assert that decoding 'in' throws IllegalArgumentException. */
+ private void assertBad(String in) throws Exception {
+ try {
+ byte[] out = Base64.decode(in, 0);
+ fail("should have failed to decode");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /** Assert that actual equals the first len bytes of expected. */
+ private void assertEquals(byte[] expected, int len, byte[] actual) {
+ assertEquals(len, actual.length);
+ for (int i = 0; i < len; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ /** Assert that actual equals the first len bytes of expected. */
+ private void assertEquals(byte[] expected, int len, byte[] actual, int alen) {
+ assertEquals(len, alen);
+ for (int i = 0; i < len; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ /** Assert that actual equals the first len bytes of expected. */
+ private void assertEquals(byte[] expected, byte[] actual) {
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ public void testDecodeExtraChars() throws Exception {
+ // padding 0
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+ assertBad("aGVsbG8sIHdvcmxk=");
+ assertBad("aGVsbG8sIHdvcmxk==");
+ assertBad("aGVsbG8sIHdvcmxk =");
+ assertBad("aGVsbG8sIHdvcmxk = = ");
+ assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk "));
+ assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k "));
+ assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk "));
+ assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk "));
+ assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k "));
+ assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_"));
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+
+ // padding 1
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE"));
+ assertBad("aGVsbG8sIHdvcmxkPyE==");
+ assertBad("aGVsbG8sIHdvcmxkPyE ==");
+ assertBad("aGVsbG8sIHdvcmxkPyE = = ");
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E"));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+
+ // padding 2
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg"));
+ assertBad("aGVsbG8sIHdvcmxkLg=");
+ assertBad("aGVsbG8sIHdvcmxkLg =");
+ assertBad("aGVsbG8sIHdvcmxkLg = ");
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g"));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g =="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ }
+
+ private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
+ (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
+ (byte) 0x99, (byte) 0x88, (byte) 0x77 };
+
+ public void testBinaryDecode() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", 0));
+ assertEquals(BYTES, 1, Base64.decode("/w==", 0));
+ assertEquals(BYTES, 2, Base64.decode("/+4=", 0));
+ assertEquals(BYTES, 3, Base64.decode("/+7d", 0));
+ assertEquals(BYTES, 4, Base64.decode("/+7dzA==", 0));
+ assertEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0));
+ assertEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0));
+ assertEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0));
+ assertEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0));
+ }
+
+ public void testWebSafe() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", Base64.URL_SAFE));
+ assertEquals(BYTES, 1, Base64.decode("_w==", Base64.URL_SAFE));
+ assertEquals(BYTES, 2, Base64.decode("_-4=", Base64.URL_SAFE));
+ assertEquals(BYTES, 3, Base64.decode("_-7d", Base64.URL_SAFE));
+ assertEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.URL_SAFE));
+ assertEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.URL_SAFE));
+ assertEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.URL_SAFE));
+ assertEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.URL_SAFE));
+ assertEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.URL_SAFE));
+
+ assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.URL_SAFE));
+ assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.URL_SAFE));
+ assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.URL_SAFE));
+ assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.URL_SAFE));
+ assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.URL_SAFE));
+ assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.URL_SAFE));
+ assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.URL_SAFE));
+ assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.URL_SAFE));
+ assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.URL_SAFE));
+ }
+
+ public void testFlags() throws Exception {
+ assertEquals("YQ==\n", encodeToString("a", 0));
+ assertEquals("YQ==", encodeToString("a", Base64.NO_WRAP));
+ assertEquals("YQ\n", encodeToString("a", Base64.NO_PADDING));
+ assertEquals("YQ", encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YQ==\r\n", encodeToString("a", Base64.CRLF));
+ assertEquals("YQ\r\n", encodeToString("a", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWI=\n", encodeToString("ab", 0));
+ assertEquals("YWI=", encodeToString("ab", Base64.NO_WRAP));
+ assertEquals("YWI\n", encodeToString("ab", Base64.NO_PADDING));
+ assertEquals("YWI", encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWI=\r\n", encodeToString("ab", Base64.CRLF));
+ assertEquals("YWI\r\n", encodeToString("ab", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJj\n", encodeToString("abc", 0));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_WRAP));
+ assertEquals("YWJj\n", encodeToString("abc", Base64.NO_PADDING));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJjZA==\n", encodeToString("abcd", 0));
+ assertEquals("YWJjZA==", encodeToString("abcd", Base64.NO_WRAP));
+ assertEquals("YWJjZA\n", encodeToString("abcd", Base64.NO_PADDING));
+ assertEquals("YWJjZA", encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF));
+ assertEquals("YWJjZA\r\n", encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING));
+ }
+
+ public void testLineLength() throws Exception {
+ String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
+ String in_57 = in_56 + "e";
+ String in_58 = in_56 + "ef";
+ String in_59 = in_56 + "efg";
+ String in_60 = in_56 + "efgh";
+ String in_61 = in_56 + "efghi";
+
+ String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
+ String out_56 = prefix + "Y2Q=\n";
+ String out_57 = prefix + "Y2Rl\n";
+ String out_58 = prefix + "Y2Rl\nZg==\n";
+ String out_59 = prefix + "Y2Rl\nZmc=\n";
+ String out_60 = prefix + "Y2Rl\nZmdo\n";
+ String out_61 = prefix + "Y2Rl\nZmdoaQ==\n";
+
+ // no newline for an empty input array.
+ assertEquals("", encodeToString("", 0));
+
+ assertEquals(out_56, encodeToString(in_56, 0));
+ assertEquals(out_57, encodeToString(in_57, 0));
+ assertEquals(out_58, encodeToString(in_58, 0));
+ assertEquals(out_59, encodeToString(in_59, 0));
+ assertEquals(out_60, encodeToString(in_60, 0));
+ assertEquals(out_61, encodeToString(in_61, 0));
+
+ assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING));
+ assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING));
+ assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING));
+ assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING));
+ assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING));
+ assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING));
+
+ assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP));
+ assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP));
+ assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP));
+ assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP));
+ assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP));
+ assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP));
+ }
+
+ /**
+ * Tests that Base64.Encoder.encode() does correct handling of the
+ * tail for each call.
+ *
+ * This test is disabled because while it passes if you can get it
+ * to run, android's test infrastructure currently doesn't allow
+ * us to get at package-private members (Base64.Encoder in
+ * this case).
+ */
+ public void XXXtestEncodeInternal() throws Exception {
+ byte[] input = { (byte) 0x61, (byte) 0x62, (byte) 0x63 };
+ byte[] output = new byte[100];
+
+ Base64.Encoder encoder = new Base64.Encoder(Base64.NO_PADDING | Base64.NO_WRAP,
+ output);
+
+ encoder.process(input, 0, 3, false);
+ assertEquals("YWJj".getBytes(), 4, encoder.output, encoder.op);
+ assertEquals(0, encoder.tailLen);
+
+ encoder.process(input, 0, 3, false);
+ assertEquals("YWJj".getBytes(), 4, encoder.output, encoder.op);
+ assertEquals(0, encoder.tailLen);
+
+ encoder.process(input, 0, 1, false);
+ assertEquals(0, encoder.op);
+ assertEquals(1, encoder.tailLen);
+
+ encoder.process(input, 0, 1, false);
+ assertEquals(0, encoder.op);
+ assertEquals(2, encoder.tailLen);
+
+ encoder.process(input, 0, 1, false);
+ assertEquals("YWFh".getBytes(), 4, encoder.output, encoder.op);
+ assertEquals(0, encoder.tailLen);
+
+ encoder.process(input, 0, 2, false);
+ assertEquals(0, encoder.op);
+ assertEquals(2, encoder.tailLen);
+
+ encoder.process(input, 0, 2, false);
+ assertEquals("YWJh".getBytes(), 4, encoder.output, encoder.op);
+ assertEquals(1, encoder.tailLen);
+
+ encoder.process(input, 0, 2, false);
+ assertEquals("YmFi".getBytes(), 4, encoder.output, encoder.op);
+ assertEquals(0, encoder.tailLen);
+
+ encoder.process(input, 0, 1, true);
+ assertEquals("YQ".getBytes(), 2, encoder.output, encoder.op);
+ }
+
+ private static final String lipsum =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
+ "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
+ "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
+ "urna, pharetra vitae consequat eget, adipiscing eu ante. " +
+ "Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " +
+ "eget lorem aliquam rutrum vel vitae eros. In placerat ornare " +
+ "pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " +
+ "eu ultrices suscipit, ligula nisi consectetur eros, dapibus " +
+ "aliquet dui sapien a turpis. Donec ultricies varius ligula, " +
+ "ut hendrerit arcu malesuada at. Praesent sed elit pretium " +
+ "eros luctus gravida. In ac dolor lorem. Cras condimentum " +
+ "convallis elementum. Phasellus vel felis in nulla ultrices " +
+ "venenatis. Nam non tortor non orci convallis convallis. " +
+ "Nam tristique lacinia hendrerit. Pellentesque habitant morbi " +
+ "tristique senectus et netus et malesuada fames ac turpis " +
+ "egestas. Vivamus cursus, nibh eu imperdiet porta, magna " +
+ "ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " +
+ "Phasellus posuere, leo at ultricies vehicula, massa risus " +
+ "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
+ "molestie dapibus commodo. Ut vel tellus at massa gravida " +
+ "semper non sed orci.";
+
+ public void testInputStream() throws Exception {
+ int[] flagses = { Base64.DEFAULT,
+ Base64.NO_PADDING,
+ Base64.NO_WRAP,
+ Base64.NO_PADDING | Base64.NO_WRAP,
+ Base64.CRLF,
+ Base64.URL_SAFE };
+ int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
+ Random rng = new Random(32176L);
+
+ // Test input needs to be at least 2048 bytes to fill up the
+ // read buffer of Base64InputStream.
+ byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();
+
+ for (int flags: flagses) {
+ byte[] encoded = Base64.encode(plain, flags);
+
+ ByteArrayInputStream bais;
+ Base64InputStream b64is;
+ byte[] actual = new byte[plain.length * 2];
+ int ap;
+ int b;
+
+ // ----- test decoding ("encoded" -> "plain") -----
+
+ // read as much as it will give us in one chunk
+ bais = new ByteArrayInputStream(encoded);
+ b64is = new Base64InputStream(bais, flags);
+ ap = 0;
+ while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
+ ap += b;
+ }
+ assertEquals(actual, ap, plain);
+
+ // read individual bytes
+ bais = new ByteArrayInputStream(encoded);
+ b64is = new Base64InputStream(bais, flags);
+ ap = 0;
+ while ((b = b64is.read()) != -1) {
+ actual[ap++] = (byte) b;
+ }
+ assertEquals(actual, ap, plain);
+
+ // mix reads of variously-sized arrays with one-byte reads
+ bais = new ByteArrayInputStream(encoded);
+ b64is = new Base64InputStream(bais, flags);
+ ap = 0;
+ readloop: while (true) {
+ int l = writeLengths[rng.nextInt(writeLengths.length)];
+ if (l >= 0) {
+ b = b64is.read(actual, ap, l);
+ if (b == -1) break readloop;
+ ap += b;
+ } else {
+ for (int i = 0; i < -l; ++i) {
+ if ((b = b64is.read()) == -1) break readloop;
+ actual[ap++] = (byte) b;
+ }
+ }
+ }
+ assertEquals(actual, ap, plain);
+
+ // ----- test encoding ("plain" -> "encoded") -----
+
+ // read as much as it will give us in one chunk
+ bais = new ByteArrayInputStream(plain);
+ b64is = new Base64InputStream(bais, flags, true);
+ ap = 0;
+ while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
+ ap += b;
+ }
+ assertEquals(actual, ap, encoded);
+
+ // read individual bytes
+ bais = new ByteArrayInputStream(plain);
+ b64is = new Base64InputStream(bais, flags, true);
+ ap = 0;
+ while ((b = b64is.read()) != -1) {
+ actual[ap++] = (byte) b;
+ }
+ assertEquals(actual, ap, encoded);
+
+ // mix reads of variously-sized arrays with one-byte reads
+ bais = new ByteArrayInputStream(plain);
+ b64is = new Base64InputStream(bais, flags, true);
+ ap = 0;
+ readloop: while (true) {
+ int l = writeLengths[rng.nextInt(writeLengths.length)];
+ if (l >= 0) {
+ b = b64is.read(actual, ap, l);
+ if (b == -1) break readloop;
+ ap += b;
+ } else {
+ for (int i = 0; i < -l; ++i) {
+ if ((b = b64is.read()) == -1) break readloop;
+ actual[ap++] = (byte) b;
+ }
+ }
+ }
+ assertEquals(actual, ap, encoded);
+ }
+ }
+
+ /**
+ * Tests that Base64OutputStream produces exactly the same results
+ * as calling Base64.encode/.decode on an in-memory array.
+ */
+ public void testOutputStream() throws Exception {
+ int[] flagses = { Base64.DEFAULT,
+ Base64.NO_PADDING,
+ Base64.NO_WRAP,
+ Base64.NO_PADDING | Base64.NO_WRAP,
+ Base64.CRLF,
+ Base64.URL_SAFE };
+ int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
+ Random rng = new Random(32176L);
+
+ // Test input needs to be at least 1024 bytes to test filling
+ // up the write(int) buffer of Base64OutputStream.
+ byte[] plain = (lipsum + lipsum).getBytes();
+
+ for (int flags: flagses) {
+ byte[] encoded = Base64.encode(plain, flags);
+
+ ByteArrayOutputStream baos;
+ Base64OutputStream b64os;
+ byte[] actual;
+ int p;
+
+ // ----- test encoding ("plain" -> "encoded") -----
+
+ // one large write(byte[]) of the whole input
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags);
+ b64os.write(plain);
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(encoded, actual);
+
+ // many calls to write(int)
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags);
+ for (int i = 0; i < plain.length; ++i) {
+ b64os.write(plain[i]);
+ }
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(encoded, actual);
+
+ // intermixed sequences of write(int) with
+ // write(byte[],int,int) of various lengths.
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags);
+ p = 0;
+ while (p < plain.length) {
+ int l = writeLengths[rng.nextInt(writeLengths.length)];
+ l = Math.min(l, plain.length-p);
+ if (l >= 0) {
+ b64os.write(plain, p, l);
+ p += l;
+ } else {
+ l = Math.min(-l, plain.length-p);
+ for (int i = 0; i < l; ++i) {
+ b64os.write(plain[p+i]);
+ }
+ p += l;
+ }
+ }
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(encoded, actual);
+
+ // ----- test decoding ("encoded" -> "plain") -----
+
+ // one large write(byte[]) of the whole input
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags, false);
+ b64os.write(encoded);
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(plain, actual);
+
+ // many calls to write(int)
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags, false);
+ for (int i = 0; i < encoded.length; ++i) {
+ b64os.write(encoded[i]);
+ }
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(plain, actual);
+
+ // intermixed sequences of write(int) with
+ // write(byte[],int,int) of various lengths.
+ baos = new ByteArrayOutputStream();
+ b64os = new Base64OutputStream(baos, flags, false);
+ p = 0;
+ while (p < encoded.length) {
+ int l = writeLengths[rng.nextInt(writeLengths.length)];
+ l = Math.min(l, encoded.length-p);
+ if (l >= 0) {
+ b64os.write(encoded, p, l);
+ p += l;
+ } else {
+ l = Math.min(-l, encoded.length-p);
+ for (int i = 0; i < l; ++i) {
+ b64os.write(encoded[p+i]);
+ }
+ p += l;
+ }
+ }
+ b64os.close();
+ actual = baos.toByteArray();
+ assertEquals(plain, actual);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
new file mode 100644
index 0000000..4c5ad76
--- /dev/null
+++ b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+
+import java.util.Calendar;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for {@link DayOfMonthCursor}.
+ */
+public class DayOfMonthCursorTest extends TestCase {
+
+ @SmallTest
+ public void testMonthRows() {
+ DayOfMonthCursor mc = new DayOfMonthCursor(2007,
+ Calendar.SEPTEMBER, 11, Calendar.SUNDAY);
+
+ assertArraysEqual(new int[]{26, 27, 28, 29, 30, 31, 1},
+ mc.getDigitsForRow(0));
+ assertArraysEqual(new int[]{2, 3, 4, 5, 6, 7, 8},
+ mc.getDigitsForRow(1));
+ assertArraysEqual(new int[]{30, 1, 2, 3, 4, 5, 6},
+ mc.getDigitsForRow(5));
+ }
+
+ @SmallTest
+ public void testMoveLeft() {
+ DayOfMonthCursor mc = new DayOfMonthCursor(2007,
+ Calendar.SEPTEMBER, 3, Calendar.SUNDAY);
+
+ assertEquals(Calendar.SEPTEMBER, mc.getMonth());
+ assertEquals(3, mc.getSelectedDayOfMonth());
+ assertEquals(1, mc.getSelectedRow());
+ assertEquals(1, mc.getSelectedColumn());
+
+ // move left, still same row
+ assertFalse(mc.left());
+ assertEquals(2, mc.getSelectedDayOfMonth());
+ assertEquals(1, mc.getSelectedRow());
+ assertEquals(0, mc.getSelectedColumn());
+
+ // wrap over to previous column, same month
+ assertFalse(mc.left());
+ assertEquals(1, mc.getSelectedDayOfMonth());
+ assertEquals(0, mc.getSelectedRow());
+ assertEquals(6, mc.getSelectedColumn());
+
+ // wrap to previous month
+ assertTrue(mc.left());
+ assertEquals(Calendar.AUGUST, mc.getMonth());
+ assertEquals(31, mc.getSelectedDayOfMonth());
+ assertEquals(4, mc.getSelectedRow());
+ assertEquals(5, mc.getSelectedColumn());
+ }
+
+ @SmallTest
+ public void testMoveRight() {
+ DayOfMonthCursor mc = new DayOfMonthCursor(2007,
+ Calendar.SEPTEMBER, 28, Calendar.SUNDAY);
+
+ assertEquals(Calendar.SEPTEMBER, mc.getMonth());
+ assertEquals(28, mc.getSelectedDayOfMonth());
+ assertEquals(4, mc.getSelectedRow());
+ assertEquals(5, mc.getSelectedColumn());
+
+ // same row
+ assertFalse(mc.right());
+ assertEquals(29, mc.getSelectedDayOfMonth());
+ assertEquals(4, mc.getSelectedRow());
+ assertEquals(6, mc.getSelectedColumn());
+
+ // wrap to next column, same month
+ assertFalse(mc.right());
+ assertEquals(30, mc.getSelectedDayOfMonth());
+ assertEquals(5, mc.getSelectedRow());
+ assertEquals(0, mc.getSelectedColumn());
+
+ // next month
+ assertTrue(mc.right());
+ assertEquals(Calendar.OCTOBER, mc.getMonth());
+ assertEquals(1, mc.getSelectedDayOfMonth());
+ assertEquals(0, mc.getSelectedRow());
+ assertEquals(1, mc.getSelectedColumn());
+ }
+
+ @SmallTest
+ public void testMoveUp() {
+ DayOfMonthCursor mc = new DayOfMonthCursor(2007,
+ Calendar.SEPTEMBER, 13, Calendar.SUNDAY);
+
+ assertEquals(Calendar.SEPTEMBER, mc.getMonth());
+ assertEquals(13, mc.getSelectedDayOfMonth());
+ assertEquals(2, mc.getSelectedRow());
+ assertEquals(4, mc.getSelectedColumn());
+
+ // up, same month
+ assertFalse(mc.up());
+ assertEquals(6, mc.getSelectedDayOfMonth());
+ assertEquals(1, mc.getSelectedRow());
+ assertEquals(4, mc.getSelectedColumn());
+
+ // up, flips back
+ assertTrue(mc.up());
+ assertEquals(Calendar.AUGUST, mc.getMonth());
+ assertEquals(30, mc.getSelectedDayOfMonth());
+ assertEquals(4, mc.getSelectedRow());
+ assertEquals(4, mc.getSelectedColumn());
+ }
+
+ @SmallTest
+ public void testMoveDown() {
+ DayOfMonthCursor mc = new DayOfMonthCursor(2007,
+ Calendar.SEPTEMBER, 23, Calendar.SUNDAY);
+
+ assertEquals(Calendar.SEPTEMBER, mc.getMonth());
+ assertEquals(23, mc.getSelectedDayOfMonth());
+ assertEquals(4, mc.getSelectedRow());
+ assertEquals(0, mc.getSelectedColumn());
+
+ // down, same month
+ assertFalse(mc.down());
+ assertEquals(30, mc.getSelectedDayOfMonth());
+ assertEquals(5, mc.getSelectedRow());
+ assertEquals(0, mc.getSelectedColumn());
+
+ // down, next month
+ assertTrue(mc.down());
+ assertEquals(Calendar.OCTOBER, mc.getMonth());
+ assertEquals(7, mc.getSelectedDayOfMonth());
+ assertEquals(1, mc.getSelectedRow());
+ assertEquals(0, mc.getSelectedColumn());
+ }
+
+ private void assertArraysEqual(int[] expected, int[] actual) {
+ assertEquals("array length", expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("index " + i,
+ expected[i], actual[i]);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ExpandableListScenario.java b/core/tests/coretests/src/android/util/ExpandableListScenario.java
new file mode 100644
index 0000000..4a12b0d
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ExpandableListScenario.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Utility base class for creating various Expandable List scenarios.
+ * <p>
+ * WARNING: A lot of the features are mixed between ListView's expected position
+ * (flat list position) and an ExpandableListView's expected position. You must add/change
+ * features as you need them.
+ *
+ * @see ListScenario
+ */
+public abstract class ExpandableListScenario extends ListScenario {
+ protected ExpandableListAdapter mAdapter;
+ protected List<MyGroup> mGroups;
+
+ @Override
+ protected ListView createListView() {
+ return new ExpandableListView(this);
+ }
+
+ @Override
+ protected Params createParams() {
+ return new ExpandableParams();
+ }
+
+ @Override
+ protected void setAdapter(ListView listView) {
+ ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
+ }
+
+ protected ExpandableListAdapter createAdapter() {
+ return new MyAdapter();
+ }
+
+ @Override
+ protected void readAndValidateParams(Params params) {
+ ExpandableParams expandableParams = (ExpandableParams) params;
+
+ int[] numChildren = expandableParams.mNumChildren;
+
+ mGroups = new ArrayList<MyGroup>(numChildren.length);
+ for (int i = 0; i < numChildren.length; i++) {
+ mGroups.add(new MyGroup(numChildren[i]));
+ }
+
+ expandableParams.superSetNumItems();
+
+ super.readAndValidateParams(params);
+ }
+
+ /**
+ * Get the ExpandableListView widget.
+ * @return The main widget.
+ */
+ public ExpandableListView getExpandableListView() {
+ return (ExpandableListView) super.getListView();
+ }
+
+ public static class ExpandableParams extends Params {
+ private int[] mNumChildren;
+
+ /**
+ * Sets the number of children per group.
+ *
+ * @param numChildrenPerGroup The number of children per group.
+ */
+ public ExpandableParams setNumChildren(int[] numChildren) {
+ mNumChildren = numChildren;
+ return this;
+ }
+
+ /**
+ * Sets the number of items on the superclass based on the number of
+ * groups and children per group.
+ */
+ private ExpandableParams superSetNumItems() {
+ int numItems = 0;
+
+ if (mNumChildren != null) {
+ for (int i = mNumChildren.length - 1; i >= 0; i--) {
+ numItems += mNumChildren[i];
+ }
+ }
+
+ super.setNumItems(numItems);
+
+ return this;
+ }
+
+ @Override
+ public Params setNumItems(int numItems) {
+ throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
+ }
+
+ @Override
+ public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
+ return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
+ }
+
+ @Override
+ public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
+ return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
+ }
+
+ @Override
+ public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
+ return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
+ }
+
+ @Override
+ public ExpandableParams setMustFillScreen(boolean fillScreen) {
+ return (ExpandableParams) super.setMustFillScreen(fillScreen);
+ }
+
+ @Override
+ public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
+ return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
+ }
+
+ @Override
+ public ExpandableParams setPositionUnselectable(int position) {
+ return (ExpandableParams) super.setPositionUnselectable(position);
+ }
+
+ @Override
+ public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
+ return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
+ }
+
+ @Override
+ public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
+ return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
+ }
+
+ @Override
+ public ExpandableParams setConnectAdapter(boolean connectAdapter) {
+ return (ExpandableParams) super.setConnectAdapter(connectAdapter);
+ }
+ }
+
+ /**
+ * Gets a string for the value of some item.
+ * @param packedPosition The position of the item.
+ * @return The string.
+ */
+ public final String getValueAtPosition(long packedPosition) {
+ final int type = ExpandableListView.getPackedPositionType(packedPosition);
+
+ if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
+ .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
+ .name;
+ } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
+ return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
+ .name;
+ } else {
+ throw new IllegalStateException("packedPosition is not a valid position.");
+ }
+ }
+
+ /**
+ * Whether a particular position is out of bounds.
+ *
+ * @param packedPosition The packed position.
+ * @return Whether it's out of bounds.
+ */
+ private boolean isOutOfBounds(long packedPosition) {
+ final int type = ExpandableListView.getPackedPositionType(packedPosition);
+
+ if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
+ throw new IllegalStateException("packedPosition is not a valid position.");
+ }
+
+ final int group = ExpandableListView.getPackedPositionGroup(packedPosition);
+ if (group >= mGroups.size() || group < 0) {
+ return true;
+ }
+
+ if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ final int child = ExpandableListView.getPackedPositionChild(packedPosition);
+ if (child >= mGroups.get(group).children.size() || child < 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets a view for the packed position, possibly reusing the convertView.
+ *
+ * @param packedPosition The position to get a view for.
+ * @param convertView Optional view to convert.
+ * @param parent The future parent.
+ * @return A view.
+ */
+ private View getView(long packedPosition, View convertView, ViewGroup parent) {
+ if (isOutOfBounds(packedPosition)) {
+ throw new IllegalStateException("position out of range for adapter!");
+ }
+
+ final ExpandableListView elv = getExpandableListView();
+ final int flPos = elv.getFlatListPosition(packedPosition);
+
+ if (convertView != null) {
+ ((TextView) convertView).setText(getValueAtPosition(packedPosition));
+ convertView.setId(flPos);
+ return convertView;
+ }
+
+ int desiredHeight = getHeightForPosition(flPos);
+ return createView(packedPosition, flPos, parent, desiredHeight);
+ }
+
+ /**
+ * Create a view for a group or child position.
+ *
+ * @param packedPosition The packed position (has type, group pos, and optionally child pos).
+ * @param flPos The flat list position (the position that the ListView goes by).
+ * @param parent The parent view.
+ * @param desiredHeight The desired height.
+ * @return A view.
+ */
+ protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
+ TextView result = new TextView(parent.getContext());
+ result.setHeight(desiredHeight);
+ result.setText(getValueAtPosition(packedPosition));
+ final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ result.setLayoutParams(lp);
+ result.setGravity(Gravity.CENTER_VERTICAL);
+ result.setPadding(36, 0, 0, 0);
+ result.setId(flPos);
+ return result;
+ }
+
+ /**
+ * Returns a group index containing either the number of children or at
+ * least one child.
+ *
+ * @param numChildren The group must have this amount, or -1 if using
+ * atLeastOneChild.
+ * @param atLeastOneChild The group must have at least one child, or false
+ * if using numChildren.
+ * @return A group index with the requirements.
+ */
+ public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
+ final ExpandableListAdapter adapter = mAdapter;
+
+ for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
+ final int curNumChildren = adapter.getChildrenCount(i);
+
+ if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public List<MyGroup> getGroups() {
+ return mGroups;
+ }
+
+ public ExpandableListAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Simple expandable list adapter.
+ */
+ protected class MyAdapter extends BaseExpandableListAdapter {
+ public Object getChild(int groupPosition, int childPosition) {
+ return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
+ childPosition));
+ }
+
+ public long getChildId(int groupPosition, int childPosition) {
+ return mGroups.get(groupPosition).children.get(childPosition).id;
+ }
+
+ public int getChildrenCount(int groupPosition) {
+ return mGroups.get(groupPosition).children.size();
+ }
+
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
+ childPosition), convertView, parent);
+ }
+
+ public Object getGroup(int groupPosition) {
+ return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
+ }
+
+ public int getGroupCount() {
+ return mGroups.size();
+ }
+
+ public long getGroupId(int groupPosition) {
+ return mGroups.get(groupPosition).id;
+ }
+
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
+ convertView, parent);
+ }
+
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ }
+
+ public static class MyGroup {
+ private static long mNextId = 1000;
+
+ String name;
+ long id = mNextId++;
+ List<MyChild> children;
+
+ public MyGroup(int numChildren) {
+ name = "Group " + id;
+ children = new ArrayList<MyChild>(numChildren);
+ for (int i = 0; i < numChildren; i++) {
+ children.add(new MyChild());
+ }
+ }
+ }
+
+ public static class MyChild {
+ private static long mNextId = 2000;
+
+ String name;
+ long id = mNextId++;
+
+ public MyChild() {
+ name = "Child " + id;
+ }
+ }
+
+ @Override
+ protected final void init(Params params) {
+ init((ExpandableParams) params);
+ }
+
+ /**
+ * @see ListScenario#init
+ */
+ protected abstract void init(ExpandableParams params);
+}
diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java
new file mode 100644
index 0000000..f479e2b
--- /dev/null
+++ b/core/tests/coretests/src/android/util/FloatMathTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class FloatMathTest extends TestCase {
+
+ @SmallTest
+ public void testSqrt() {
+ assertEquals(7, FloatMath.sqrt(49), 0);
+ assertEquals(10, FloatMath.sqrt(100), 0);
+ assertEquals(0, FloatMath.sqrt(0), 0);
+ assertEquals(1, FloatMath.sqrt(1), 0);
+ }
+
+ @SmallTest
+ public void testFloor() {
+ assertEquals(78, FloatMath.floor(78.89f), 0);
+ assertEquals(-79, FloatMath.floor(-78.89f), 0);
+ }
+
+ @SmallTest
+ public void testCeil() {
+ assertEquals(79, FloatMath.ceil(78.89f), 0);
+ assertEquals(-78, FloatMath.ceil(-78.89f), 0);
+ }
+
+ @SmallTest
+ public void testSin() {
+ assertEquals(0.0, FloatMath.sin(0), 0);
+ assertEquals(0.8414709848078965f, FloatMath.sin(1), 0);
+ }
+
+ @SmallTest
+ public void testCos() {
+ assertEquals(1.0f, FloatMath.cos(0), 0);
+ assertEquals(0.5403023058681398f, FloatMath.cos(1), 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/GridScenario.java b/core/tests/coretests/src/android/util/GridScenario.java
new file mode 100644
index 0000000..0f1730e
--- /dev/null
+++ b/core/tests/coretests/src/android/util/GridScenario.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.google.android.collect.Maps;
+
+import java.util.Map;
+
+/**
+ * Utility base class for creating various GridView scenarios. Configurable by the number
+ * of items, how tall each item should be (in relation to the screen height), and
+ * what item should start with selection.
+ */
+public abstract class GridScenario extends Activity {
+
+ private GridView mGridView;
+
+ private int mNumItems;
+
+ private int mStartingSelectionPosition;
+ private double mItemScreenSizeFactor;
+ private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
+
+ private int mScreenHeight;
+
+ private boolean mStackFromBottom;
+
+ private int mColumnWidth;
+
+ private int mNumColumns;
+
+ private int mStretchMode;
+
+ private int mVerticalSpacing;
+
+ public GridView getGridView() {
+ return mGridView;
+ }
+
+ protected int getScreenHeight() {
+ return mScreenHeight;
+ }
+
+ /**
+ * @return The initial number of items in the grid as specified by the scenario.
+ * This number may change over time.
+ */
+ protected int getInitialNumItems() {
+ return mNumItems;
+ }
+
+ /**
+ * @return The desired height of 1 item, ignoring overrides
+ */
+ public int getDesiredItemHeight() {
+ return (int) (mScreenHeight * mItemScreenSizeFactor);
+ }
+
+ /**
+ * Better way to pass in optional params than a honkin' paramater list :)
+ */
+ public static class Params {
+ private int mNumItems = 4;
+ private int mStartingSelectionPosition = -1;
+ private double mItemScreenSizeFactor = 1 / 5;
+
+ private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
+
+ private boolean mStackFromBottom = false;
+ private boolean mMustFillScreen = true;
+
+ private int mColumnWidth = 0;
+ private int mNumColumns = GridView.AUTO_FIT;
+ private int mStretchMode = GridView.STRETCH_COLUMN_WIDTH;
+ private int mVerticalSpacing = 0;
+
+ /**
+ * Set the number of items in the grid.
+ */
+ public Params setNumItems(int numItems) {
+ mNumItems = numItems;
+ return this;
+ }
+
+ /**
+ * Set the position that starts selected.
+ *
+ * @param startingSelectionPosition The selected position within the adapter's data set.
+ * Pass -1 if you do not want to force a selection.
+ * @return
+ */
+ public Params setStartingSelectionPosition(int startingSelectionPosition) {
+ mStartingSelectionPosition = startingSelectionPosition;
+ return this;
+ }
+
+ /**
+ * Set the factor that determines how tall each item is in relation to the
+ * screen height.
+ */
+ public Params setItemScreenSizeFactor(double itemScreenSizeFactor) {
+ mItemScreenSizeFactor = itemScreenSizeFactor;
+ return this;
+ }
+
+ /**
+ * Override the item screen size factor for a particular item. Useful for
+ * creating grids with non-uniform item height.
+ * @param position The position in the grid.
+ * @param itemScreenSizeFactor The screen size factor to use for the height.
+ */
+ public Params setPositionScreenSizeFactorOverride(
+ int position, double itemScreenSizeFactor) {
+ mOverrideItemScreenSizeFactors.put(position, itemScreenSizeFactor);
+ return this;
+ }
+
+ /**
+ * Sets the stacking direction
+ * @param stackFromBottom
+ * @return
+ */
+ public Params setStackFromBottom(boolean stackFromBottom) {
+ mStackFromBottom = stackFromBottom;
+ return this;
+ }
+
+ /**
+ * Sets whether the sum of the height of the grid items must be at least the
+ * height of the grid view.
+ */
+ public Params setMustFillScreen(boolean fillScreen) {
+ mMustFillScreen = fillScreen;
+ return this;
+ }
+
+ /**
+ * Sets the individual width of each column.
+ *
+ * @param requestedWidth the width in pixels of the column
+ */
+ public Params setColumnWidth(int requestedWidth) {
+ mColumnWidth = requestedWidth;
+ return this;
+ }
+
+ /**
+ * Sets the number of columns in the grid.
+ */
+ public Params setNumColumns(int numColumns) {
+ mNumColumns = numColumns;
+ return this;
+ }
+
+ /**
+ * Sets the stretch mode.
+ */
+ public Params setStretchMode(int stretchMode) {
+ mStretchMode = stretchMode;
+ return this;
+ }
+
+ /**
+ * Sets the spacing between rows in the grid
+ */
+ public Params setVerticalSpacing(int verticalSpacing) {
+ mVerticalSpacing = verticalSpacing;
+ return this;
+ }
+ }
+
+ /**
+ * How each scenario customizes its behavior.
+ * @param params
+ */
+ protected abstract void init(Params params);
+
+ /**
+ * Override this to provide an different adapter for your scenario
+ * @return The adapter that this scenario will use
+ */
+ protected ListAdapter createAdapter() {
+ return new MyAdapter();
+ }
+
+ /**
+ * Override this if you want to know when something has been selected (perhaps
+ * more importantly, that {@link android.widget.AdapterView.OnItemSelectedListener} has
+ * been triggered).
+ */
+ @SuppressWarnings({ "UnusedDeclaration" })
+ protected void positionSelected(int positon) {
+
+ }
+
+ /**
+ * Override this if you want to know that nothing is selected.
+ */
+ protected void nothingSelected() {
+
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // turn off title bar
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
+
+ final Params params = new Params();
+ init(params);
+
+ readAndValidateParams(params);
+
+ mGridView = new GridView(this);
+ mGridView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mGridView.setDrawSelectorOnTop(false);
+ if (mNumColumns >= GridView.AUTO_FIT) {
+ mGridView.setNumColumns(mNumColumns);
+ }
+ if (mColumnWidth > 0) {
+ mGridView.setColumnWidth(mColumnWidth);
+ }
+ if (mVerticalSpacing > 0) {
+ mGridView.setVerticalSpacing(mVerticalSpacing);
+ }
+ mGridView.setStretchMode(mStretchMode);
+ mGridView.setAdapter(createAdapter());
+ if (mStartingSelectionPosition >= 0) {
+ mGridView.setSelection(mStartingSelectionPosition);
+ }
+ mGridView.setPadding(10, 10, 10, 10);
+ mGridView.setStackFromBottom(mStackFromBottom);
+
+ mGridView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ positionSelected(position);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ nothingSelected();
+ }
+ });
+
+ setContentView(mGridView);
+ }
+
+
+
+ /**
+ * Read in and validate all of the params passed in by the scenario.
+ * @param params
+ */
+ private void readAndValidateParams(Params params) {
+ if (params.mMustFillScreen ) {
+ double totalFactor = 0.0;
+ for (int i = 0; i < params.mNumItems; i++) {
+ if (params.mOverrideItemScreenSizeFactors.containsKey(i)) {
+ totalFactor += params.mOverrideItemScreenSizeFactors.get(i);
+ } else {
+ totalFactor += params.mItemScreenSizeFactor;
+ }
+ }
+ if (totalFactor < 1.0) {
+ throw new IllegalArgumentException("grid items must combine to be at least " +
+ "the height of the screen. this is not the case with " + params.mNumItems
+ + " items and " + params.mItemScreenSizeFactor + " screen factor and " +
+ "screen height of " + mScreenHeight);
+ }
+ }
+
+ mNumItems = params.mNumItems;
+ mStartingSelectionPosition = params.mStartingSelectionPosition;
+ mItemScreenSizeFactor = params.mItemScreenSizeFactor;
+
+ mOverrideItemScreenSizeFactors.putAll(params.mOverrideItemScreenSizeFactors);
+
+ mStackFromBottom = params.mStackFromBottom;
+ mColumnWidth = params.mColumnWidth;
+ mNumColumns = params.mNumColumns;
+ mStretchMode = params.mStretchMode;
+ mVerticalSpacing = params.mVerticalSpacing;
+ }
+
+ public final String getValueAtPosition(int position) {
+ return "postion " + position;
+ }
+
+ /**
+ * Create a view for a grid item. Override this to create a custom view beyond
+ * the simple focusable / unfocusable text view.
+ * @param position The position.
+ * @param parent The parent
+ * @param desiredHeight The height the view should be to respect the desired item
+ * to screen height ratio.
+ * @return a view for the grid.
+ */
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ TextView result = new TextView(parent.getContext());
+ result.setHeight(desiredHeight);
+ result.setText(getValueAtPosition(position));
+ final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ result.setLayoutParams(lp);
+ result.setId(position);
+ result.setBackgroundColor(0x55ffffff);
+ return result;
+ }
+
+
+
+ private class MyAdapter extends BaseAdapter {
+ public int getCount() {
+ return mNumItems;
+ }
+
+ public Object getItem(int position) {
+ return getValueAtPosition(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView != null) {
+ ((TextView) convertView).setText(getValueAtPosition(position));
+ convertView.setId(position);
+ return convertView;
+ }
+
+ int desiredHeight = getDesiredItemHeight();
+ if (mOverrideItemScreenSizeFactors.containsKey(position)) {
+ desiredHeight = (int) (mScreenHeight * mOverrideItemScreenSizeFactors.get(position));
+ }
+ return createView(position, parent, desiredHeight);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/InternalSelectionView.java b/core/tests/coretests/src/android/util/InternalSelectionView.java
new file mode 100644
index 0000000..babf38d
--- /dev/null
+++ b/core/tests/coretests/src/android/util/InternalSelectionView.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import com.android.frameworks.coretests.R;
+
+import android.view.View;
+import android.view.KeyEvent;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Paint;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Color;
+import android.util.AttributeSet;
+
+
+
+/**
+ * A view that has a known number of selectable rows, and maintains a notion of which
+ * row is selected. The rows take up the
+ * entire width of the view. The height of the view is divided evenly among
+ * the rows.
+ *
+ * Notice what this view does to be a good citizen w.r.t its internal selection:
+ * 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to
+ * internal navigation.
+ * 2) implements {@link View#getFocusedRect} by filling in the rectangle of the currently
+ * selected row
+ * 3) overrides {@link View#onFocusChanged} and sets selection appropriately according to
+ * the previously focused rectangle.
+ */
+public class InternalSelectionView extends View {
+
+ private Paint mPainter = new Paint();
+ private Paint mTextPaint = new Paint();
+ private Rect mTempRect = new Rect();
+
+ private int mNumRows = 5;
+ private int mSelectedRow = 0;
+ private final int mEstimatedPixelHeight = 10;
+
+ private Integer mDesiredHeight = null;
+ private String mLabel = null;
+
+ public InternalSelectionView(Context context, int numRows, String label) {
+ super(context);
+ mNumRows = numRows;
+ mLabel = label;
+ init();
+ }
+
+ public InternalSelectionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.SelectableRowView);
+ mNumRows = a.getInt(R.styleable.SelectableRowView_numRows, 5);
+ init();
+ }
+
+ private void init() {
+ setFocusable(true);
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setTextSize(10);
+ mTextPaint.setColor(Color.WHITE);
+ }
+
+ public int getNumRows() {
+ return mNumRows;
+ }
+
+ public int getSelectedRow() {
+ return mSelectedRow;
+ }
+
+ public void setDesiredHeight(int desiredHeight) {
+ mDesiredHeight = desiredHeight;
+ }
+
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(
+ measureWidth(widthMeasureSpec),
+ measureHeight(heightMeasureSpec));
+ }
+
+ private int measureWidth(int measureSpec) {
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ int desiredWidth = 300 + mPaddingLeft + mPaddingRight;
+ if (specMode == MeasureSpec.EXACTLY) {
+ // We were told how big to be
+ return specSize;
+ } else if (specMode == MeasureSpec.AT_MOST) {
+ return desiredWidth < specSize ? desiredWidth : specSize;
+ } else {
+ return desiredWidth;
+ }
+ }
+
+ private int measureHeight(int measureSpec) {
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ int desiredHeight = mDesiredHeight != null ?
+ mDesiredHeight :
+ mNumRows * mEstimatedPixelHeight + mPaddingTop + mPaddingBottom;
+ if (specMode == MeasureSpec.EXACTLY) {
+ // We were told how big to be
+ return specSize;
+ } else if (specMode == MeasureSpec.AT_MOST) {
+ return desiredHeight < specSize ? desiredHeight : specSize;
+ } else {
+ return desiredHeight;
+ }
+ }
+
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+
+ int rowHeight = getRowHeight();
+
+ int rectTop = mPaddingTop;
+ int rectLeft = mPaddingLeft;
+ int rectRight = getWidth() - mPaddingRight;
+ for (int i = 0; i < mNumRows; i++) {
+
+ mPainter.setColor(Color.BLACK);
+ mPainter.setAlpha(0x20);
+
+ // draw background rect
+ mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight);
+ canvas.drawRect(mTempRect, mPainter);
+
+ // draw forground rect
+ if (i == mSelectedRow && hasFocus()) {
+ mPainter.setColor(Color.RED);
+ mPainter.setAlpha(0xF0);
+ mTextPaint.setAlpha(0xFF);
+ } else {
+ mPainter.setColor(Color.BLACK);
+ mPainter.setAlpha(0x40);
+ mTextPaint.setAlpha(0xF0);
+ }
+ mTempRect.set(rectLeft + 2, rectTop + 2,
+ rectRight - 2, rectTop + rowHeight - 2);
+ canvas.drawRect(mTempRect, mPainter);
+
+ // draw text to help when visually inspecting
+ canvas.drawText(
+ Integer.toString(i),
+ rectLeft + 2,
+ rectTop + 2 - (int) mTextPaint.ascent(),
+ mTextPaint);
+
+ rectTop += rowHeight;
+ }
+ }
+
+ private int getRowHeight() {
+ return (getHeight() - mPaddingTop - mPaddingBottom) / mNumRows;
+ }
+
+ public void getRectForRow(Rect rect, int row) {
+ final int rowHeight = getRowHeight();
+ final int top = mPaddingTop + row * rowHeight;
+ rect.set(mPaddingLeft,
+ top,
+ getWidth() - mPaddingRight,
+ top + rowHeight);
+ }
+
+
+ void ensureRectVisible() {
+ getRectForRow(mTempRect, mSelectedRow);
+ requestRectangleOnScreen(mTempRect);
+ }
+
+
+ /* (non-Javadoc)
+ * @see android.view.KeyEvent.Callback#onKeyDown(int, android.view.KeyEvent)
+ */
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch(event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (mSelectedRow > 0) {
+ mSelectedRow--;
+ invalidate();
+ ensureRectVisible();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (mSelectedRow < (mNumRows - 1)) {
+ mSelectedRow++;
+ invalidate();
+ ensureRectVisible();
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+
+ @Override
+ public void getFocusedRect(Rect r) {
+ getRectForRow(r, mSelectedRow);
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+
+ if (focused) {
+ switch (direction) {
+ case View.FOCUS_DOWN:
+ mSelectedRow = 0;
+ break;
+ case View.FOCUS_UP:
+ mSelectedRow = mNumRows - 1;
+ break;
+ case View.FOCUS_LEFT: // fall through
+ case View.FOCUS_RIGHT:
+ // set the row that is closest to the rect
+ if (previouslyFocusedRect != null) {
+ int y = previouslyFocusedRect.top
+ + (previouslyFocusedRect.height() / 2);
+ int yPerRow = getHeight() / mNumRows;
+ mSelectedRow = y / yPerRow;
+ } else {
+ mSelectedRow = 0;
+ }
+ break;
+ default:
+ // can't gleam any useful information about what internal
+ // selection should be...
+ return;
+ }
+ invalidate();
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (mLabel != null) {
+ return mLabel;
+ }
+ return super.toString();
+ }
+}
diff --git a/core/tests/coretests/src/android/util/KeyUtils.java b/core/tests/coretests/src/android/util/KeyUtils.java
new file mode 100644
index 0000000..b58fda3
--- /dev/null
+++ b/core/tests/coretests/src/android/util/KeyUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.InstrumentationTestCase;
+import android.view.Gravity;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+
+/**
+ * Reusable methods for generating key events.
+ * <p>
+ * Definitions:
+ * <li> Tap refers to pushing and releasing a button (down and up event).
+ * <li> Chord refers to pushing a modifier key, tapping a regular key, and
+ * releasing the modifier key.
+ */
+public class KeyUtils {
+ /**
+ * Simulates tapping the menu key.
+ *
+ * @param test The test case that is being run.
+ */
+ public static void tapMenuKey(ActivityInstrumentationTestCase test) {
+ final Instrumentation inst = test.getInstrumentation();
+
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU));
+ }
+
+ /**
+ * Simulates chording the menu key.
+ *
+ * @param test The test case that is being run.
+ * @param shortcutKey The shortcut key to tap while chording the menu key.
+ */
+ public static void chordMenuKey(ActivityInstrumentationTestCase test, char shortcutKey) {
+ final Instrumentation inst = test.getInstrumentation();
+
+ final KeyEvent pushMenuKey = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU);
+ final KeyCharacterMap keyCharMap = KeyCharacterMap.load(pushMenuKey.getDeviceId());
+ final KeyEvent shortcutKeyEvent = keyCharMap.getEvents(new char[] { shortcutKey })[0];
+ final int shortcutKeyCode = shortcutKeyEvent.getKeyCode();
+
+ inst.sendKeySync(pushMenuKey);
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, shortcutKeyCode));
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, shortcutKeyCode));
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU));
+ }
+
+ /**
+ * Simulates a long click via the keyboard.
+ *
+ * @param test The test case that is being run.
+ */
+ public static void longClick(ActivityInstrumentationTestCase test) {
+ final Instrumentation inst = test.getInstrumentation();
+
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER));
+ try {
+ Thread.sleep((long)(ViewConfiguration.getLongPressTimeout() * 1.5f));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER));
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ListItemFactory.java b/core/tests/coretests/src/android/util/ListItemFactory.java
new file mode 100644
index 0000000..e8a498d
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ListItemFactory.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Reusable methods for creating more complex list items.
+ */
+public class ListItemFactory {
+
+ /**
+ * Create a view with a button at the top and bottom, with filler in between.
+ * The filler is sized to take up any space left over within desiredHeight.
+ *
+ * @param position The position within the list.
+ * @param context The context.
+ * @param desiredHeight The desired height of the entire view.
+ * @return The created view.
+ */
+ public static View twoButtonsSeparatedByFiller(int position, Context context, int desiredHeight) {
+ if (desiredHeight < 90) {
+ throw new IllegalArgumentException("need at least 90 pixels of height " +
+ "to create the two buttons and leave 10 pixels for the filler");
+ }
+
+ final LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.VERTICAL);
+
+ final LinearLayout.LayoutParams buttonLp =
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 50);
+
+ final Button topButton = new Button(context);
+ topButton.setLayoutParams(
+ buttonLp);
+ topButton.setText("top (position " + position + ")");
+ ll.addView(topButton);
+
+ final TextView middleFiller = new TextView(context);
+ middleFiller.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ desiredHeight - 100));
+ middleFiller.setText("filler");
+ ll.addView(middleFiller);
+
+ final Button bottomButton = new Button(context);
+ bottomButton.setLayoutParams(buttonLp);
+ bottomButton.setText("bottom (position " + position + ")");
+ ll.addView(bottomButton);
+ ll.setTag("twoButtons");
+ return ll;
+ }
+
+ public enum Slot {
+ Left,
+ Middle,
+ Right
+ }
+
+ /**
+ * Create a horizontal linear layout divided into thirds (with some margins
+ * separating the thirds), filled with buttons into some slots.
+ * @param context The context.
+ * @param desiredHeight The height of the LL.
+ * @param slots Which slots to fill with buttons.
+ * @return The linear layout.
+ */
+ public static View horizontalButtonSlots(Context context, int desiredHeight, Slot... slots) {
+
+ final LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.HORIZONTAL);
+
+ final LinearLayout.LayoutParams lp
+ = new LinearLayout.LayoutParams(0, desiredHeight);
+ lp.setMargins(10, 0, 10, 0);
+ lp.weight = 0.33f;
+
+ boolean left = false;
+ boolean middle = false;
+ boolean right = false;
+ for (Slot slot : slots) {
+ switch (slot) {
+ case Left:
+ left = true;
+ break;
+ case Middle:
+ middle = true;
+ break;
+ case Right:
+ right = true;
+ break;
+ }
+ }
+
+ if (left) {
+ final Button button = new Button(context);
+ button.setText("left");
+ ll.addView(button, lp);
+ } else {
+ ll.addView(new View(context), lp);
+ }
+
+ if (middle) {
+ final Button button = new Button(context);
+ button.setText("center");
+ ll.addView(button, lp);
+ } else {
+ ll.addView(new View(context), lp);
+ }
+
+ if (right) {
+ final Button button = new Button(context);
+ button.setText("right");
+ ll.addView(button, lp);
+ } else {
+ ll.addView(new View(context), lp);
+ }
+
+ return ll;
+ }
+
+
+ /**
+ * Create a button ready to be a list item.
+ *
+ * @param position The position within the list.
+ * @param context The context.
+ * @param text The text of the button
+ * @param desiredHeight The desired height of the button
+ * @return The created view.
+ */
+ public static View button(int position, Context context, String text, int desiredHeight) {
+ TextView result = new Button(context);
+ result.setHeight(desiredHeight);
+ result.setText(text);
+ final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ result.setLayoutParams(lp);
+ result.setId(position);
+ result.setTag("button");
+ return result;
+ }
+
+ /**
+ * Convert an existing button view to display the data at a new position.
+ *
+ * @param convertView Non-null Button created by {@link #button}
+ * @param text The text of the button
+ * @param position The position withion the list
+ * @return The converted view
+ */
+ public static View convertButton(View convertView, String text, int position) {
+ if (((String) convertView.getTag()).equals("button")) {
+ ((Button) convertView).setText(text);
+ convertView.setId(position);
+ return convertView;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Create a text view ready to be a list item.
+ *
+ * @param position The position within the list.
+ * @param context The context.
+ * @param text The text to display
+ * @param desiredHeight The desired height of the text view
+ * @return The created view.
+ */
+ public static View text(int position, Context context, String text, int desiredHeight) {
+ TextView result = new TextView(context);
+ result.setHeight(desiredHeight);
+ result.setText(text);
+ final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ result.setLayoutParams(lp);
+ result.setId(position);
+ result.setTag("text");
+ return result;
+ }
+
+ /**
+ * Convert an existing text view to display the data at a new position.
+ *
+ * @param convertView Non-null TextView created by {@link #text}
+ * @param text The text to display
+ * @param position The position withion the list
+ * @return The converted view
+ */
+ public static View convertText(View convertView, String text, int position) {
+ if(convertView.getTag() != null && ((String) convertView.getTag()).equals("text")) {
+ ((TextView) convertView).setText(text);
+ convertView.setId(position);
+ return convertView;
+
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Create a text view ready to be a list item.
+ *
+ * @param position The position within the list.
+ * @param context The context.
+ * @param text The text of the button
+ * @param desiredHeight The desired height of the button
+ * @return The created view.
+ */
+ public static View doubleText(int position, Context context, String text, int desiredHeight) {
+ final LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.HORIZONTAL);
+
+ final AbsListView.LayoutParams lp =
+ new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ desiredHeight);
+ ll.setLayoutParams(lp);
+ ll.setId(position);
+
+ TextView t1 = new TextView(context);
+ t1.setHeight(desiredHeight);
+ t1.setText(text);
+ t1.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ final ViewGroup.LayoutParams lp1 = new LinearLayout.LayoutParams(
+ 0,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f);
+ ll.addView(t1, lp1);
+
+ TextView t2 = new TextView(context);
+ t2.setHeight(desiredHeight);
+ t2.setText(text);
+ t2.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
+ final ViewGroup.LayoutParams lp2 = new LinearLayout.LayoutParams(
+ 0,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1.0f);
+
+ ll.addView(t2, lp2);
+ ll.setTag("double");
+ return ll;
+ }
+
+
+ /**
+ * Convert an existing button view to display the data at a new position.
+ *
+ * @param convertView Non-null view created by {@link #doubleText}
+ * @param text The text of the button
+ * @param position The position withion the list
+ * @return The converted view
+ */
+ public static View convertDoubleText(View convertView, String text, int position) {
+ if (((String) convertView.getTag()).equals("double")) {
+ TextView t1 = (TextView) ((LinearLayout) convertView).getChildAt(0);
+ TextView t2 = (TextView) ((LinearLayout) convertView).getChildAt(1);
+ t1.setText(text);
+ t2.setText(text);
+ convertView.setId(position);
+ return convertView;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ListScenario.java b/core/tests/coretests/src/android/util/ListScenario.java
new file mode 100644
index 0000000..22be4e7
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ListScenario.java
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.google.android.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility base class for creating various List scenarios. Configurable by the number
+ * of items, how tall each item should be (in relation to the screen height), and
+ * what item should start with selection.
+ */
+public abstract class ListScenario extends Activity {
+
+ private ListView mListView;
+ private TextView mHeaderTextView;
+
+ private int mNumItems;
+ protected boolean mItemsFocusable;
+
+ private int mStartingSelectionPosition;
+ private double mItemScreenSizeFactor;
+ private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
+
+ private int mScreenHeight;
+
+ // whether to include a text view above the list
+ private boolean mIncludeHeader;
+
+ // separators
+ private Set<Integer> mUnselectableItems = new HashSet<Integer>();
+
+ private boolean mStackFromBottom;
+
+ private int mClickedPosition = -1;
+
+ private int mLongClickedPosition = -1;
+
+ private int mConvertMisses = 0;
+
+ private int mHeaderViewCount;
+ private boolean mHeadersFocusable;
+
+ private int mFooterViewCount;
+ private LinearLayout mLinearLayout;
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ protected int getScreenHeight() {
+ return mScreenHeight;
+ }
+
+ /**
+ * Return whether the item at position is selectable (i.e is a separator).
+ * (external users can access this info using the adapter)
+ */
+ private boolean isItemAtPositionSelectable(int position) {
+ return !mUnselectableItems.contains(position);
+ }
+
+ /**
+ * Better way to pass in optional params than a honkin' paramater list :)
+ */
+ public static class Params {
+ private int mNumItems = 4;
+ private boolean mItemsFocusable = false;
+ private int mStartingSelectionPosition = 0;
+ private double mItemScreenSizeFactor = 1 / 5;
+ private Double mFadingEdgeScreenSizeFactor = null;
+
+ private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
+
+ // separators
+ private List<Integer> mUnselectableItems = new ArrayList<Integer>(8);
+ // whether to include a text view above the list
+ private boolean mIncludeHeader = false;
+ private boolean mStackFromBottom = false;
+ public boolean mMustFillScreen = true;
+ private int mHeaderViewCount;
+ private boolean mHeaderFocusable = false;
+ private int mFooterViewCount;
+
+ private boolean mConnectAdapter = true;
+
+ /**
+ * Set the number of items in the list.
+ */
+ public Params setNumItems(int numItems) {
+ mNumItems = numItems;
+ return this;
+ }
+
+ /**
+ * Set whether the items are focusable.
+ */
+ public Params setItemsFocusable(boolean itemsFocusable) {
+ mItemsFocusable = itemsFocusable;
+ return this;
+ }
+
+ /**
+ * Set the position that starts selected.
+ *
+ * @param startingSelectionPosition The selected position within the adapter's data set.
+ * Pass -1 if you do not want to force a selection.
+ * @return
+ */
+ public Params setStartingSelectionPosition(int startingSelectionPosition) {
+ mStartingSelectionPosition = startingSelectionPosition;
+ return this;
+ }
+
+ /**
+ * Set the factor that determines how tall each item is in relation to the
+ * screen height.
+ */
+ public Params setItemScreenSizeFactor(double itemScreenSizeFactor) {
+ mItemScreenSizeFactor = itemScreenSizeFactor;
+ return this;
+ }
+
+ /**
+ * Override the item screen size factor for a particular item. Useful for
+ * creating lists with non-uniform item height.
+ * @param position The position in the list.
+ * @param itemScreenSizeFactor The screen size factor to use for the height.
+ */
+ public Params setPositionScreenSizeFactorOverride(
+ int position, double itemScreenSizeFactor) {
+ mOverrideItemScreenSizeFactors.put(position, itemScreenSizeFactor);
+ return this;
+ }
+
+ /**
+ * Set a position as unselectable (a.k.a a separator)
+ * @param position
+ * @return
+ */
+ public Params setPositionUnselectable(int position) {
+ mUnselectableItems.add(position);
+ return this;
+ }
+
+ /**
+ * Set positions as unselectable (a.k.a a separator)
+ */
+ public Params setPositionsUnselectable(int ...positions) {
+ for (int pos : positions) {
+ setPositionUnselectable(pos);
+ }
+ return this;
+ }
+
+ /**
+ * Include a header text view above the list.
+ * @param includeHeader
+ * @return
+ */
+ public Params includeHeaderAboveList(boolean includeHeader) {
+ mIncludeHeader = includeHeader;
+ return this;
+ }
+
+ /**
+ * Sets the stacking direction
+ * @param stackFromBottom
+ * @return
+ */
+ public Params setStackFromBottom(boolean stackFromBottom) {
+ mStackFromBottom = stackFromBottom;
+ return this;
+ }
+
+ /**
+ * Sets whether the sum of the height of the list items must be at least the
+ * height of the list view.
+ */
+ public Params setMustFillScreen(boolean fillScreen) {
+ mMustFillScreen = fillScreen;
+ return this;
+ }
+
+ /**
+ * Set the factor for the fading edge length.
+ */
+ public Params setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
+ mFadingEdgeScreenSizeFactor = fadingEdgeScreenSizeFactor;
+ return this;
+ }
+
+ /**
+ * Set the number of header views to appear within the list
+ */
+ public Params setHeaderViewCount(int headerViewCount) {
+ mHeaderViewCount = headerViewCount;
+ return this;
+ }
+
+ /**
+ * Set whether the headers should be focusable.
+ * @param headerFocusable Whether the headers should be focusable (i.e
+ * created as edit texts rather than text views).
+ */
+ public Params setHeaderFocusable(boolean headerFocusable) {
+ mHeaderFocusable = headerFocusable;
+ return this;
+ }
+
+ /**
+ * Set the number of footer views to appear within the list
+ */
+ public Params setFooterViewCount(int footerViewCount) {
+ mFooterViewCount = footerViewCount;
+ return this;
+ }
+
+ /**
+ * Sets whether the {@link ListScenario} will automatically set the
+ * adapter on the list view. If this is false, the client MUST set it
+ * manually (this is useful when adding headers to the list view, which
+ * must be done before the adapter is set).
+ */
+ public Params setConnectAdapter(boolean connectAdapter) {
+ mConnectAdapter = connectAdapter;
+ return this;
+ }
+ }
+
+ /**
+ * How each scenario customizes its behavior.
+ * @param params
+ */
+ protected abstract void init(Params params);
+
+ /**
+ * Override this if you want to know when something has been selected (perhaps
+ * more importantly, that {@link android.widget.AdapterView.OnItemSelectedListener} has
+ * been triggered).
+ */
+ protected void positionSelected(int positon) {
+ }
+
+ /**
+ * Override this if you want to know that nothing is selected.
+ */
+ protected void nothingSelected() {
+ }
+
+ /**
+ * Override this if you want to know when something has been clicked (perhaps
+ * more importantly, that {@link android.widget.AdapterView.OnItemClickListener} has
+ * been triggered).
+ */
+ protected void positionClicked(int position) {
+ setClickedPosition(position);
+ }
+
+ /**
+ * Override this if you want to know when something has been long clicked (perhaps
+ * more importantly, that {@link android.widget.AdapterView.OnItemLongClickListener} has
+ * been triggered).
+ */
+ protected void positionLongClicked(int position) {
+ setLongClickedPosition(position);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // for test stability, turn off title bar
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+
+ mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
+
+ final Params params = createParams();
+ init(params);
+
+ readAndValidateParams(params);
+
+
+ mListView = createListView();
+ mListView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mListView.setDrawSelectorOnTop(false);
+
+ for (int i=0; i<mHeaderViewCount; i++) {
+ TextView header = mHeadersFocusable ?
+ new EditText(this) :
+ new TextView(this);
+ header.setText("Header: " + i);
+ mListView.addHeaderView(header);
+ }
+
+ for (int i=0; i<mFooterViewCount; i++) {
+ TextView header = new TextView(this);
+ header.setText("Footer: " + i);
+ mListView.addFooterView(header);
+ }
+
+ if (params.mConnectAdapter) {
+ setAdapter(mListView);
+ }
+
+ mListView.setItemsCanFocus(mItemsFocusable);
+ if (mStartingSelectionPosition >= 0) {
+ mListView.setSelection(mStartingSelectionPosition);
+ }
+ mListView.setPadding(0, 0, 0, 0);
+ mListView.setStackFromBottom(mStackFromBottom);
+ mListView.setDivider(null);
+
+ mListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ positionSelected(position);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ nothingSelected();
+ }
+ });
+
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView parent, View v, int position, long id) {
+ positionClicked(position);
+ }
+ });
+
+ // set the fading edge length porportionally to the screen
+ // height for test stability
+ if (params.mFadingEdgeScreenSizeFactor != null) {
+ mListView.setFadingEdgeLength((int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));
+ } else {
+ mListView.setFadingEdgeLength((int) ((64.0 / 480) * mScreenHeight));
+ }
+
+ if (mIncludeHeader) {
+ mLinearLayout = new LinearLayout(this);
+
+ mHeaderTextView = new TextView(this);
+ mHeaderTextView.setText("hi");
+ mHeaderTextView.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mLinearLayout.addView(mHeaderTextView);
+
+ mLinearLayout.setOrientation(LinearLayout.VERTICAL);
+ mLinearLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mListView.setLayoutParams((new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0,
+ 1f)));
+
+ mLinearLayout.addView(mListView);
+ setContentView(mLinearLayout);
+ } else {
+ mLinearLayout = new LinearLayout(this);
+ mLinearLayout.setOrientation(LinearLayout.VERTICAL);
+ mLinearLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mListView.setLayoutParams((new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0,
+ 1f)));
+ mLinearLayout.addView(mListView);
+ setContentView(mLinearLayout);
+ }
+ }
+
+ /**
+ * Returns the LinearLayout containing the ListView in this scenario.
+ *
+ * @return The LinearLayout in which the ListView is held.
+ */
+ protected LinearLayout getListViewContainer() {
+ return mLinearLayout;
+ }
+
+ /**
+ * Attaches a long press listener. You can find out which views were clicked by calling
+ * {@link #getLongClickedPosition()}.
+ */
+ public void enableLongPress() {
+ mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ public boolean onItemLongClick(AdapterView parent, View v, int position, long id) {
+ positionLongClicked(position);
+ return true;
+ }
+ });
+ }
+
+ /**
+ * @return The newly created ListView widget.
+ */
+ protected ListView createListView() {
+ return new ListView(this);
+ }
+
+ /**
+ * @return The newly created Params object.
+ */
+ protected Params createParams() {
+ return new Params();
+ }
+
+ /**
+ * Sets an adapter on a ListView.
+ *
+ * @param listView The ListView to set the adapter on.
+ */
+ protected void setAdapter(ListView listView) {
+ listView.setAdapter(new MyAdapter());
+ }
+
+ /**
+ * Read in and validate all of the params passed in by the scenario.
+ * @param params
+ */
+ protected void readAndValidateParams(Params params) {
+ if (params.mMustFillScreen ) {
+ double totalFactor = 0.0;
+ for (int i = 0; i < params.mNumItems; i++) {
+ if (params.mOverrideItemScreenSizeFactors.containsKey(i)) {
+ totalFactor += params.mOverrideItemScreenSizeFactors.get(i);
+ } else {
+ totalFactor += params.mItemScreenSizeFactor;
+ }
+ }
+ if (totalFactor < 1.0) {
+ throw new IllegalArgumentException("list items must combine to be at least " +
+ "the height of the screen. this is not the case with " + params.mNumItems
+ + " items and " + params.mItemScreenSizeFactor + " screen factor and " +
+ "screen height of " + mScreenHeight);
+ }
+ }
+
+ mNumItems = params.mNumItems;
+ mItemsFocusable = params.mItemsFocusable;
+ mStartingSelectionPosition = params.mStartingSelectionPosition;
+ mItemScreenSizeFactor = params.mItemScreenSizeFactor;
+
+ mOverrideItemScreenSizeFactors.putAll(params.mOverrideItemScreenSizeFactors);
+
+ mUnselectableItems.addAll(params.mUnselectableItems);
+ mIncludeHeader = params.mIncludeHeader;
+ mStackFromBottom = params.mStackFromBottom;
+ mHeaderViewCount = params.mHeaderViewCount;
+ mHeadersFocusable = params.mHeaderFocusable;
+ mFooterViewCount = params.mFooterViewCount;
+ }
+
+ public final String getValueAtPosition(int position) {
+ return isItemAtPositionSelectable(position)
+ ?
+ "position " + position:
+ "------- " + position;
+ }
+
+ /**
+ * @return The height that will be set for a particular position.
+ */
+ public int getHeightForPosition(int position) {
+ int desiredHeight = (int) (mScreenHeight * mItemScreenSizeFactor);
+ if (mOverrideItemScreenSizeFactors.containsKey(position)) {
+ desiredHeight = (int) (mScreenHeight * mOverrideItemScreenSizeFactors.get(position));
+ }
+ return desiredHeight;
+ }
+
+
+ /**
+ * @return The contents of the header above the list.
+ * @throws IllegalArgumentException if there is no header.
+ */
+ public final String getHeaderValue() {
+ if (!mIncludeHeader) {
+ throw new IllegalArgumentException("no header above list");
+ }
+ return mHeaderTextView.getText().toString();
+ }
+
+ /**
+ * @param value What to put in the header text view
+ * @throws IllegalArgumentException if there is no header.
+ */
+ protected final void setHeaderValue(String value) {
+ if (!mIncludeHeader) {
+ throw new IllegalArgumentException("no header above list");
+ }
+ mHeaderTextView.setText(value);
+ }
+
+ /**
+ * Create a view for a list item. Override this to create a custom view beyond
+ * the simple focusable / unfocusable text view.
+ * @param position The position.
+ * @param parent The parent
+ * @param desiredHeight The height the view should be to respect the desired item
+ * to screen height ratio.
+ * @return a view for the list.
+ */
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ return ListItemFactory.text(position, parent.getContext(), getValueAtPosition(position),
+ desiredHeight);
+ }
+
+ /**
+ * Convert a non-null view.
+ */
+ public View convertView(int position, View convertView, ViewGroup parent) {
+ return ListItemFactory.convertText(convertView, getValueAtPosition(position), position);
+ }
+
+ public void setClickedPosition(int clickedPosition) {
+ mClickedPosition = clickedPosition;
+ }
+
+ public int getClickedPosition() {
+ return mClickedPosition;
+ }
+
+ public void setLongClickedPosition(int longClickedPosition) {
+ mLongClickedPosition = longClickedPosition;
+ }
+
+ public int getLongClickedPosition() {
+ return mLongClickedPosition;
+ }
+
+ /**
+ * Have a child of the list view call {@link View#requestRectangleOnScreen(android.graphics.Rect)}.
+ * @param childIndex The index into the viewgroup children (i.e the children that are
+ * currently visible).
+ * @param rect The rectangle, in the child's coordinates.
+ */
+ public void requestRectangleOnScreen(int childIndex, final Rect rect) {
+ final View child = getListView().getChildAt(childIndex);
+
+ child.post(new Runnable() {
+ public void run() {
+ child.requestRectangleOnScreen(rect);
+ }
+ });
+ }
+
+ /**
+ * Return an item type for the specified position in the adapter. Override if your
+ * adapter creates more than one type.
+ */
+ public int getItemViewType(int position) {
+ return 0;
+ }
+
+ /**
+ * Return an the number of types created by the adapter. Override if your
+ * adapter creates more than one type.
+ */
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ /**
+ * @return The number of times convertView failed
+ */
+ public int getConvertMisses() {
+ return mConvertMisses;
+ }
+
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ return mNumItems;
+ }
+
+ public Object getItem(int position) {
+ return getValueAtPosition(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return mUnselectableItems.isEmpty();
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return isItemAtPositionSelectable(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View result = null;
+ if (position >= mNumItems || position < 0) {
+ throw new IllegalStateException("position out of range for adapter!");
+ }
+
+ if (convertView != null) {
+ result = convertView(position, convertView, parent);
+ if (result == null) {
+ mConvertMisses++;
+ }
+ }
+
+ if (result == null) {
+ int desiredHeight = getHeightForPosition(position);
+ result = createView(position, parent, desiredHeight);
+ }
+ return result;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return ListScenario.this.getItemViewType(position);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return ListScenario.this.getViewTypeCount();
+ }
+
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ListUtil.java b/core/tests/coretests/src/android/util/ListUtil.java
new file mode 100644
index 0000000..2a7cb96
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ListUtil.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.app.Instrumentation;
+import android.view.KeyEvent;
+import android.widget.ListView;
+
+
+/**
+ * Various useful stuff for instrumentation testing listview.
+ */
+public class ListUtil {
+
+
+ private final ListView mListView;
+ private final Instrumentation mInstrumentation;
+
+ /**
+ * @param listView The listview to act on
+ * @param instrumentation The instrumentation to use.
+ */
+ public ListUtil(ListView listView, Instrumentation instrumentation) {
+ mListView = listView;
+ mInstrumentation = instrumentation;
+ }
+
+ /**
+ * Set the selected position of the list view.
+ * @param pos The desired position.
+ */
+ public final void setSelectedPosition(final int pos) {
+ mListView.post(new Runnable() {
+ public void run() {
+ mListView.setSelection(pos);
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+
+ /**
+ * Get the top of the list.
+ */
+ public final int getListTop() {
+ return mListView.getListPaddingTop();
+ }
+
+ /**
+ * Get the bottom of the list.
+ */
+ public final int getListBottom() {
+ return mListView.getHeight() - mListView.getListPaddingBottom();
+ }
+
+ /**
+ * Arrow (up or down as appropriate) to the desired position in the list.
+ * @param desiredPos The desired position
+ * @throws IllegalStateException if the position can't be reached within 20 presses.
+ */
+ public final void arrowScrollToSelectedPosition(int desiredPos) {
+ if (desiredPos > mListView.getSelectedItemPosition()) {
+ arrowDownToSelectedPosition(desiredPos);
+ } else {
+ arrowUpToSelectedPosition(desiredPos);
+ }
+ }
+
+ private void arrowDownToSelectedPosition(int position) {
+ int maxDowns = 20;
+ while(mListView.getSelectedItemPosition() < position && --maxDowns > 0) {
+ mInstrumentation.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ if (position != mListView.getSelectedItemPosition()) {
+ throw new IllegalStateException("couldn't get to item after 20 downs");
+ }
+
+ }
+
+ private void arrowUpToSelectedPosition(int position) {
+ int maxUps = 20;
+ while(mListView.getSelectedItemPosition() > position && --maxUps > 0) {
+ mInstrumentation.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ if (position != mListView.getSelectedItemPosition()) {
+ throw new IllegalStateException("couldn't get to item after 20 ups");
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
new file mode 100644
index 0000000..30c81b0
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import android.os.SystemProperties;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+
+//This is an empty TestCase.
+@Suppress
+public class LogTest extends TestCase {
+ private static final String PROPERTY_TAG = "log.tag.LogTest";
+ private static final String LOG_TAG = "LogTest";
+
+
+ // TODO: remove this test once we uncomment out the following test.
+ public void testLogTestDummy() {
+ return;
+ }
+
+
+ /* TODO: This test is commented out because we will not be able to set properities. Fix the test.
+ public void testIsLoggable() {
+ // First clear any SystemProperty setting for our test key.
+ SystemProperties.set(PROPERTY_TAG, null);
+
+ String value = SystemProperties.get(PROPERTY_TAG);
+ Assert.assertTrue(value == null || value.length() == 0);
+
+ // Check to make sure that all levels expect for INFO, WARN, ERROR, and ASSERT are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be VERBOSE for this tag.
+ SystemProperties.set(PROPERTY_TAG, "VERBOSE");
+
+ // Test to make sure all log levels >= VERBOSE are loggable.
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be DEBUG for this tag.
+ SystemProperties.set(PROPERTY_TAG, "DEBUG");
+
+ // Test to make sure all log levels >= DEBUG are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be INFO for this tag.
+ SystemProperties.set(PROPERTY_TAG, "INFO");
+
+ // Test to make sure all log levels >= INFO are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be WARN for this tag.
+ SystemProperties.set(PROPERTY_TAG, "WARN");
+
+ // Test to make sure all log levels >= WARN are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be ERROR for this tag.
+ SystemProperties.set(PROPERTY_TAG, "ERROR");
+
+ // Test to make sure all log levels >= ERROR are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be ASSERT for this tag.
+ SystemProperties.set(PROPERTY_TAG, "ASSERT");
+
+ // Test to make sure all log levels >= ASSERT are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertTrue(Log.isLoggable(LOG_TAG, Log.ASSERT));
+
+ // Set the log level to be SUPPRESS for this tag.
+ SystemProperties.set(PROPERTY_TAG, "SUPPRESS");
+
+ // Test to make sure all log levels >= ASSERT are loggable.
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.VERBOSE));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.DEBUG));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.INFO));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.WARN));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ERROR));
+ Assert.assertFalse(Log.isLoggable(LOG_TAG, Log.ASSERT));
+ }
+ */
+
+ public static class PerformanceTest extends TestCase implements PerformanceTestCase {
+ private static final int ITERATIONS = 1000;
+
+ @Override
+ public void setUp() {
+ SystemProperties.set(LOG_TAG, "VERBOSE");
+ }
+
+ public boolean isPerformanceOnly() {
+ return true;
+ }
+
+ public int startPerformance(PerformanceTestCase.Intermediates intermediates) {
+ intermediates.setInternalIterations(ITERATIONS * 10);
+ return 0;
+ }
+
+ public void testIsLoggable() {
+ boolean canLog = false;
+ for (int i = ITERATIONS - 1; i >= 0; i--) {
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ canLog = Log.isLoggable(LOG_TAG, Log.VERBOSE);
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
new file mode 100644
index 0000000..5207ad9
--- /dev/null
+++ b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.util.Calendar;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * Unit tests for {@link MonthDisplayHelper}.
+ */
+public class MonthDisplayHelperTest extends TestCase {
+
+
+ @SmallTest
+ public void testFirstDayOfMonth() {
+
+ assertEquals("august 2007",
+ Calendar.WEDNESDAY,
+ new MonthDisplayHelper(2007, Calendar.AUGUST).getFirstDayOfMonth());
+
+ assertEquals("september, 2007",
+ Calendar.SATURDAY,
+ new MonthDisplayHelper(2007, Calendar.SEPTEMBER).getFirstDayOfMonth());
+ }
+
+ @MediumTest
+ public void testNumberOfDaysInCurrentMonth() {
+ assertEquals(30,
+ new MonthDisplayHelper(2007, Calendar.SEPTEMBER).getNumberOfDaysInMonth());
+ }
+
+ @SmallTest
+ public void testMonthRows() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007, Calendar.SEPTEMBER);
+
+ assertArraysEqual(new int[]{26, 27, 28, 29, 30, 31, 1},
+ helper.getDigitsForRow(0));
+ assertArraysEqual(new int[]{2, 3, 4, 5, 6, 7, 8},
+ helper.getDigitsForRow(1));
+ assertArraysEqual(new int[]{30, 1, 2, 3, 4, 5, 6},
+ helper.getDigitsForRow(5));
+
+ }
+
+ @SmallTest
+ public void testMonthRowsWeekStartsMonday() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.SEPTEMBER, Calendar.MONDAY);
+
+ assertArraysEqual(new int[]{27, 28, 29, 30, 31, 1, 2},
+ helper.getDigitsForRow(0));
+ assertArraysEqual(new int[]{3, 4, 5, 6, 7, 8, 9},
+ helper.getDigitsForRow(1));
+ assertArraysEqual(new int[]{24, 25, 26, 27, 28, 29, 30},
+ helper.getDigitsForRow(4));
+ assertArraysEqual(new int[]{1, 2, 3, 4, 5, 6, 7},
+ helper.getDigitsForRow(5));
+ }
+
+ @SmallTest
+ public void testMonthRowsWeekStartsSaturday() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.SEPTEMBER, Calendar.SATURDAY);
+
+ assertArraysEqual(new int[]{1, 2, 3, 4, 5, 6, 7},
+ helper.getDigitsForRow(0));
+ assertArraysEqual(new int[]{8, 9, 10, 11, 12, 13, 14},
+ helper.getDigitsForRow(1));
+ assertArraysEqual(new int[]{29, 30, 1, 2, 3, 4, 5},
+ helper.getDigitsForRow(4));
+
+
+ helper = new MonthDisplayHelper(2007,
+ Calendar.AUGUST, Calendar.SATURDAY);
+
+ assertArraysEqual(new int[]{28, 29, 30, 31, 1, 2, 3},
+ helper.getDigitsForRow(0));
+ assertArraysEqual(new int[]{4, 5, 6, 7, 8, 9, 10},
+ helper.getDigitsForRow(1));
+ assertArraysEqual(new int[]{25, 26, 27, 28, 29, 30, 31},
+ helper.getDigitsForRow(4));
+ }
+
+ @SmallTest
+ public void testGetDayAt() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.SEPTEMBER, Calendar.SUNDAY);
+
+ assertEquals(26, helper.getDayAt(0, 0));
+ assertEquals(1, helper.getDayAt(0, 6));
+ assertEquals(17, helper.getDayAt(3, 1));
+ assertEquals(2, helper.getDayAt(5, 2));
+ }
+
+ @SmallTest
+ public void testPrevMonth() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.SEPTEMBER, Calendar.SUNDAY);
+
+ assertArraysEqual(new int[]{26, 27, 28, 29, 30, 31, 1},
+ helper.getDigitsForRow(0));
+
+ helper.previousMonth();
+
+ assertEquals(Calendar.AUGUST, helper.getMonth());
+ assertArraysEqual(new int[]{29, 30, 31, 1, 2, 3, 4},
+ helper.getDigitsForRow(0));
+ }
+
+ @SmallTest
+ public void testPrevMonthRollOver() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.JANUARY);
+
+ helper.previousMonth();
+
+ assertEquals(2006, helper.getYear());
+ assertEquals(Calendar.DECEMBER, helper.getMonth());
+ }
+
+ @SmallTest
+ public void testNextMonth() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.AUGUST, Calendar.SUNDAY);
+
+ assertArraysEqual(new int[]{29, 30, 31, 1, 2, 3, 4},
+ helper.getDigitsForRow(0));
+
+ helper.nextMonth();
+
+ assertEquals(Calendar.SEPTEMBER, helper.getMonth());
+ assertArraysEqual(new int[]{26, 27, 28, 29, 30, 31, 1},
+ helper.getDigitsForRow(0));
+ }
+
+ @SmallTest
+ public void testGetRowOf() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.AUGUST, Calendar.SUNDAY);
+
+ assertEquals(0, helper.getRowOf(2));
+ assertEquals(0, helper.getRowOf(4));
+ assertEquals(2, helper.getRowOf(12));
+ assertEquals(2, helper.getRowOf(18));
+ assertEquals(3, helper.getRowOf(19));
+ }
+
+ @SmallTest
+ public void testGetColumnOf() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.AUGUST, Calendar.SUNDAY);
+
+ assertEquals(3, helper.getColumnOf(1));
+ assertEquals(4, helper.getColumnOf(9));
+ assertEquals(5, helper.getColumnOf(17));
+ assertEquals(6, helper.getColumnOf(25));
+ assertEquals(0, helper.getColumnOf(26));
+ }
+
+ @SmallTest
+ public void testWithinCurrentMonth() {
+ MonthDisplayHelper helper = new MonthDisplayHelper(2007,
+ Calendar.SEPTEMBER, Calendar.SUNDAY);
+
+ // out of bounds
+ assertFalse(helper.isWithinCurrentMonth(-1, 3));
+ assertFalse(helper.isWithinCurrentMonth(6, 3));
+ assertFalse(helper.isWithinCurrentMonth(2, -1));
+ assertFalse(helper.isWithinCurrentMonth(2, 7));
+
+ // last day of previous month
+ assertFalse(helper.isWithinCurrentMonth(0, 5));
+
+ // first day of next month
+ assertFalse(helper.isWithinCurrentMonth(5, 1));
+
+ // first day in month
+ assertTrue(helper.isWithinCurrentMonth(0, 6));
+
+ // last day in month
+ assertTrue(helper.isWithinCurrentMonth(5, 0));
+ }
+
+ private void assertArraysEqual(int[] expected, int[] actual) {
+ assertEquals("array length", expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("index " + i,
+ expected[i], actual[i]);
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
new file mode 100644
index 0000000..b90c97b
--- /dev/null
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest;
+import android.util.Patterns;
+
+import java.util.regex.Matcher;
+
+import junit.framework.TestCase;
+
+public class PatternsTest extends TestCase {
+
+ @SmallTest
+ public void testTldPattern() throws Exception {
+ boolean t;
+
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("com").matches();
+ assertTrue("Missed valid TLD", t);
+
+ // One of the new top level domain.
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("me").matches();
+ assertTrue("Missed valid TLD", t);
+
+ // One of the new top level test domain.
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("xn--0zwm56d").matches();
+ assertTrue("Missed valid TLD", t);
+
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("mem").matches();
+ assertFalse("Matched invalid TLD!", t);
+
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("xn").matches();
+ assertFalse("Matched invalid TLD!", t);
+
+ t = Patterns.TOP_LEVEL_DOMAIN.matcher("xer").matches();
+ assertFalse("Matched invalid TLD!", t);
+ }
+
+ @SmallTest
+ public void testUrlPattern() throws Exception {
+ boolean t;
+
+ t = Patterns.WEB_URL.matcher("http://www.google.com").matches();
+ assertTrue("Valid URL", t);
+
+ // Google in one of the new top level domain.
+ t = Patterns.WEB_URL.matcher("http://www.google.me").matches();
+ assertTrue("Valid URL", t);
+ t = Patterns.WEB_URL.matcher("google.me").matches();
+ assertTrue("Valid URL", t);
+
+ // Test url in Chinese: http://xn--fsqu00a.xn--0zwm56d
+ t = Patterns.WEB_URL.matcher("http://xn--fsqu00a.xn--0zwm56d").matches();
+ assertTrue("Valid URL", t);
+ t = Patterns.WEB_URL.matcher("xn--fsqu00a.xn--0zwm56d").matches();
+ assertTrue("Valid URL", t);
+
+ // Internationalized URL.
+ t = Patterns.WEB_URL.matcher("http://\uD604\uAE08\uC601\uC218\uC99D.kr").matches();
+ assertTrue("Valid URL", t);
+ t = Patterns.WEB_URL.matcher("\uD604\uAE08\uC601\uC218\uC99D.kr").matches();
+ assertTrue("Valid URL", t);
+
+ t = Patterns.WEB_URL.matcher("http://brainstormtech.blogs.fortune.cnn.com/2010/03/11/" +
+ "top-five-moments-from-eric-schmidt\u2019s-talk-in-abu-dhabi/").matches();
+ assertTrue("Valid URL", t);
+
+ t = Patterns.WEB_URL.matcher("ftp://www.example.com").matches();
+ assertFalse("Matched invalid protocol", t);
+
+ t = Patterns.WEB_URL.matcher("http://www.example.com:8080").matches();
+ assertTrue("Didn't match valid URL with port", t);
+
+ t = Patterns.WEB_URL.matcher("http://www.example.com:8080/?foo=bar").matches();
+ assertTrue("Didn't match valid URL with port and query args", t);
+
+ t = Patterns.WEB_URL.matcher("http://www.example.com:8080/~user/?foo=bar").matches();
+ assertTrue("Didn't match valid URL with ~", t);
+ }
+
+ @SmallTest
+ public void testIpPattern() throws Exception {
+ boolean t;
+
+ t = Patterns.IP_ADDRESS.matcher("172.29.86.3").matches();
+ assertTrue("Valid IP", t);
+
+ t = Patterns.IP_ADDRESS.matcher("1234.4321.9.9").matches();
+ assertFalse("Invalid IP", t);
+ }
+
+ @SmallTest
+ public void testDomainPattern() throws Exception {
+ boolean t;
+
+ t = Patterns.DOMAIN_NAME.matcher("mail.example.com").matches();
+ assertTrue("Valid domain", t);
+
+ t = Patterns.WEB_URL.matcher("google.me").matches();
+ assertTrue("Valid domain", t);
+
+ // Internationalized domains.
+ t = Patterns.DOMAIN_NAME.matcher("\uD604\uAE08\uC601\uC218\uC99D.kr").matches();
+ assertTrue("Valid domain", t);
+
+ t = Patterns.DOMAIN_NAME.matcher("__+&42.xer").matches();
+ assertFalse("Invalid domain", t);
+ }
+
+ @SmallTest
+ public void testPhonePattern() throws Exception {
+ boolean t;
+
+ t = Patterns.PHONE.matcher("(919) 555-1212").matches();
+ assertTrue("Valid phone", t);
+
+ t = Patterns.PHONE.matcher("2334 9323/54321").matches();
+ assertFalse("Invalid phone", t);
+
+ String[] tests = {
+ "Me: 16505551212 this\n",
+ "Me: 6505551212 this\n",
+ "Me: 5551212 this\n",
+
+ "Me: 1-650-555-1212 this\n",
+ "Me: (650) 555-1212 this\n",
+ "Me: +1 (650) 555-1212 this\n",
+ "Me: +1-650-555-1212 this\n",
+ "Me: 650-555-1212 this\n",
+ "Me: 555-1212 this\n",
+
+ "Me: 1.650.555.1212 this\n",
+ "Me: (650) 555.1212 this\n",
+ "Me: +1 (650) 555.1212 this\n",
+ "Me: +1.650.555.1212 this\n",
+ "Me: 650.555.1212 this\n",
+ "Me: 555.1212 this\n",
+
+ "Me: 1 650 555 1212 this\n",
+ "Me: (650) 555 1212 this\n",
+ "Me: +1 (650) 555 1212 this\n",
+ "Me: +1 650 555 1212 this\n",
+ "Me: 650 555 1212 this\n",
+ "Me: 555 1212 this\n",
+ };
+
+ for (String test : tests) {
+ Matcher m = Patterns.PHONE.matcher(test);
+
+ assertTrue("Valid phone " + test, m.find());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/ScrollViewScenario.java b/core/tests/coretests/src/android/util/ScrollViewScenario.java
new file mode 100644
index 0000000..83afe06
--- /dev/null
+++ b/core/tests/coretests/src/android/util/ScrollViewScenario.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import com.google.android.collect.Lists;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Utility base class for creating scroll view scenarios, allowing you to add
+ * a series of different kinds of views arranged vertically, taking up a
+ * specified amount of the screen height.
+ */
+public abstract class ScrollViewScenario extends Activity {
+
+ /**
+ * Holds content of scroll view
+ */
+ private LinearLayout mLinearLayout;
+
+ /**
+ * The actual scroll view
+ */
+ private ScrollView mScrollView;
+
+
+ /**
+ * What we need of each view that the user wants: the view, and the ratio
+ * to the screen height for its desired height.
+ */
+ private interface ViewFactory {
+ View create(final Context context);
+
+ float getHeightRatio();
+ }
+
+ /**
+ * Partially implement ViewFactory given a height ratio.
+ */
+ private static abstract class ViewFactoryBase implements ViewFactory {
+
+ private float mHeightRatio;
+
+ @SuppressWarnings({"UnusedDeclaration"})
+ private ViewFactoryBase() {throw new UnsupportedOperationException("don't call this!");}
+
+ protected ViewFactoryBase(float heightRatio) {
+ mHeightRatio = heightRatio;
+ }
+
+ public float getHeightRatio() {
+ return mHeightRatio;
+ }
+ }
+
+ /**
+ * Builder for selecting the views to be vertically arranged in the scroll
+ * view.
+ */
+ @SuppressWarnings({"JavaDoc"})
+ public static class Params {
+
+ List<ViewFactory> mViewFactories = Lists.newArrayList();
+
+ /**
+ * Add a text view.
+ * @param text The text of the text view.
+ * @param heightRatio The view's height will be this * the screen height.
+ */
+ public Params addTextView(final String text, float heightRatio) {
+ mViewFactories.add(new ViewFactoryBase(heightRatio) {
+ public View create(final Context context) {
+ final TextView tv = new TextView(context);
+ tv.setText(text);
+ return tv;
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Add multiple text views.
+ * @param numViews the number of views to add.
+ * @param textPrefix The text to prepend to each text view.
+ * @param heightRatio The view's height will be this * the screen height.
+ */
+ public Params addTextViews(int numViews, String textPrefix, float heightRatio) {
+ for (int i = 0; i < numViews; i++) {
+ addTextView(textPrefix + i, heightRatio);
+ }
+ return this;
+ }
+
+ /**
+ * Add a button.
+ * @param text The text of the button.
+ * @param heightRatio The view's height will be this * the screen height.
+ */
+ public Params addButton(final String text, float heightRatio) {
+ mViewFactories.add(new ViewFactoryBase(heightRatio) {
+ public View create(final Context context) {
+ final Button button = new Button(context);
+ button.setText(text);
+ return button;
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Add multiple buttons.
+ * @param numButtons the number of views to add.
+ * @param textPrefix The text to prepend to each button.
+ * @param heightRatio The view's height will be this * the screen height.
+ */
+ public Params addButtons(int numButtons, String textPrefix, float heightRatio) {
+ for (int i = 0; i < numButtons; i++) {
+ addButton(textPrefix + i, heightRatio);
+ }
+ return this;
+ }
+
+ /**
+ * Add an {@link InternalSelectionView}.
+ * @param numRows The number of rows in the internal selection view.
+ * @param heightRatio The view's height will be this * the screen height.
+ */
+ public Params addInternalSelectionView(final int numRows, float heightRatio) {
+ mViewFactories.add(new ViewFactoryBase(heightRatio) {
+ public View create(final Context context) {
+ return new InternalSelectionView(context, numRows, "isv");
+ }
+ });
+ return this;
+ }
+
+ /**
+ * Add a sublayout of buttons as a single child of the scroll view.
+ * @param numButtons The number of buttons in the sub layout
+ * @param heightRatio The layout's height will be this * the screen height.
+ */
+ public Params addVerticalLLOfButtons(final String prefix, final int numButtons, float heightRatio) {
+ mViewFactories.add(new ViewFactoryBase(heightRatio) {
+
+ public View create(Context context) {
+ final LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.VERTICAL);
+
+ // fill width, equally weighted on height
+ final LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f);
+ for (int i = 0; i < numButtons; i++) {
+ final Button button = new Button(context);
+ button.setText(prefix + i);
+ ll.addView(button, lp);
+ }
+
+ return ll;
+ }
+ });
+ return this;
+ }
+ }
+
+ /**
+ * Override this and initialized the views in the scroll view.
+ * @param params Used to configure the contents of the scroll view.
+ */
+ protected abstract void init(Params params);
+
+ public LinearLayout getLinearLayout() {
+ return mLinearLayout;
+ }
+
+ public ScrollView getScrollView() {
+ return mScrollView;
+ }
+
+ /**
+ * Get the child contained within the vertical linear layout of the
+ * scroll view.
+ * @param index The index within the linear layout.
+ * @return the child within the vertical linear layout of the scroll view
+ * at the specified index.
+ */
+ @SuppressWarnings({"unchecked"})
+ public <T extends View> T getContentChildAt(int index) {
+ return (T) mLinearLayout.getChildAt(index);
+ }
+
+ /**
+ * Hook for changing how scroll view's are created.
+ */
+ @SuppressWarnings({"JavaDoc"})
+ protected ScrollView createScrollView() {
+ return new ScrollView(this);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // for test stability, turn off title bar
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ int screenHeight = getWindowManager().getDefaultDisplay().getHeight()
+ - 25;
+ mLinearLayout = new LinearLayout(this);
+ mLinearLayout.setOrientation(LinearLayout.VERTICAL);
+
+ // initialize params
+ final Params params = new Params();
+ init(params);
+
+ // create views specified by params
+ for (ViewFactory viewFactory : params.mViewFactories) {
+ final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ (int) (viewFactory.getHeightRatio() * screenHeight));
+ mLinearLayout.addView(viewFactory.create(this), lp);
+ }
+
+ mScrollView = createScrollView();
+ mScrollView.addView(mLinearLayout, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // no animation to speed up tests
+ mScrollView.setSmoothScrollingEnabled(false);
+
+ setContentView(mScrollView);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
new file mode 100644
index 0000000..e481ce04
--- /dev/null
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for {@link StateSet}
+ */
+
+public class StateSetTest extends TestCase {
+
+ @SmallTest
+ public void testStateSetPositiveMatches() throws Exception {
+ int[] stateSpec = new int[2];
+ int[] stateSet = new int[3];
+ // Single states in both sets - match
+ stateSpec[0] = 1;
+ stateSet[0] = 1;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Single states in both sets - non-match
+ stateSet[0] = 2;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add another state to the spec which the stateSet doesn't match
+ stateSpec[1] = 2;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add the missing matching element to the stateSet
+ stateSet[1] = 1;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add an irrelevent state to the stateSpec
+ stateSet[2] = 12345;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ }
+
+ @SmallTest
+ public void testStatesSetMatchMixEmUp() throws Exception {
+ int[] stateSpec = new int[2];
+ int[] stateSet = new int[2];
+ // One element in stateSpec which we must match and one which we must
+ // not match. stateSet only contains the match.
+ stateSpec[0] = 1;
+ stateSpec[1] = -2;
+ stateSet[0] = 1;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ // stateSet now contains just the element we must not match
+ stateSet[0] = 2;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add another matching state to the the stateSet. We still fail
+ // because stateSet contains a must-not-match element
+ stateSet[1] = 1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Switch the must-not-match element in stateSet with a don't care
+ stateSet[0] = 12345;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ }
+
+ @SmallTest
+ public void testStateSetNegativeMatches() throws Exception {
+ int[] stateSpec = new int[2];
+ int[] stateSet = new int[3];
+ // Single states in both sets - match
+ stateSpec[0] = -1;
+ stateSet[0] = 2;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add another arrelevent state to the stateSet
+ stateSet[1] = 12345;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Single states in both sets - non-match
+ stateSet[0] = 1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add another state to the spec which the stateSet doesn't match
+ stateSpec[1] = -2;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // Add an irrelevent state to the stateSet
+ stateSet[2] = 12345;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ }
+
+ @SmallTest
+ public void testEmptySetMatchesNegtives() throws Exception {
+ int[] stateSpec = {-12345, -6789};
+ int[] stateSet = new int[0];
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ int[] stateSet2 = {0};
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet2));
+ }
+
+ @SmallTest
+ public void testEmptySetFailsPositives() throws Exception {
+ int[] stateSpec = {12345};
+ int[] stateSet = new int[0];
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ int[] stateSet2 = {0};
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet2));
+ }
+
+ @SmallTest
+ public void testEmptySetMatchesWildcard() throws Exception {
+ int[] stateSpec = StateSet.WILD_CARD;
+ int[] stateSet = new int[0];
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ int[] stateSet2 = {0};
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet2));
+ }
+
+ @SmallTest
+ public void testSingleStatePositiveMatches() throws Exception {
+ int[] stateSpec = new int[2];
+ int state;
+ // match
+ stateSpec[0] = 1;
+ state = 1;
+ assertTrue(StateSet.stateSetMatches(stateSpec, state));
+ // non-match
+ state = 2;
+ assertFalse(StateSet.stateSetMatches(stateSpec, state));
+ // add irrelevant must-not-match
+ stateSpec[1] = -12345;
+ assertFalse(StateSet.stateSetMatches(stateSpec, state));
+ }
+
+ @SmallTest
+ public void testSingleStateNegativeMatches() throws Exception {
+ int[] stateSpec = new int[2];
+ int state;
+ // match
+ stateSpec[0] = -1;
+ state = 1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, state));
+ // non-match
+ state = 2;
+ assertTrue(StateSet.stateSetMatches(stateSpec, state));
+ // add irrelevant must-not-match
+ stateSpec[1] = -12345;
+ assertTrue(StateSet.stateSetMatches(stateSpec, state));
+ }
+
+ @SmallTest
+ public void testZeroStateOnlyMatchesDefault() throws Exception {
+ int[] stateSpec = new int[3];
+ int state = 0;
+ // non-match
+ stateSpec[0] = 1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, state));
+ // non-match
+ stateSpec[1] = -1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, state));
+ // match
+ stateSpec = StateSet.WILD_CARD;
+ assertTrue(StateSet.stateSetMatches(stateSpec, state));
+ }
+
+ @SmallTest
+ public void testNullStateOnlyMatchesDefault() throws Exception {
+ int[] stateSpec = new int[3];
+ int[] stateSet = null;
+ // non-match
+ stateSpec[0] = 1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // non-match
+ stateSpec[1] = -1;
+ assertFalse(StateSet.stateSetMatches(stateSpec, stateSet));
+ // match
+ stateSpec = StateSet.WILD_CARD;
+ assertTrue(StateSet.stateSetMatches(stateSpec, stateSet));
+ }
+}
diff --git a/core/tests/coretests/src/android/util/TimeUtilsTest.java b/core/tests/coretests/src/android/util/TimeUtilsTest.java
new file mode 100644
index 0000000..65a6078
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TimeUtilsTest.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 junit.framework.TestCase;
+
+import android.util.TimeUtils;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * TimeUtilsTest tests the time zone guesser.
+ */
+public class TimeUtilsTest extends TestCase {
+ public void testMainstream() throws Exception {
+ String[] mainstream = new String[] {
+ "America/New_York", // Eastern
+ "America/Chicago", // Central
+ "America/Denver", // Mountain
+ "America/Los_Angeles", // Pacific
+ "America/Anchorage", // Alaska
+ "Pacific/Honolulu", // Hawaii, no DST
+ };
+
+ for (String name : mainstream) {
+ TimeZone tz = TimeZone.getTimeZone(name);
+ Calendar c = Calendar.getInstance(tz);
+ TimeZone guess;
+
+ c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00);
+ guess = guess(c, "us");
+ assertEquals(name, guess.getID());
+
+ c.set(2009, Calendar.JANUARY, 20, 12, 00, 00);
+ guess = guess(c, "us");
+ assertEquals(name, guess.getID());
+ }
+ }
+
+ public void testWeird() throws Exception {
+ String[] weird = new String[] {
+ "America/Phoenix", // Mountain, no DST
+ "America/Adak", // Same as Hawaii, but with DST
+ };
+
+ for (String name : weird) {
+ TimeZone tz = TimeZone.getTimeZone(name);
+ Calendar c = Calendar.getInstance(tz);
+ TimeZone guess;
+
+ c.set(2008, Calendar.OCTOBER, 20, 12, 00, 00);
+ guess = guess(c, "us");
+ assertEquals(name, guess.getID());
+ }
+ }
+
+ public void testOld() throws Exception {
+ String[] old = new String[] {
+ "America/Indiana/Indianapolis", // Eastern, formerly no DST
+ };
+
+ for (String name : old) {
+ TimeZone tz = TimeZone.getTimeZone(name);
+ Calendar c = Calendar.getInstance(tz);
+ TimeZone guess;
+
+ c.set(2005, Calendar.OCTOBER, 20, 12, 00, 00);
+ guess = guess(c, "us");
+ assertEquals(name, guess.getID());
+ }
+ }
+
+ public void testWorld() throws Exception {
+ String[] world = new String[] {
+ "ad", "Europe/Andorra",
+ "ae", "Asia/Dubai",
+ "af", "Asia/Kabul",
+ "ag", "America/Antigua",
+ "ai", "America/Anguilla",
+ "al", "Europe/Tirane",
+ "am", "Asia/Yerevan",
+ "an", "America/Curacao",
+ "ao", "Africa/Luanda",
+ "aq", "Antarctica/McMurdo",
+ "aq", "Antarctica/DumontDUrville",
+ "aq", "Antarctica/Casey",
+ "aq", "Antarctica/Davis",
+ "aq", "Antarctica/Mawson",
+ "aq", "Antarctica/Syowa",
+ "aq", "Antarctica/Rothera",
+ "aq", "Antarctica/Palmer",
+ "ar", "America/Argentina/Buenos_Aires",
+ "as", "Pacific/Pago_Pago",
+ "at", "Europe/Vienna",
+ "au", "Australia/Sydney",
+ "au", "Australia/Adelaide",
+ "au", "Australia/Perth",
+ "au", "Australia/Eucla",
+ "aw", "America/Aruba",
+ "ax", "Europe/Mariehamn",
+ "az", "Asia/Baku",
+ "ba", "Europe/Sarajevo",
+ "bb", "America/Barbados",
+ "bd", "Asia/Dhaka",
+ "be", "Europe/Brussels",
+ "bf", "Africa/Ouagadougou",
+ "bg", "Europe/Sofia",
+ "bh", "Asia/Bahrain",
+ "bi", "Africa/Bujumbura",
+ "bj", "Africa/Porto-Novo",
+ "bm", "Atlantic/Bermuda",
+ "bn", "Asia/Brunei",
+ "bo", "America/La_Paz",
+ "br", "America/Noronha",
+ "br", "America/Sao_Paulo",
+ "br", "America/Manaus",
+ "bs", "America/Nassau",
+ "bt", "Asia/Thimphu",
+ "bw", "Africa/Gaborone",
+ "by", "Europe/Minsk",
+ "bz", "America/Belize",
+ "ca", "America/St_Johns",
+ "ca", "America/Halifax",
+ "ca", "America/Toronto",
+ "ca", "America/Winnipeg",
+ "ca", "America/Edmonton",
+ "ca", "America/Vancouver",
+ "cc", "Indian/Cocos",
+ "cd", "Africa/Lubumbashi",
+ "cd", "Africa/Kinshasa",
+ "cf", "Africa/Bangui",
+ "cg", "Africa/Brazzaville",
+ "ch", "Europe/Zurich",
+ "ci", "Africa/Abidjan",
+ "ck", "Pacific/Rarotonga",
+ "cl", "America/Santiago",
+ "cl", "Pacific/Easter",
+ "cm", "Africa/Douala",
+ "cn", "Asia/Shanghai",
+ "co", "America/Bogota",
+ "cr", "America/Costa_Rica",
+ "cu", "America/Havana",
+ "cv", "Atlantic/Cape_Verde",
+ "cx", "Indian/Christmas",
+ "cy", "Asia/Nicosia",
+ "cz", "Europe/Prague",
+ "de", "Europe/Berlin",
+ "dj", "Africa/Djibouti",
+ "dk", "Europe/Copenhagen",
+ "dm", "America/Dominica",
+ "do", "America/Santo_Domingo",
+ "dz", "Africa/Algiers",
+ "ec", "America/Guayaquil",
+ "ec", "Pacific/Galapagos",
+ "ee", "Europe/Tallinn",
+ "eg", "Africa/Cairo",
+ "eh", "Africa/El_Aaiun",
+ "er", "Africa/Asmara",
+ "es", "Europe/Madrid",
+ "es", "Atlantic/Canary",
+ "et", "Africa/Addis_Ababa",
+ "fi", "Europe/Helsinki",
+ "fj", "Pacific/Fiji",
+ "fk", "Atlantic/Stanley",
+ "fm", "Pacific/Ponape",
+ "fm", "Pacific/Truk",
+ "fo", "Atlantic/Faroe",
+ "fr", "Europe/Paris",
+ "ga", "Africa/Libreville",
+ "gb", "Europe/London",
+ "gd", "America/Grenada",
+ "ge", "Asia/Tbilisi",
+ "gf", "America/Cayenne",
+ "gg", "Europe/Guernsey",
+ "gh", "Africa/Accra",
+ "gi", "Europe/Gibraltar",
+ "gl", "America/Danmarkshavn",
+ "gl", "America/Scoresbysund",
+ "gl", "America/Godthab",
+ "gl", "America/Thule",
+ "gm", "Africa/Banjul",
+ "gn", "Africa/Conakry",
+ "gp", "America/Guadeloupe",
+ "gq", "Africa/Malabo",
+ "gr", "Europe/Athens",
+ "gs", "Atlantic/South_Georgia",
+ "gt", "America/Guatemala",
+ "gu", "Pacific/Guam",
+ "gw", "Africa/Bissau",
+ "gy", "America/Guyana",
+ "hk", "Asia/Hong_Kong",
+ "hn", "America/Tegucigalpa",
+ "hr", "Europe/Zagreb",
+ "ht", "America/Port-au-Prince",
+ "hu", "Europe/Budapest",
+ "id", "Asia/Jayapura",
+ "id", "Asia/Makassar",
+ "id", "Asia/Jakarta",
+ "ie", "Europe/Dublin",
+ "il", "Asia/Jerusalem",
+ "im", "Europe/Isle_of_Man",
+ "in", "Asia/Calcutta",
+ "io", "Indian/Chagos",
+ "iq", "Asia/Baghdad",
+ "ir", "Asia/Tehran",
+ "is", "Atlantic/Reykjavik",
+ "it", "Europe/Rome",
+ "je", "Europe/Jersey",
+ "jm", "America/Jamaica",
+ "jo", "Asia/Amman",
+ "jp", "Asia/Tokyo",
+ "ke", "Africa/Nairobi",
+ "kg", "Asia/Bishkek",
+ "kh", "Asia/Phnom_Penh",
+ "ki", "Pacific/Kiritimati",
+ "ki", "Pacific/Enderbury",
+ "ki", "Pacific/Tarawa",
+ "km", "Indian/Comoro",
+ "kn", "America/St_Kitts",
+ "kp", "Asia/Pyongyang",
+ "kr", "Asia/Seoul",
+ "kw", "Asia/Kuwait",
+ "ky", "America/Cayman",
+ "kz", "Asia/Almaty",
+ "kz", "Asia/Aqtau",
+ "la", "Asia/Vientiane",
+ "lb", "Asia/Beirut",
+ "lc", "America/St_Lucia",
+ "li", "Europe/Vaduz",
+ "lk", "Asia/Colombo",
+ "lr", "Africa/Monrovia",
+ "ls", "Africa/Maseru",
+ "lt", "Europe/Vilnius",
+ "lu", "Europe/Luxembourg",
+ "lv", "Europe/Riga",
+ "ly", "Africa/Tripoli",
+ "ma", "Africa/Casablanca",
+ "mc", "Europe/Monaco",
+ "md", "Europe/Chisinau",
+ "me", "Europe/Podgorica",
+ "mg", "Indian/Antananarivo",
+ "mh", "Pacific/Majuro",
+ "mk", "Europe/Skopje",
+ "ml", "Africa/Bamako",
+ "mm", "Asia/Rangoon",
+ "mn", "Asia/Choibalsan",
+ "mn", "Asia/Hovd",
+ "mo", "Asia/Macau",
+ "mp", "Pacific/Saipan",
+ "mq", "America/Martinique",
+ "mr", "Africa/Nouakchott",
+ "ms", "America/Montserrat",
+ "mt", "Europe/Malta",
+ "mu", "Indian/Mauritius",
+ "mv", "Indian/Maldives",
+ "mw", "Africa/Blantyre",
+ "mx", "America/Mexico_City",
+ "mx", "America/Chihuahua",
+ "mx", "America/Tijuana",
+ "my", "Asia/Kuala_Lumpur",
+ "mz", "Africa/Maputo",
+ "na", "Africa/Windhoek",
+ "nc", "Pacific/Noumea",
+ "ne", "Africa/Niamey",
+ "nf", "Pacific/Norfolk",
+ "ng", "Africa/Lagos",
+ "ni", "America/Managua",
+ "nl", "Europe/Amsterdam",
+ "no", "Europe/Oslo",
+ "np", "Asia/Katmandu",
+ "nr", "Pacific/Nauru",
+ "nu", "Pacific/Niue",
+ "nz", "Pacific/Auckland",
+ "nz", "Pacific/Chatham",
+ "om", "Asia/Muscat",
+ "pa", "America/Panama",
+ "pe", "America/Lima",
+ "pf", "Pacific/Gambier",
+ "pf", "Pacific/Marquesas",
+ "pf", "Pacific/Tahiti",
+ "pg", "Pacific/Port_Moresby",
+ "ph", "Asia/Manila",
+ "pk", "Asia/Karachi",
+ "pl", "Europe/Warsaw",
+ "pm", "America/Miquelon",
+ "pn", "Pacific/Pitcairn",
+ "pr", "America/Puerto_Rico",
+ "ps", "Asia/Gaza",
+ "pt", "Europe/Lisbon",
+ "pt", "Atlantic/Azores",
+ "pw", "Pacific/Palau",
+ "py", "America/Asuncion",
+ "qa", "Asia/Qatar",
+ "re", "Indian/Reunion",
+ "ro", "Europe/Bucharest",
+ "rs", "Europe/Belgrade",
+ "ru", "Asia/Kamchatka",
+ "ru", "Asia/Magadan",
+ "ru", "Asia/Vladivostok",
+ "ru", "Asia/Yakutsk",
+ "ru", "Asia/Irkutsk",
+ "ru", "Asia/Krasnoyarsk",
+ "ru", "Asia/Novosibirsk",
+ "ru", "Asia/Yekaterinburg",
+ "ru", "Europe/Samara",
+ "ru", "Europe/Moscow",
+ "ru", "Europe/Kaliningrad",
+ "rw", "Africa/Kigali",
+ "sa", "Asia/Riyadh",
+ "sb", "Pacific/Guadalcanal",
+ "sc", "Indian/Mahe",
+ "sd", "Africa/Khartoum",
+ "se", "Europe/Stockholm",
+ "sg", "Asia/Singapore",
+ "sh", "Atlantic/St_Helena",
+ "si", "Europe/Ljubljana",
+ "sj", "Arctic/Longyearbyen",
+ "sk", "Europe/Bratislava",
+ "sl", "Africa/Freetown",
+ "sm", "Europe/San_Marino",
+ "sn", "Africa/Dakar",
+ "so", "Africa/Mogadishu",
+ "sr", "America/Paramaribo",
+ "st", "Africa/Sao_Tome",
+ "sv", "America/El_Salvador",
+ "sy", "Asia/Damascus",
+ "sz", "Africa/Mbabane",
+ "tc", "America/Grand_Turk",
+ "td", "Africa/Ndjamena",
+ "tf", "Indian/Kerguelen",
+ "tg", "Africa/Lome",
+ "th", "Asia/Bangkok",
+ "tj", "Asia/Dushanbe",
+ "tk", "Pacific/Fakaofo",
+ "tl", "Asia/Dili",
+ "tm", "Asia/Ashgabat",
+ "tn", "Africa/Tunis",
+ "to", "Pacific/Tongatapu",
+ "tr", "Europe/Istanbul",
+ "tt", "America/Port_of_Spain",
+ "tv", "Pacific/Funafuti",
+ "tw", "Asia/Taipei",
+ "tz", "Africa/Dar_es_Salaam",
+ "ua", "Europe/Kiev",
+ "ug", "Africa/Kampala",
+ "um", "Pacific/Wake",
+ "um", "Pacific/Johnston",
+ "um", "Pacific/Midway",
+ "us", "America/New_York",
+ "us", "America/Chicago",
+ "us", "America/Denver",
+ "us", "America/Los_Angeles",
+ "us", "America/Anchorage",
+ "us", "Pacific/Honolulu",
+ "uy", "America/Montevideo",
+ "uz", "Asia/Tashkent",
+ "va", "Europe/Vatican",
+ "vc", "America/St_Vincent",
+ "ve", "America/Caracas",
+ "vg", "America/Tortola",
+ "vi", "America/St_Thomas",
+ "vn", "Asia/Saigon",
+ "vu", "Pacific/Efate",
+ "wf", "Pacific/Wallis",
+ "ws", "Pacific/Apia",
+ "ye", "Asia/Aden",
+ "yt", "Indian/Mayotte",
+ "za", "Africa/Johannesburg",
+ "zm", "Africa/Lusaka",
+ "zw", "Africa/Harare",
+ };
+
+ for (int i = 0; i < world.length; i += 2) {
+ String country = world[i];
+ String name = world[i + 1];
+
+ TimeZone tz = TimeZone.getTimeZone(name);
+ Calendar c = Calendar.getInstance(tz);
+ TimeZone guess;
+
+ c.set(2009, Calendar.JULY, 20, 12, 00, 00);
+ guess = guess(c, country);
+ assertEquals(name, guess.getID());
+
+ c.set(2009, Calendar.JANUARY, 20, 12, 00, 00);
+ guess = guess(c, country);
+ assertEquals(name, guess.getID());
+ }
+ }
+
+ public void testWorldWeird() throws Exception {
+ String[] world = new String[] {
+ // Distinguisable from Sydney only when DST not in effect
+ "au", "Australia/Lord_Howe",
+ };
+
+ for (int i = 0; i < world.length; i += 2) {
+ String country = world[i];
+ String name = world[i + 1];
+
+ TimeZone tz = TimeZone.getTimeZone(name);
+ Calendar c = Calendar.getInstance(tz);
+ TimeZone guess;
+
+ c.set(2009, Calendar.JULY, 20, 12, 00, 00);
+ guess = guess(c, country);
+ assertEquals(name, guess.getID());
+ }
+ }
+
+ private static TimeZone guess(Calendar c, String country) {
+ return TimeUtils.getTimeZone(c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET),
+ c.get(c.DST_OFFSET) != 0,
+ c.getTimeInMillis(),
+ country);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/TouchModeFlexibleAsserts.java b/core/tests/coretests/src/android/util/TouchModeFlexibleAsserts.java
new file mode 100644
index 0000000..ca12a15
--- /dev/null
+++ b/core/tests/coretests/src/android/util/TouchModeFlexibleAsserts.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import junit.framework.Assert;
+
+import android.test.InstrumentationTestCase;
+import android.test.TouchUtils;
+import android.view.View;
+
+/**
+ * When entering touch mode via touch, the tests can be flaky. These asserts
+ * are more flexible (allowing up to MAX_ATTEMPTS touches to enter touch mode via touch or
+ * tap) until we can find a way to solve the flakiness.
+ */
+public class TouchModeFlexibleAsserts {
+
+ private static int MAX_ATTEMPTS = 2;
+
+ private static int MAX_DELAY_MILLIS = 2000;
+
+ public static void assertInTouchModeAfterClick(InstrumentationTestCase test, View viewToTouch) {
+ int numAttemptsAtTouchMode = 0;
+ while (numAttemptsAtTouchMode < MAX_ATTEMPTS &&
+ !viewToTouch.isInTouchMode()) {
+ TouchUtils.clickView(test, viewToTouch);
+ numAttemptsAtTouchMode++;
+ }
+ Assert.assertTrue("even after " + MAX_ATTEMPTS + " clicks, did not enter "
+ + "touch mode", viewToTouch.isInTouchMode());
+ //Assert.assertEquals("number of touches to enter touch mode", 1, numAttemptsAtTouchMode);
+ }
+
+ public static void assertInTouchModeAfterTap(InstrumentationTestCase test, View viewToTouch) {
+ int numAttemptsAtTouchMode = 0;
+ while (numAttemptsAtTouchMode < MAX_ATTEMPTS &&
+ !viewToTouch.isInTouchMode()) {
+ TouchUtils.tapView(test, viewToTouch);
+ numAttemptsAtTouchMode++;
+ }
+ Assert.assertTrue("even after " + MAX_ATTEMPTS + " taps, did not enter "
+ + "touch mode", viewToTouch.isInTouchMode());
+ //Assert.assertEquals("number of touches to enter touch mode", 1, numAttemptsAtTouchMode);
+ }
+
+ public static void assertNotInTouchModeAfterKey(InstrumentationTestCase test, int keyCode, View checkForTouchMode) {
+ test.sendKeys(keyCode);
+ int amountLeft = MAX_DELAY_MILLIS;
+
+ while (checkForTouchMode.isInTouchMode() && amountLeft > 0) {
+ amountLeft -= 200;
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ Assert.assertFalse("even after waiting " + MAX_DELAY_MILLIS + " millis after "
+ + "pressing key event, still in touch mode", checkForTouchMode.isInTouchMode());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/BigCache.java b/core/tests/coretests/src/android/view/BigCache.java
new file mode 100644
index 0000000..2182176
--- /dev/null
+++ b/core/tests/coretests/src/android/view/BigCache.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.Display;
+import android.view.ViewConfiguration;
+
+/**
+ * This activity contains two Views, one as big as the screen, one much larger. The large one
+ * should not be able to activate its drawing cache.
+ */
+public class BigCache extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final LinearLayout testBed = new LinearLayout(this);
+ testBed.setOrientation(LinearLayout.VERTICAL);
+ testBed.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ final int cacheSize = ViewConfiguration.getMaximumDrawingCacheSize();
+ final Display display = getWindowManager().getDefaultDisplay();
+ final int screenWidth = display.getWidth();
+ final int screenHeight = display.getHeight();
+
+ final View tiny = new View(this);
+ tiny.setId(R.id.a);
+ tiny.setBackgroundColor(0xFFFF0000);
+ tiny.setLayoutParams(new LinearLayout.LayoutParams(screenWidth, screenHeight));
+
+ final View large = new View(this);
+ large.setId(R.id.b);
+ large.setBackgroundColor(0xFF00FF00);
+ // Compute the height of the view assuming a cache size based on ARGB8888
+ final int height = 2 * (cacheSize / 2) / screenWidth;
+ large.setLayoutParams(new LinearLayout.LayoutParams(screenWidth, height));
+
+ final ScrollView scroller = new ScrollView(this);
+ scroller.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ testBed.addView(tiny);
+ testBed.addView(large);
+ scroller.addView(testBed);
+
+ setContentView(scroller);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/BigCacheTest.java b/core/tests/coretests/src/android/view/BigCacheTest.java
new file mode 100644
index 0000000..8c2c865
--- /dev/null
+++ b/core/tests/coretests/src/android/view/BigCacheTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.BigCache;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.graphics.Bitmap;
+
+/**
+ * Builds the drawing cache of two Views, one smaller than the maximum cache size,
+ * one larger than the maximum cache size. The latter should always have a null
+ * drawing cache.
+ */
+public class BigCacheTest extends ActivityInstrumentationTestCase<BigCache> {
+ private View mTiny;
+ private View mLarge;
+
+ public BigCacheTest() {
+ super("com.android.frameworks.coretests", BigCache.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final BigCache activity = getActivity();
+ mTiny = activity.findViewById(R.id.a);
+ mLarge = activity.findViewById(R.id.b);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mTiny);
+ assertNotNull(mLarge);
+ }
+
+ @MediumTest
+ public void testDrawingCacheBelowMaximumSize() throws Exception {
+ final int max = ViewConfiguration.get(getActivity()).getScaledMaximumDrawingCacheSize();
+ assertTrue(mTiny.getWidth() * mTiny.getHeight() * 2 < max);
+ assertNotNull(createCacheForView(mTiny));
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @MediumTest
+ public void testDrawingCacheAboveMaximumSize() throws Exception {
+ final int max = ViewConfiguration.get(getActivity()).getScaledMaximumDrawingCacheSize();
+ assertTrue(mLarge.getWidth() * mLarge.getHeight() * 2 > max);
+ assertNull(createCacheForView(mLarge));
+ }
+
+ private Bitmap createCacheForView(final View view) {
+ final Bitmap[] cache = new Bitmap[1];
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ view.setDrawingCacheEnabled(true);
+ view.invalidate();
+ view.buildDrawingCache();
+ cache[0] = view.getDrawingCache();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ return cache[0];
+ }
+}
diff --git a/core/tests/coretests/src/android/view/BitmapDrawable.java b/core/tests/coretests/src/android/view/BitmapDrawable.java
new file mode 100644
index 0000000..f7bad84
--- /dev/null
+++ b/core/tests/coretests/src/android/view/BitmapDrawable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+public class BitmapDrawable extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.with_bitmap_background);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/CreateViewTest.java b/core/tests/coretests/src/android/view/CreateViewTest.java
new file mode 100644
index 0000000..16656f6
--- /dev/null
+++ b/core/tests/coretests/src/android/view/CreateViewTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class CreateViewTest extends AndroidTestCase implements PerformanceTestCase {
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ public int startPerformance(PerformanceTestCase.Intermediates intermediates) {
+ return 0;
+ }
+
+ @SmallTest
+ public void testLayout1() throws Exception {
+ new CreateViewTest.ViewOne(mContext);
+ }
+
+ @SmallTest
+ public void testLayout2() throws Exception {
+ LinearLayout vert = new LinearLayout(mContext);
+ vert.addView(new CreateViewTest.ViewOne(mContext),
+ new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+ }
+
+ @SmallTest
+ public void testLayout3() throws Exception {
+ LinearLayout vert = new LinearLayout(mContext);
+
+ ViewOne one = new ViewOne(mContext);
+ vert.addView(one, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+
+ ViewOne two = new ViewOne(mContext);
+ vert.addView(two, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+
+ ViewOne three = new ViewOne(mContext);
+ vert.addView(three, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+
+ ViewOne four = new ViewOne(mContext);
+ vert.addView(four, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+
+ ViewOne five = new ViewOne(mContext);
+ vert.addView(five, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+
+ ViewOne six = new ViewOne(mContext);
+ vert.addView(six, new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT, 0));
+ }
+
+ @SmallTest
+ public void testLayout4() throws Exception {
+ TextView text = new TextView(mContext);
+ text.setText("S");
+ }
+
+ @SmallTest
+ public void testLayout5() throws Exception {
+ TextView text = new TextView(mContext);
+ text.setText("S");
+
+ LinearLayout vert = new LinearLayout(mContext);
+ vert.addView(text, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+ }
+
+ @SmallTest
+ public void testLayout6() throws Exception {
+ LinearLayout vert = new LinearLayout(mContext);
+
+ TextView one = new TextView(mContext);
+ one.setText("S");
+ vert.addView(one, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+
+ TextView two = new TextView(mContext);
+ two.setText("M");
+ vert.addView(two, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+
+ TextView three = new TextView(mContext);
+ three.setText("T");
+ vert.addView(three, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+
+ TextView four = new TextView(mContext);
+ four.setText("W");
+ vert.addView(four, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+
+ TextView five = new TextView(mContext);
+ five.setText("H");
+ vert.addView(five, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+
+ TextView six = new TextView(mContext);
+ six.setText("F");
+ vert.addView(six, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT, 0));
+ }
+
+ public static class ViewOne extends View {
+ public ViewOne(Context context) {
+ super(context);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/Disabled.java b/core/tests/coretests/src/android/view/Disabled.java
new file mode 100644
index 0000000..fa92107
--- /dev/null
+++ b/core/tests/coretests/src/android/view/Disabled.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.app.Activity;
+
+/**
+ * Exercise View's disabled state.
+ */
+public class Disabled extends Activity implements OnClickListener {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.disabled);
+
+ // Find our buttons
+ Button disabledButton = (Button) findViewById(R.id.disabledButton);
+ disabledButton.setEnabled(false);
+
+ // Find our buttons
+ Button disabledButtonA = (Button) findViewById(R.id.disabledButtonA);
+ disabledButtonA.setOnClickListener(this);
+ }
+
+ public void onClick(View v) {
+ Button disabledButtonB = (Button) findViewById(R.id.disabledButtonB);
+ disabledButtonB.setEnabled(!disabledButtonB.isEnabled());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/DisabledLongpressTest.java b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
new file mode 100644
index 0000000..3123897
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.Longpress;
+import com.android.frameworks.coretests.R;
+import android.util.KeyUtils;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+
+/**
+ * Exercises {@link android.view.View}'s longpress plumbing by testing the
+ * disabled case.
+ */
+public class DisabledLongpressTest extends ActivityInstrumentationTestCase<Longpress> {
+ private View mSimpleView;
+ private boolean mLongClicked;
+
+ public DisabledLongpressTest() {
+ super("com.android.frameworks.coretests", Longpress.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final Longpress a = getActivity();
+ mSimpleView = a.findViewById(R.id.simple_view);
+ mSimpleView.setOnLongClickListener(new OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ mLongClicked = true;
+ return true;
+ }
+ });
+ // The View#setOnLongClickListener will ensure the View is long
+ // clickable, we reverse that here
+ mSimpleView.setLongClickable(false);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mLongClicked = false;
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mSimpleView);
+ assertTrue(mSimpleView.hasFocus());
+ assertFalse(mLongClicked);
+ }
+
+ @LargeTest
+ public void testKeypadLongClick() throws Exception {
+ mSimpleView.requestFocus();
+ getInstrumentation().waitForIdleSync();
+ KeyUtils.longClick(this);
+
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mLongClicked);
+ }
+
+ @LargeTest
+ public void testTouchLongClick() throws Exception {
+ TouchUtils.longClickView(this, mSimpleView);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mLongClicked);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/DisabledTest.java b/core/tests/coretests/src/android/view/DisabledTest.java
new file mode 100644
index 0000000..992c277
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisabledTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.widget.Button;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * Exercises {@link android.view.View}'s disabled property.
+ */
+public class DisabledTest extends ActivityInstrumentationTestCase<Disabled> {
+ private Button mDisabled;
+ private View mDisabledParent;
+ private boolean mClicked;
+ private boolean mParentClicked;
+
+ public DisabledTest() {
+ super("com.android.frameworks.coretests", Disabled.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final Disabled a = getActivity();
+ mDisabled = (Button) a.findViewById(R.id.disabledButton);
+ mDisabled.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mClicked = true;
+ }
+ });
+
+ mDisabledParent = a.findViewById(R.id.clickableParent);
+ mDisabledParent.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mParentClicked = true;
+ }
+ });
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mClicked = false;
+ mParentClicked = false;
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mDisabled);
+ assertNotNull(mDisabledParent);
+ assertFalse(mDisabled.isEnabled());
+ assertTrue(mDisabledParent.isEnabled());
+ assertTrue(mDisabled.hasFocus());
+ }
+
+ @MediumTest
+ public void testKeypadClick() throws Exception {
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mClicked);
+ assertFalse(mParentClicked);
+ }
+
+ @LargeTest
+ public void testTouchClick() throws Exception {
+ TouchUtils.clickView(this, mDisabled);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mClicked);
+ assertFalse(mParentClicked);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/DrawableBgMinSize.java b/core/tests/coretests/src/android/view/DrawableBgMinSize.java
new file mode 100644
index 0000000..a75b23a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DrawableBgMinSize.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AbsoluteLayout;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * Views should obey their background {@link Drawable}'s minimum size
+ * requirements ({@link Drawable#getMinimumHeight()} and
+ * {@link Drawable#getMinimumWidth()}) when possible.
+ * <p>
+ * This Activity exercises a few Views with background {@link Drawable}s.
+ */
+public class DrawableBgMinSize extends Activity implements OnClickListener {
+ private boolean mUsingBigBg = false;
+ private Drawable mBackgroundDrawable;
+ private Drawable mBigBackgroundDrawable;
+ private Button mChangeBackgroundsButton;
+
+ private TextView mTextView;
+ private LinearLayout mLinearLayout;
+ private RelativeLayout mRelativeLayout;
+ private FrameLayout mFrameLayout;
+ private AbsoluteLayout mAbsoluteLayout;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.drawable_background_minimum_size);
+
+ mBackgroundDrawable = getResources().getDrawable(R.drawable.drawable_background);
+ mBigBackgroundDrawable = getResources().getDrawable(R.drawable.big_drawable_background);
+
+ mChangeBackgroundsButton = (Button) findViewById(R.id.change_backgrounds);
+ mChangeBackgroundsButton.setOnClickListener(this);
+
+ mTextView = (TextView) findViewById(R.id.text_view);
+ mLinearLayout = (LinearLayout) findViewById(R.id.linear_layout);
+ mRelativeLayout = (RelativeLayout) findViewById(R.id.relative_layout);
+ mFrameLayout = (FrameLayout) findViewById(R.id.frame_layout);
+ mAbsoluteLayout = (AbsoluteLayout) findViewById(R.id.absolute_layout);
+
+ changeBackgrounds(mBackgroundDrawable);
+ }
+
+ private void changeBackgrounds(Drawable newBg) {
+ mTextView.setBackgroundDrawable(newBg);
+ mLinearLayout.setBackgroundDrawable(newBg);
+ mRelativeLayout.setBackgroundDrawable(newBg);
+ mFrameLayout.setBackgroundDrawable(newBg);
+ mAbsoluteLayout.setBackgroundDrawable(newBg);
+ }
+
+ public void onClick(View v) {
+ if (mUsingBigBg) {
+ changeBackgrounds(mBackgroundDrawable);
+ } else {
+ changeBackgrounds(mBigBackgroundDrawable);
+ }
+
+ mUsingBigBg = !mUsingBigBg;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/DrawableBgMinSizeTest.java b/core/tests/coretests/src/android/view/DrawableBgMinSizeTest.java
new file mode 100644
index 0000000..e705c87
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DrawableBgMinSizeTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+import android.view.DrawableBgMinSize;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.graphics.drawable.Drawable;
+import android.test.ActivityInstrumentationTestCase;
+import android.view.View;
+import android.widget.AbsoluteLayout;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * {@link DrawableBgMinSize} exercises Views to obey their background drawable's
+ * minimum sizes.
+ */
+public class DrawableBgMinSizeTest extends
+ ActivityInstrumentationTestCase<DrawableBgMinSize> {
+ private Button mChangeBackgroundsButton;
+
+ private Drawable mBackgroundDrawable;
+ private Drawable mBigBackgroundDrawable;
+
+ private TextView mTextView;
+ private LinearLayout mLinearLayout;
+ private RelativeLayout mRelativeLayout;
+ private FrameLayout mFrameLayout;
+ private AbsoluteLayout mAbsoluteLayout;
+
+ public DrawableBgMinSizeTest() {
+ super("com.android.frameworks.coretests", DrawableBgMinSize.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final DrawableBgMinSize a = getActivity();
+
+ mChangeBackgroundsButton = (Button) a.findViewById(R.id.change_backgrounds);
+ mBackgroundDrawable = a.getResources().getDrawable(R.drawable.drawable_background);
+ mBigBackgroundDrawable = a.getResources().getDrawable(R.drawable.big_drawable_background);
+ mTextView = (TextView) a.findViewById(R.id.text_view);
+ mLinearLayout = (LinearLayout) a.findViewById(R.id.linear_layout);
+ mRelativeLayout = (RelativeLayout) a.findViewById(R.id.relative_layout);
+ mFrameLayout = (FrameLayout) a.findViewById(R.id.frame_layout);
+ mAbsoluteLayout = (AbsoluteLayout) a.findViewById(R.id.absolute_layout);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mChangeBackgroundsButton);
+ assertNotNull(mBackgroundDrawable);
+ assertNotNull(mBigBackgroundDrawable);
+ assertNotNull(mTextView);
+ assertNotNull(mLinearLayout);
+ assertNotNull(mRelativeLayout);
+ assertNotNull(mFrameLayout);
+ assertNotNull(mAbsoluteLayout);
+ }
+
+ public void doMinimumSizeTest(View view) throws Exception {
+ assertTrue(view.getClass().getSimpleName() + " should respect the background Drawable's minimum width",
+ view.getWidth() >= mBackgroundDrawable.getMinimumWidth());
+ assertTrue(view.getClass().getSimpleName() + " should respect the background Drawable's minimum height",
+ view.getHeight() >= mBackgroundDrawable.getMinimumHeight());
+ }
+
+ @MediumTest
+ public void testTextViewMinimumSize() throws Exception {
+ doMinimumSizeTest(mTextView);
+ }
+
+ @MediumTest
+ public void testLinearLayoutMinimumSize() throws Exception {
+ doMinimumSizeTest(mLinearLayout);
+ }
+
+ @MediumTest
+ public void testRelativeLayoutMinimumSize() throws Exception {
+ doMinimumSizeTest(mRelativeLayout);
+ }
+
+ @MediumTest
+ public void testAbsoluteLayoutMinimumSize() throws Exception {
+ doMinimumSizeTest(mAbsoluteLayout);
+ }
+
+ @MediumTest
+ public void testFrameLayoutMinimumSize() throws Exception {
+ doMinimumSizeTest(mFrameLayout);
+ }
+
+ public void doDiffBgMinimumSizeTest(final View view) throws Exception {
+ // Change to the bigger backgrounds
+ TouchUtils.tapView(this, mChangeBackgroundsButton);
+
+ assertTrue(view.getClass().getSimpleName()
+ + " should respect the different bigger background Drawable's minimum width", view
+ .getWidth() >= mBigBackgroundDrawable.getMinimumWidth());
+ assertTrue(view.getClass().getSimpleName()
+ + " should respect the different bigger background Drawable's minimum height", view
+ .getHeight() >= mBigBackgroundDrawable.getMinimumHeight());
+ }
+
+ @MediumTest
+ public void testTextViewDiffBgMinimumSize() throws Exception {
+ doDiffBgMinimumSizeTest(mTextView);
+ }
+
+ @MediumTest
+ public void testLinearLayoutDiffBgMinimumSize() throws Exception {
+ doDiffBgMinimumSizeTest(mLinearLayout);
+ }
+
+ @MediumTest
+ public void testRelativeLayoutDiffBgMinimumSize() throws Exception {
+ doDiffBgMinimumSizeTest(mRelativeLayout);
+ }
+
+ @MediumTest
+ public void testAbsoluteLayoutDiffBgMinimumSize() throws Exception {
+ doDiffBgMinimumSizeTest(mAbsoluteLayout);
+ }
+
+ @MediumTest
+ public void testFrameLayoutDiffBgMinimumSize() throws Exception {
+ doDiffBgMinimumSizeTest(mFrameLayout);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/FocusFinderTest.java b/core/tests/coretests/src/android/view/FocusFinderTest.java
new file mode 100644
index 0000000..186689f
--- /dev/null
+++ b/core/tests/coretests/src/android/view/FocusFinderTest.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Rect;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class FocusFinderTest extends AndroidTestCase {
+
+ private FocusFinderHelper mFocusFinder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mFocusFinder = new FocusFinderHelper(FocusFinder.getInstance());
+ }
+
+ @SmallTest
+ public void testPreconditions() {
+ assertNotNull("focus finder instance", mFocusFinder);
+ }
+
+ @SmallTest
+ public void testBelowNotCandidateForDirectionUp() {
+ assertIsNotCandidate(View.FOCUS_UP,
+ new Rect(0, 30, 10, 40), // src (left, top, right, bottom)
+ new Rect(0, 50, 10, 60)); // dest (left, top, right, bottom)
+ }
+
+ @SmallTest
+ public void testAboveShareEdgeEdgeOkForDirectionUp() {
+ final Rect src = new Rect(0, 30, 10, 40);
+
+ final Rect dest = new Rect(src);
+ dest.offset(0, -src.height());
+ assertEquals(src.top, dest.bottom);
+
+ assertDirectionIsCandidate(View.FOCUS_UP, src, dest);
+ }
+
+ @SmallTest
+ public void testCompletelyContainedNotCandidate() {
+ assertIsNotCandidate(
+ View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50),
+ new Rect(0, 1, 50, 49));
+ }
+
+ @SmallTest
+ public void testContinaedWithCommonBottomNotCandidate() {
+ assertIsNotCandidate(
+ View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50),
+ new Rect(0, 1, 50, 50));
+ }
+
+ @SmallTest
+ public void testOverlappingIsCandidateWhenBothEdgesAreInDirection() {
+ assertDirectionIsCandidate(
+ View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50),
+ new Rect(0, 1, 50, 51));
+ }
+
+ @SmallTest
+ public void testTopEdgeOfDestAtOrAboveTopOfSrcNotCandidateForDown() {
+ assertIsNotCandidate(
+ View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50),
+ new Rect(0, 0, 50, 51));
+ assertIsNotCandidate(
+ View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50),
+ new Rect(0, -1, 50, 51));
+ }
+
+ @SmallTest
+ public void testSameRectBeamsOverlap() {
+ final Rect rect = new Rect(0, 0, 20, 20);
+
+ assertBeamsOverlap(View.FOCUS_LEFT, rect, rect);
+ assertBeamsOverlap(View.FOCUS_RIGHT, rect, rect);
+ assertBeamsOverlap(View.FOCUS_UP, rect, rect);
+ assertBeamsOverlap(View.FOCUS_DOWN, rect, rect);
+ }
+
+ @SmallTest
+ public void testOverlapBeamsRightLeftUpToEdge() {
+ final Rect rect1 = new Rect(0, 0, 20, 20);
+ final Rect rect2 = new Rect(rect1);
+
+ // just below bottom edge
+ rect2.offset(0, rect1.height() - 1);
+ assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
+
+ // at edge
+ rect2.offset(0, 1);
+ assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
+
+ // just beyond
+ rect2.offset(0, 1);
+ assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
+
+ // just below top edge
+ rect2.set(rect1);
+ rect2.offset(0, -(rect1.height() - 1));
+ assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
+
+ // at top edge
+ rect2.offset(0, -1);
+ assertBeamsOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_RIGHT, rect1, rect2);
+
+ // just beyond top edge
+ rect2.offset(0, -1);
+ assertBeamsDontOverlap(View.FOCUS_LEFT, rect1, rect2);
+ assertBeamsDontOverlap(View.FOCUS_RIGHT, rect1, rect2);
+ }
+
+ @SmallTest
+ public void testOverlapBeamsUpDownUpToEdge() {
+ final Rect rect1 = new Rect(0, 0, 20, 20);
+ final Rect rect2 = new Rect(rect1);
+
+ // just short of right edge
+ rect2.offset(rect1.width() - 1, 0);
+ assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
+
+ // at edge
+ rect2.offset(1, 0);
+ assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
+
+ // just beyond
+ rect2.offset(1, 0);
+ assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
+
+ // just short of left edge
+ rect2.set(rect1);
+ rect2.offset(-(rect1.width() - 1), 0);
+ assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
+
+ // at edge
+ rect2.offset(-1, 0);
+ assertBeamsOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsOverlap(View.FOCUS_DOWN, rect1, rect2);
+
+ // just beyond edge
+ rect2.offset(-1, 0);
+ assertBeamsDontOverlap(View.FOCUS_UP, rect1, rect2);
+ assertBeamsDontOverlap(View.FOCUS_DOWN, rect1, rect2);
+ }
+
+ @SmallTest
+ public void testDirectlyAboveTrumpsAboveLeft() {
+ Rect src = new Rect(0, 50, 20, 70); // src (left, top, right, bottom)
+
+ Rect directlyAbove = new Rect(src);
+ directlyAbove.offset(0, -(1 + src.height()));
+
+ Rect aboveLeft = new Rect(src);
+ aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
+
+ assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
+ }
+
+ @SmallTest
+ public void testAboveInBeamTrumpsSlightlyCloserOutOfBeam() {
+ Rect src = new Rect(0, 50, 20, 70); // src (left, top, right, bottom)
+
+ Rect directlyAbove = new Rect(src);
+ directlyAbove.offset(0, -(1 + src.height()));
+
+ Rect aboveLeft = new Rect(src);
+ aboveLeft.offset(-(1 + src.width()), -(1 + src.height()));
+
+ // offset directly above a little further up
+ directlyAbove.offset(0, -5);
+ assertBetterCandidate(View.FOCUS_UP, src, directlyAbove, aboveLeft);
+ }
+
+ @SmallTest
+ public void testOutOfBeamBeatsInBeamUp() {
+
+ Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
+
+ Rect aboveLeftOfBeam = new Rect(src);
+ aboveLeftOfBeam.offset(-(src.width() + 1), -src.height());
+ assertBeamsDontOverlap(View.FOCUS_UP, src, aboveLeftOfBeam);
+
+ Rect aboveInBeam = new Rect(src);
+ aboveInBeam.offset(0, -src.height());
+ assertBeamsOverlap(View.FOCUS_UP, src, aboveInBeam);
+
+ // in beam wins
+ assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
+
+ // still wins while aboveInBeam's bottom edge is < out of beams' top
+ aboveInBeam.offset(0, -(aboveLeftOfBeam.height() - 1));
+ assertTrue("aboveInBeam.bottom > aboveLeftOfBeam.top", aboveInBeam.bottom > aboveLeftOfBeam.top);
+ assertBetterCandidate(View.FOCUS_UP, src, aboveInBeam, aboveLeftOfBeam);
+
+ // cross the threshold: the out of beam prevails
+ aboveInBeam.offset(0, -1);
+ assertEquals(aboveInBeam.bottom, aboveLeftOfBeam.top);
+ assertBetterCandidate(View.FOCUS_UP, src, aboveLeftOfBeam, aboveInBeam);
+ }
+
+ /**
+ * A non-candidate (even a much closer one) is always a worse choice
+ * than a real candidate.
+ */
+ @MediumTest
+ public void testSomeCandidateBetterThanNonCandidate() {
+ Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom)
+
+ Rect nonCandidate = new Rect(src);
+ nonCandidate.offset(src.width() + 1, 0);
+
+ assertIsNotCandidate(View.FOCUS_LEFT, src, nonCandidate);
+
+ Rect candidate = new Rect(src);
+ candidate.offset(-(4 * src.width()), 0);
+ assertDirectionIsCandidate(View.FOCUS_LEFT, src, candidate);
+
+ assertBetterCandidate(View.FOCUS_LEFT, src, candidate, nonCandidate);
+ }
+
+ /**
+ * Grabbed from {@link android.widget.focus.VerticalFocusSearchTest#testSearchFromMidLeft()}
+ */
+ @SmallTest
+ public void testVerticalFocusSearchScenario() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 109, 153, 169), // src
+ new Rect(166, 169, 319, 229), // expectedbetter
+ new Rect(0, 229, 320, 289)); // expectedworse
+
+ // failing test 4/10/2008, the values were tweaked somehow in functional
+ // test...
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 91, 153, 133), // src
+ new Rect(166, 133, 319, 175), // expectedbetter
+ new Rect(0, 175, 320, 217)); // expectedworse
+
+ }
+
+ /**
+ * Example: going down from a thin button all the way to the left of a
+ * screen where, just below, is a very wide button, and just below that,
+ * is an equally skinny button all the way to the left. want to make
+ * sure any minor axis factor doesn't override the fact that the one below
+ * in vertical beam should be next focus
+ */
+ @SmallTest
+ public void testBeamsOverlapMajorAxisCloserMinorAxisFurther() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 100, 100), // src
+ new Rect(0, 100, 480, 200), // expectedbetter
+ new Rect(0, 200, 100, 300)); // expectedworse
+ }
+
+ /**
+ * Real scenario grabbed from song playback screen.
+ */
+ @SmallTest
+ public void testMusicPlaybackScenario() {
+ assertBetterCandidate(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(227, 185, 312, 231), // src
+ new Rect(195, 386, 266, 438), // expectedbetter
+ new Rect(124, 386, 195, 438)); // expectedworse
+ }
+
+ /**
+ * more generalized version of {@link #testMusicPlaybackScenario()}
+ */
+ @SmallTest
+ public void testOutOfBeamOverlapBeatsOutOfBeamFurtherOnMajorAxis() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50), // src
+ new Rect(60, 40, 110, 90), // expectedbetter
+ new Rect(60, 70, 110, 120)); // expectedworse
+ }
+
+ /**
+ * Make sure that going down prefers views that are actually
+ * down (and not those next to but still a candidate because
+ * they are overlapping on the major axis)
+ */
+ @SmallTest
+ public void testInBeamTrumpsOutOfBeamOverlapping() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50), // src
+ new Rect(0, 60, 50, 110), // expectedbetter
+ new Rect(51, 1, 101, 51)); // expectedworse
+ }
+
+ @SmallTest
+ public void testOverlappingBeatsNonOverlapping() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 50, 50), // src
+ new Rect(0, 40, 50, 90), // expectedbetter
+ new Rect(0, 75, 50, 125)); // expectedworse
+ }
+
+ @SmallTest
+ public void testEditContactScenarioLeftFromDiscardChangesGoesToSaveContactInLandscape() {
+ assertBetterCandidate(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(357, 258, 478, 318), // src
+ new Rect(2, 258, 100, 318), // better
+ new Rect(106, 120, 424, 184)); // worse
+ }
+
+ /**
+ * A dial pad with 9 squares arranged in a grid. no padding, so
+ * the edges are equal. see {@link android.widget.focus.LinearLayoutGrid}
+ */
+ @SmallTest
+ public void testGridWithTouchingEdges() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(106, 49, 212, 192), // src
+ new Rect(106, 192, 212, 335), // better
+ new Rect(0, 192, 106, 335)); // worse
+
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(106, 49, 212, 192), // src
+ new Rect(106, 192, 212, 335), // better
+ new Rect(212, 192, 318, 335)); // worse
+ }
+
+ @SmallTest
+ public void testSearchFromEmptyRect() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 0, 0), // src
+ new Rect(0, 0, 320, 45), // better
+ new Rect(0, 45, 320, 545)); // worse
+ }
+
+ /**
+ * Reproduce bug 1124559, drilling down to actual bug
+ * (majorAxisDistance was wrong for direction left)
+ */
+ @SmallTest
+ public void testGmailReplyButtonsScenario() {
+ assertBetterCandidate(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417), // src
+ new Rect(102, 380, 210, 417), // better
+ new Rect(111, 443, 206, 480)); // worse
+
+ assertBeamBeats(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417), // src
+ new Rect(102, 380, 210, 417), // better
+ new Rect(111, 443, 206, 480)); // worse
+
+ assertBeamsOverlap(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417),
+ new Rect(102, 380, 210, 417));
+
+ assertBeamsDontOverlap(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417),
+ new Rect(111, 443, 206, 480));
+
+ assertTrue(
+ "major axis distance less than major axis distance to "
+ + "far edge",
+ FocusFinderHelper.majorAxisDistance(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417),
+ new Rect(102, 380, 210, 417)) <
+ FocusFinderHelper.majorAxisDistanceToFarEdge(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(223, 380, 312, 417),
+ new Rect(111, 443, 206, 480)));
+ }
+
+ @SmallTest
+ public void testGmailScenarioBug1203288() {
+ assertBetterCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 2, 480, 82), // src
+ new Rect(344, 87, 475, 124), // better
+ new Rect(0, 130, 480, 203)); // worse
+ }
+
+ @SmallTest
+ public void testHomeShortcutScenarioBug1295354() {
+ assertBetterCandidate(View.FOCUS_RIGHT,
+ // L T R B
+ new Rect(3, 338, 77, 413), // src
+ new Rect(163, 338, 237, 413), // better
+ new Rect(83, 38, 157, 113)); // worse
+ }
+
+ @SmallTest
+ public void testBeamAlwaysBeatsHoriz() {
+ assertBetterCandidate(View.FOCUS_RIGHT,
+ // L T R B
+ new Rect(0, 0, 50, 50), // src
+ new Rect(150, 0, 200, 50), // better, (way further, but in beam)
+ new Rect(60, 51, 110, 101)); // worse, even though it is closer
+
+ assertBetterCandidate(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(150, 0, 200, 50), // src
+ new Rect(0, 50, 50, 50), // better, (way further, but in beam)
+ new Rect(49, 99, 149, 101)); // worse, even though it is closer
+ }
+
+ @SmallTest
+ public void testIsCandidateOverlappingEdgeFromEmptyRect() {
+ assertDirectionIsCandidate(View.FOCUS_DOWN,
+ // L T R B
+ new Rect(0, 0, 0, 0), // src
+ new Rect(0, 0, 20, 1)); // candidate
+
+ assertDirectionIsCandidate(View.FOCUS_UP,
+ // L T R B
+ new Rect(0, 0, 0, 0), // src
+ new Rect(0, -1, 20, 0)); // candidate
+
+ assertDirectionIsCandidate(View.FOCUS_LEFT,
+ // L T R B
+ new Rect(0, 0, 0, 0), // src
+ new Rect(-1, 0, 0, 20)); // candidate
+
+ assertDirectionIsCandidate(View.FOCUS_RIGHT,
+ // L T R B
+ new Rect(0, 0, 0, 0), // src
+ new Rect(0, 0, 1, 20)); // candidate
+ }
+
+ private void assertBeamsOverlap(int direction, Rect rect1, Rect rect2) {
+ String directionStr = validateAndGetStringFor(direction);
+ String assertMsg = String.format("Expected beams to overlap in direction %s "
+ + "for rectangles %s and %s", directionStr, rect1, rect2);
+ assertTrue(assertMsg, mFocusFinder.beamsOverlap(direction, rect1, rect2));
+ }
+
+ private void assertBeamsDontOverlap(int direction, Rect rect1, Rect rect2) {
+ String directionStr = validateAndGetStringFor(direction);
+ String assertMsg = String.format("Expected beams not to overlap in direction %s "
+ + "for rectangles %s and %s", directionStr, rect1, rect2);
+ assertFalse(assertMsg, mFocusFinder.beamsOverlap(direction, rect1, rect2));
+ }
+
+ /**
+ * Assert that particular rect is a better focus search candidate from a
+ * source rect than another.
+ * @param direction The direction of focus search.
+ * @param srcRect The src rectangle.
+ * @param expectedBetter The candidate that should be better.
+ * @param expectedWorse The candidate that should be worse.
+ */
+ private void assertBetterCandidate(int direction, Rect srcRect,
+ Rect expectedBetter, Rect expectedWorse) {
+
+ String directionStr = validateAndGetStringFor(direction);
+ String assertMsg = String.format(
+ "expected %s to be a better focus search candidate than "
+ + "%s when searching "
+ + "from %s in direction %s",
+ expectedBetter, expectedWorse, srcRect, directionStr);
+
+ assertTrue(assertMsg,
+ mFocusFinder.isBetterCandidate(direction, srcRect,
+ expectedBetter, expectedWorse));
+
+ assertMsg = String.format(
+ "expected %s to not be a better focus search candidate than "
+ + "%s when searching "
+ + "from %s in direction %s",
+ expectedWorse, expectedBetter, srcRect, directionStr);
+
+ assertFalse(assertMsg,
+ mFocusFinder.isBetterCandidate(direction, srcRect,
+ expectedWorse, expectedBetter));
+ }
+
+ private void assertIsNotCandidate(int direction, Rect src, Rect dest) {
+ String directionStr = validateAndGetStringFor(direction);
+
+ final String assertMsg = String.format(
+ "expected going from %s to %s in direction %s to be an invalid "
+ + "focus search candidate",
+ src, dest, directionStr);
+ assertFalse(assertMsg, mFocusFinder.isCandidate(src, dest, direction));
+ }
+
+ private void assertBeamBeats(int direction, Rect srcRect,
+ Rect rect1, Rect rect2) {
+
+ String directionStr = validateAndGetStringFor(direction);
+ String assertMsg = String.format(
+ "expecting %s to beam beat %s w.r.t %s in direction %s",
+ rect1, rect2, srcRect, directionStr);
+ assertTrue(assertMsg, mFocusFinder.beamBeats(direction, srcRect, rect1, rect2));
+ }
+
+
+ private void assertDirectionIsCandidate(int direction, Rect src, Rect dest) {
+ String directionStr = validateAndGetStringFor(direction);
+
+ final String assertMsg = String.format(
+ "expected going from %s to %s in direction %s to be a valid "
+ + "focus search candidate",
+ src, dest, directionStr);
+ assertTrue(assertMsg, mFocusFinder.isCandidate(src, dest, direction));
+ }
+
+ private String validateAndGetStringFor(int direction) {
+ String directionStr = "??";
+ switch(direction) {
+ case View.FOCUS_UP:
+ directionStr = "FOCUS_UP";
+ break;
+ case View.FOCUS_DOWN:
+ directionStr = "FOCUS_DOWN";
+ break;
+ case View.FOCUS_LEFT:
+ directionStr = "FOCUS_LEFT";
+ break;
+ case View.FOCUS_RIGHT:
+ directionStr = "FOCUS_RIGHT";
+ break;
+ default:
+ fail("passed in unknown direction, ya blewit!");
+ }
+ return directionStr;
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/view/GlobalFocusChange.java b/core/tests/coretests/src/android/view/GlobalFocusChange.java
new file mode 100644
index 0000000..041c0de
--- /dev/null
+++ b/core/tests/coretests/src/android/view/GlobalFocusChange.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewTreeObserver;
+import android.view.View;
+
+public class GlobalFocusChange extends Activity implements ViewTreeObserver.OnGlobalFocusChangeListener {
+ public View mOldFocus;
+ public View mNewFocus;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.focus_listener);
+ findViewById(R.id.left).getViewTreeObserver().addOnGlobalFocusChangeListener(this);
+ }
+
+ public void reset() {
+ mOldFocus = mNewFocus = null;
+ }
+
+ public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+ mOldFocus = oldFocus;
+ mNewFocus = newFocus;
+ }
+}
diff --git a/core/tests/coretests/src/android/view/GlobalFocusChangeTest.java b/core/tests/coretests/src/android/view/GlobalFocusChangeTest.java
new file mode 100644
index 0000000..89e32e4
--- /dev/null
+++ b/core/tests/coretests/src/android/view/GlobalFocusChangeTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.View;
+import android.view.KeyEvent;
+import com.android.frameworks.coretests.R;
+
+public class GlobalFocusChangeTest extends ActivityInstrumentationTestCase<GlobalFocusChange> {
+ private GlobalFocusChange mActivity;
+ private View mLeft;
+ private View mRight;
+
+ public GlobalFocusChangeTest() {
+ super("com.android.frameworks.coretests", GlobalFocusChange.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ mLeft = mActivity.findViewById(R.id.left);
+ mRight = mActivity.findViewById(R.id.right);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mActivity.reset();
+ super.tearDown();
+ }
+
+ @FlakyTest(tolerance = 4)
+ @LargeTest
+ public void testFocusChange() throws Exception {
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+
+ assertFalse(mLeft.isFocused());
+ assertTrue(mRight.isFocused());
+
+ assertSame(mLeft, mActivity.mOldFocus);
+ assertSame(mRight, mActivity.mNewFocus);
+ }
+
+ @FlakyTest(tolerance = 4)
+ @MediumTest
+ public void testEnterTouchMode() throws Exception {
+ assertTrue(mLeft.isFocused());
+
+ TouchUtils.tapView(this, mLeft);
+
+ assertSame(mLeft, mActivity.mOldFocus);
+ assertSame(null, mActivity.mNewFocus);
+ }
+
+ @FlakyTest(tolerance = 4)
+ @MediumTest
+ public void testLeaveTouchMode() throws Exception {
+ assertTrue(mLeft.isFocused());
+
+ TouchUtils.tapView(this, mLeft);
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+
+ assertTrue(mLeft.isFocused());
+
+ assertSame(null, mActivity.mOldFocus);
+ assertSame(mLeft, mActivity.mNewFocus);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/Include.java b/core/tests/coretests/src/android/view/Include.java
new file mode 100644
index 0000000..e90c484
--- /dev/null
+++ b/core/tests/coretests/src/android/view/Include.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+
+/**
+ * Exercise <include /> tag in XML files.
+ */
+public class Include extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.include_tag);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/IncludeTest.java b/core/tests/coretests/src/android/view/IncludeTest.java
new file mode 100644
index 0000000..cdcfa3c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/IncludeTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.Include;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class IncludeTest extends ActivityInstrumentationTestCase<Include> {
+ public IncludeTest() {
+ super("com.android.frameworks.coretests", Include.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @MediumTest
+ public void testIncluded() throws Exception {
+ final Include activity = getActivity();
+
+ final View button1 = activity.findViewById(R.id.included_button);
+ assertNotNull("The layout include_button was not included", button1);
+
+ final View button2 = activity.findViewById(R.id.included_button_overriden);
+ assertNotNull("The layout include_button was not included with overriden id", button2);
+ }
+
+ @MediumTest
+ public void testIncludedWithLayoutParams() throws Exception {
+ final Include activity = getActivity();
+
+ final View button1 = activity.findViewById(R.id.included_button);
+ final View button2 = activity.findViewById(R.id.included_button_overriden);
+
+ assertTrue("Both buttons should have different width",
+ button1.getLayoutParams().width != button2.getLayoutParams().width);
+ assertTrue("Both buttons should have different height",
+ button1.getLayoutParams().height != button2.getLayoutParams().height);
+ }
+
+ @MediumTest
+ public void testIncludedWithVisibility() throws Exception {
+ final Include activity = getActivity();
+ final View button1 = activity.findViewById(R.id.included_button_visibility);
+
+ assertEquals("Included button should be invisible", View.INVISIBLE, button1.getVisibility());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @MediumTest
+ public void testIncludedWithSize() throws Exception {
+ final Include activity = getActivity();
+ final View button1 = activity.findViewById(R.id.included_button_with_size);
+
+ final ViewGroup.LayoutParams lp = button1.getLayoutParams();
+ assertEquals("Included button should be 23dip x 23dip", 23, lp.width);
+ assertEquals("Included button should be 23dip x 23dip", 23, lp.height);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InflateTest.java b/core/tests/coretests/src/android/view/InflateTest.java
new file mode 100644
index 0000000..cb4f8e2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/InflateTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.test.PerformanceTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.frameworks.coretests.R;
+
+import java.util.Map;
+
+public class InflateTest extends AndroidTestCase implements PerformanceTestCase {
+ private LayoutInflater mInflater;
+ private Resources mResources;
+ private View mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mInflater = LayoutInflater.from(mContext);
+ mResources = mContext.getResources();
+
+ // to try to make things consistent, before doing timing
+ // do an initial instantiation of the layout and then clear
+ // out the layout cache.
+// mInflater.inflate(mResId, null, null);
+// mResources.flushLayoutCache();
+ }
+
+ public int startPerformance(PerformanceTestCase.Intermediates intermediates) {
+ return 0;
+ }
+
+ public boolean isPerformanceOnly() {
+ return false;
+ }
+
+ public void inflateTest(int resourceId) {
+ mView = mInflater.inflate(resourceId, null);
+ mResources.flushLayoutCache();
+ }
+
+ public void inflateCachedTest(int resourceId) {
+ // Make sure this layout is in the cache.
+ mInflater.inflate(resourceId, null);
+
+ mInflater.inflate(resourceId, null);
+ }
+
+ @SmallTest
+ public void testLayout1() throws Exception {
+ inflateTest(R.layout.layout_one);
+ }
+
+ @SmallTest
+ public void testLayout2() throws Exception {
+ inflateTest(R.layout.layout_two);
+ }
+
+ @SmallTest
+ public void testLayout3() throws Exception {
+ inflateTest(R.layout.layout_three);
+ }
+
+ @SmallTest
+ public void testLayout4() throws Exception {
+ inflateTest(R.layout.layout_four);
+ }
+
+ @SmallTest
+ public void testLayout5() throws Exception {
+ inflateTest(R.layout.layout_five);
+ }
+
+ @SmallTest
+ public void testLayout6() throws Exception {
+ inflateTest(R.layout.layout_six);
+ }
+
+ @SmallTest
+ public void testCachedLayout1() throws Exception {
+ inflateCachedTest(R.layout.layout_one);
+ }
+
+ @SmallTest
+ public void testCachedLayout2() throws Exception {
+ inflateCachedTest(R.layout.layout_two);
+ }
+
+ @SmallTest
+ public void testCachedLayout3() throws Exception {
+ inflateCachedTest(R.layout.layout_three);
+ }
+
+ @SmallTest
+ public void testCachedLayout4() throws Exception {
+ inflateCachedTest(R.layout.layout_four);
+ }
+
+ @SmallTest
+ public void testCachedLayout5() throws Exception {
+ inflateCachedTest(R.layout.layout_five);
+ }
+
+ @SmallTest
+ public void testCachedLayout6() throws Exception {
+ inflateCachedTest(R.layout.layout_six);
+ }
+
+// public void testLayoutTag() throws Exception {
+// public void setUp
+// (Context
+// context){
+// setUp(context, R.layout.layout_tag);
+// }
+// public void run
+// ()
+// {
+// super.run();
+// if (!"MyTag".equals(mView.getTag())) {
+// throw new RuntimeException("Incorrect tag: " + mView.getTag());
+// }
+// }
+// }
+
+ public static class ViewOne extends View {
+ public ViewOne(Context context) {
+ super(context);
+ }
+
+ public ViewOne(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ListContextMenu.java b/core/tests/coretests/src/android/view/ListContextMenu.java
new file mode 100644
index 0000000..1b4ece6
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ListContextMenu.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+/**
+ * Exercises context menus in lists
+ */
+public class ListContextMenu extends ListActivity implements View.OnCreateContextMenuListener
+{
+ static final String TAG = "ListContextMenu";
+
+ ThrashListAdapter mAdapter;
+
+ private class ThrashListAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+
+ private String[] mTitles = new String[100];
+
+ public ThrashListAdapter(Context context) {
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTitles = new String[100];
+
+ int i;
+ for (i=0; i<100; i++) {
+ mTitles[i] = "[" + i + "]";
+ }
+ }
+
+ public int getCount() {
+ return mTitles.length;
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+
+ if (convertView == null) {
+ view = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, null);
+ } else {
+ view = (TextView) convertView;
+ }
+ view.setText("List item " + mTitles[position]);
+ return view;
+ }
+
+ }
+
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ mAdapter = new ThrashListAdapter(this);
+ getListView().setOnCreateContextMenuListener(this);
+ setListAdapter(mAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItem item = menu.add(0, 0, 0, "Really long menu item name");
+ item.setTitleCondensed("Long name");
+ item.setIcon(R.drawable.black_square);
+
+ SubMenu sm = menu.addSubMenu(0, 0, 0, "The 2nd item, a sub menu").setIcon(R.drawable.black_square_stretchable);
+ item = sm.getItem();
+ item.setTitleCondensed("Sub menu");
+ sm.add(1, 0, 0, "Subitem 1");
+ sm.add(1, 0, 0, "Subitem 2");
+ sm.add(1, 0, 0, "Subitem 3");
+ sm.setGroupCheckable(1, true, true);
+ menu.add(0, 0, 0, "Item 3");
+ menu.add(0, 0, 0, "Item 4");
+ menu.add(0, 0, 0, "Item 5");
+ menu.add(0, 0, 0, "Item 6");
+ menu.add(0, 0, 0, "Item 7");
+ menu.add(0, 0, 0, "Item 8");
+ menu.add(0, 0, 0, "Item 9");
+ sm = menu.addSubMenu(0, 0, 0, "Item 10 SM");
+ sm.add(0, 0, 0, "Subitem 1");
+ sm.add(0, 0, 0, "Subitem 2");
+ sm.add(0, 0, 0, "Subitem 3");
+ sm.add(0, 0, 0, "Subitem 4");
+ sm.add(0, 0, 0, "Subitem 5");
+ sm.add(0, 0, 0, "Subitem 6");
+ sm.add(0, 0, 0, "Subitem 7");
+ sm.add(0, 0, 0, "Subitem 8");
+
+ return true;
+ }
+
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
+
+ String text = ((TextView) info.targetView).getText().toString();
+ if (text.contains("[0]")) {
+ menu.setHeaderTitle("This is a test of the title and the icon").setHeaderIcon(
+ android.R.drawable.sym_def_app_icon);
+ } else if (text.contains("[1]")) {
+ menu.setHeaderTitle("This is a test of just the title");
+ } else {
+ TextView textView = new TextView(this);
+ textView.setText("This is a test of a custom View");
+ menu.setHeaderView(textView);
+ }
+
+ menu.add(0, 0, 0, "Test 1");
+ SubMenu sm = menu.addSubMenu(0, 0, 0, "Test 1.5 SM");
+ sm.add(0, 0, 0, "CM Subitem 1");
+ sm.add(0, 0, 0, "CM Subitem 2");
+ sm.add(0, 0, 0, "CM Subitem 3");
+ menu.add(0, 0, 0, "Test 2");
+ menu.add(0, 0, 0, "Test 3");
+ menu.add(0, 0, 0, "Test 4");
+ menu.add(0, 0, 0, "Test 5");
+ menu.add(0, 0, 0, "Test 6");
+ menu.add(0, 0, 0, "Test 7");
+ menu.add(0, 0, 0, "Test 8");
+ menu.add(0, 0, 0, "Test 9");
+ menu.add(0, 0, 0, "Test 10");
+ menu.add(0, 0, 0, "Test 11");
+ menu.add(0, 0, 0, "Test 12");
+ menu.add(0, 0, 0, "Test 13");
+ menu.add(0, 0, 0, "Test 14");
+ menu.add(0, 0, 0, "Test 15");
+ menu.add(0, 0, 0, "Test 16");
+ menu.add(0, 0, 0, "Test 17");
+ menu.add(0, 0, 0, "Test 18");
+ menu.add(0, 0, 0, "Test 19");
+ menu.add(0, 0, 0, "Test 20");
+ menu.add(0, 0, 0, "Test 21");
+ menu.add(0, 0, 0, "Test 22");
+ menu.add(0, 0, 0, "Test 23");
+ menu.add(0, 0, 0, "Test 24");
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ Log.i(TAG, "Options item " + item.toString() + " selected.");
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onOptionsMenuClosed(Menu menu) {
+ Log.i(TAG, "Options menu closed");
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ Log.i(TAG, "Context item " + item.toString() + " selected.");
+
+ return super.onContextItemSelected(item);
+ }
+
+ @Override
+ public void onContextMenuClosed(Menu menu) {
+ Log.i(TAG, "Context menu closed");
+ }
+
+
+}
diff --git a/core/java/android/os/MailboxNotAvailableException.java b/core/tests/coretests/src/android/view/Longpress.java
index 574adbd..e8e6f13 100644
--- a/core/java/android/os/MailboxNotAvailableException.java
+++ b/core/tests/coretests/src/android/view/Longpress.java
@@ -14,24 +14,19 @@
* limitations under the License.
*/
-package android.os;
+package android.view;
-/** @hide */
-public class MailboxNotAvailableException extends Throwable
-{
- /**
- * This exception represents the case when a request for a
- * named, published mailbox fails because the requested name has not been published
- */
+import com.android.frameworks.coretests.R;
- public
- MailboxNotAvailableException()
- {
- }
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Longpress extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
- public
- MailboxNotAvailableException(String s)
- {
- super(s);
+ setContentView(R.layout.longpress);
}
}
+
diff --git a/core/tests/coretests/src/android/view/LongpressTest.java b/core/tests/coretests/src/android/view/LongpressTest.java
new file mode 100644
index 0000000..45ce331
--- /dev/null
+++ b/core/tests/coretests/src/android/view/LongpressTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.Longpress;
+import com.android.frameworks.coretests.R;
+import android.util.KeyUtils;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.view.View;
+import android.view.View.OnLongClickListener;
+
+/**
+ * Exercises {@link android.view.View}'s longpress plumbing.
+ */
+public class LongpressTest extends ActivityInstrumentationTestCase<Longpress> {
+ private View mSimpleView;
+ private boolean mLongClicked;
+
+ public LongpressTest() {
+ super("com.android.frameworks.coretests", Longpress.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final Longpress a = getActivity();
+ mSimpleView = a.findViewById(R.id.simple_view);
+ mSimpleView.setOnLongClickListener(new OnLongClickListener() {
+ public boolean onLongClick(View v) {
+ mLongClicked = true;
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mLongClicked = false;
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mSimpleView);
+ assertTrue(mSimpleView.hasFocus());
+ assertFalse(mLongClicked);
+ }
+
+ @LargeTest
+ public void testKeypadLongClick() throws Exception {
+ mSimpleView.requestFocus();
+ getInstrumentation().waitForIdleSync();
+ KeyUtils.longClick(this);
+
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mLongClicked);
+ }
+
+ @LargeTest
+ public void testTouchLongClick() throws Exception {
+ TouchUtils.longClickView(this, mSimpleView);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mLongClicked);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/MenuTest.java b/core/tests/coretests/src/android/view/MenuTest.java
new file mode 100644
index 0000000..e8a8438
--- /dev/null
+++ b/core/tests/coretests/src/android/view/MenuTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.internal.view.menu.MenuBuilder;
+
+import junit.framework.Assert;
+
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.SubMenu;
+
+import com.android.frameworks.coretests.R;
+
+public class MenuTest extends AndroidTestCase {
+
+ private MenuBuilder mMenu;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mMenu = new MenuBuilder(super.getContext());
+ }
+
+ @SmallTest
+ public void testItemId() {
+ final int id = 512;
+ final MenuItem item = mMenu.add(0, id, 0, "test");
+
+ Assert.assertEquals(id, item.getItemId());
+ Assert.assertEquals(item, mMenu.findItem(id));
+ Assert.assertEquals(0, mMenu.findItemIndex(id));
+ }
+
+ @SmallTest
+ public void testGroupId() {
+ final int groupId = 541;
+ final int item1Index = 1;
+ final int item2Index = 3;
+
+ mMenu.add(0, 0, item1Index - 1, "ignore");
+ final MenuItem item = mMenu.add(groupId, 0, item1Index, "test");
+ mMenu.add(0, 0, item2Index - 1, "ignore");
+ final MenuItem item2 = mMenu.add(groupId, 0, item2Index, "test2");
+
+ Assert.assertEquals(groupId, item.getGroupId());
+ Assert.assertEquals(groupId, item2.getGroupId());
+ Assert.assertEquals(item1Index, mMenu.findGroupIndex(groupId));
+ Assert.assertEquals(item2Index, mMenu.findGroupIndex(groupId, item1Index + 1));
+ }
+
+ @SmallTest
+ public void testGroup() {
+ // This test does the following
+ // 1. Create a grouped item in the menu
+ // 2. Check that findGroupIndex() finds the grouped item.
+ // 3. Check that findGroupIndex() doesn't find a non-existent group.
+
+ final int GROUP_ONE = Menu.FIRST;
+ final int GROUP_TWO = Menu.FIRST + 1;
+
+ mMenu.add(GROUP_ONE, 0, 0, "Menu text");
+ Assert.assertEquals(mMenu.findGroupIndex(GROUP_ONE), 0);
+ Assert.assertEquals(mMenu.findGroupIndex(GROUP_TWO), -1);
+ //TODO: expand this test case to do multiple groups,
+ //adding and removing, hiding and showing, etc.
+ }
+
+ @SmallTest
+ public void testIsShortcutWithAlpha() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'a');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A, 0)));
+ Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_B,
+ makeKeyEvent(KeyEvent.KEYCODE_B, 0)));
+ }
+
+ @SmallTest
+ public void testIsShortcutWithNumeric() throws Exception {
+ mMenu.setQwertyMode(false);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'a');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_2,
+ makeKeyEvent(KeyEvent.KEYCODE_2, 0)));
+ Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A, 0)));
+ }
+
+ @SmallTest
+ public void testIsShortcutWithAlt() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'a');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A,
+ KeyEvent.META_ALT_ON)));
+ Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A,
+ KeyEvent.META_SYM_ON)));
+ }
+
+ @SmallTest
+ public void testIsNotShortcutWithShift() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'a');
+ Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A,
+ KeyEvent.META_SHIFT_ON)));
+ }
+
+ @SmallTest
+ public void testIsNotShortcutWithSym() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'a');
+ Assert.assertFalse(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A,
+ KeyEvent.META_SYM_ON)));
+ }
+
+ @SmallTest
+ public void testIsShortcutWithUpperCaseAlpha() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', 'A');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_A,
+ makeKeyEvent(KeyEvent.KEYCODE_A, 0)));
+ }
+
+ @SmallTest
+ public void testIsShortcutWithBackspace() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', '\b');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_DEL,
+ makeKeyEvent(KeyEvent.KEYCODE_DEL, 0)));
+ }
+
+ @SmallTest
+ public void testIsShortcutWithNewline() throws Exception {
+ mMenu.setQwertyMode(true);
+ mMenu.add(0, 0, 0, "test").setShortcut('2', '\n');
+ Assert.assertTrue(mMenu.isShortcutKey(KeyEvent.KEYCODE_ENTER,
+ makeKeyEvent(KeyEvent.KEYCODE_ENTER, 0)));
+ }
+
+ @SmallTest
+ public void testOrder() {
+ final String a = "a", b = "b", c = "c";
+ final int firstOrder = 7, midOrder = 8, lastOrder = 9;
+
+ mMenu.add(0, 0, lastOrder, c);
+ mMenu.add(0, 0, firstOrder, a);
+ mMenu.add(0, 0, midOrder, b);
+
+ Assert.assertEquals(firstOrder, mMenu.getItem(0).getOrder());
+ Assert.assertEquals(a, mMenu.getItem(0).getTitle());
+ Assert.assertEquals(midOrder, mMenu.getItem(1).getOrder());
+ Assert.assertEquals(b, mMenu.getItem(1).getTitle());
+ Assert.assertEquals(lastOrder, mMenu.getItem(2).getOrder());
+ Assert.assertEquals(c, mMenu.getItem(2).getTitle());
+ }
+
+ @SmallTest
+ public void testTitle() {
+ final String title = "test";
+ final MenuItem stringItem = mMenu.add(title);
+ final MenuItem resItem = mMenu.add(R.string.menu_test);
+
+ Assert.assertEquals(title, stringItem.getTitle());
+ Assert.assertEquals(getContext().getResources().getString(R.string.menu_test), resItem
+ .getTitle());
+ }
+
+ @SmallTest
+ public void testCheckable() {
+ final int groupId = 1;
+ final MenuItem item1 = mMenu.add(groupId, 1, 0, "item1");
+ final MenuItem item2 = mMenu.add(groupId, 2, 0, "item2");
+
+ // Set to exclusive
+ mMenu.setGroupCheckable(groupId, true, true);
+ Assert.assertTrue("Item was not set to checkable", item1.isCheckable());
+ item1.setChecked(true);
+ Assert.assertTrue("Item did not get checked", item1.isChecked());
+ Assert.assertFalse("Item was not unchecked due to exclusive checkable", item2.isChecked());
+ mMenu.findItem(2).setChecked(true);
+ Assert.assertTrue("Item did not get checked", item2.isChecked());
+ Assert.assertFalse("Item was not unchecked due to exclusive checkable", item1.isChecked());
+
+ // Multiple non-exlusive checkable items
+ mMenu.setGroupCheckable(groupId, true, false);
+ Assert.assertTrue("Item was not set to checkable", item1.isCheckable());
+ item1.setChecked(false);
+ Assert.assertFalse("Item did not get unchecked", item1.isChecked());
+ item1.setChecked(true);
+ Assert.assertTrue("Item did not get checked", item1.isChecked());
+ mMenu.findItem(2).setChecked(true);
+ Assert.assertTrue("Item did not get checked", item2.isChecked());
+ Assert.assertTrue("Item was unchecked when it shouldnt have been", item1.isChecked());
+ }
+
+ @SmallTest
+ public void testVisibility() {
+ final MenuItem item1 = mMenu.add(0, 1, 0, "item1");
+ final MenuItem item2 = mMenu.add(0, 2, 0, "item2");
+
+ // Should start as visible
+ Assert.assertTrue("Item did not start as visible", item1.isVisible());
+ Assert.assertTrue("Item did not start as visible", item2.isVisible());
+
+ // Hide
+ item1.setVisible(false);
+ Assert.assertFalse("Item did not become invisible", item1.isVisible());
+ mMenu.findItem(2).setVisible(false);
+ Assert.assertFalse("Item did not become invisible", item2.isVisible());
+ }
+
+ @SmallTest
+ public void testSubMenu() {
+ final SubMenu subMenu = mMenu.addSubMenu(0, 0, 0, "submenu");
+ final MenuItem subMenuItem = subMenu.getItem();
+ final MenuItem item1 = subMenu.add(0, 1, 0, "item1");
+ final MenuItem item2 = subMenu.add(0, 2, 0, "item2");
+
+ // findItem should recurse into submenus
+ Assert.assertEquals(item1, mMenu.findItem(1));
+ Assert.assertEquals(item2, mMenu.findItem(2));
+ }
+
+ @SmallTest
+ public void testRemove() {
+ final int groupId = 1;
+ final MenuItem item1 = mMenu.add(groupId, 1, 0, "item1");
+ final MenuItem item2 = mMenu.add(groupId, 2, 0, "item2");
+ final MenuItem item3 = mMenu.add(groupId, 3, 0, "item3");
+ final MenuItem item4 = mMenu.add(groupId, 4, 0, "item4");
+ final MenuItem item5 = mMenu.add(groupId, 5, 0, "item5");
+ final MenuItem item6 = mMenu.add(0, 6, 0, "item6");
+
+ Assert.assertEquals(item1, mMenu.findItem(1));
+ mMenu.removeItemAt(0);
+ Assert.assertNull(mMenu.findItem(1));
+
+ Assert.assertEquals(item2, mMenu.findItem(2));
+ mMenu.removeItem(2);
+ Assert.assertNull(mMenu.findItem(2));
+
+ Assert.assertEquals(item3, mMenu.findItem(3));
+ Assert.assertEquals(item4, mMenu.findItem(4));
+ Assert.assertEquals(item5, mMenu.findItem(5));
+ mMenu.removeGroup(groupId);
+ Assert.assertNull(mMenu.findItem(3));
+ Assert.assertNull(mMenu.findItem(4));
+ Assert.assertNull(mMenu.findItem(5));
+
+ Assert.assertEquals(item6, mMenu.findItem(6));
+ mMenu.clear();
+ Assert.assertNull(mMenu.findItem(6));
+ }
+
+ private KeyEvent makeKeyEvent(int keyCode, int metaState) {
+ return new KeyEvent(0L, 0L, KeyEvent.ACTION_DOWN, keyCode, 0, metaState);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/Merge.java b/core/tests/coretests/src/android/view/Merge.java
new file mode 100644
index 0000000..bdacd81
--- /dev/null
+++ b/core/tests/coretests/src/android/view/Merge.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.widget.LinearLayout;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+
+/**
+ * Exercise <merge /> tag in XML files.
+ */
+public class Merge extends Activity {
+ private LinearLayout mLayout;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mLayout = new LinearLayout(this);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+ LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);
+
+ setContentView(mLayout);
+ }
+
+ public ViewGroup getLayout() {
+ return mLayout;
+ }
+}
diff --git a/core/tests/coretests/src/android/view/MergeTest.java b/core/tests/coretests/src/android/view/MergeTest.java
new file mode 100644
index 0000000..acfee7e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/MergeTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.Merge;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.ViewGroup;
+
+public class MergeTest extends ActivityInstrumentationTestCase<Merge> {
+ public MergeTest() {
+ super("com.android.frameworks.coretests", Merge.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @MediumTest
+ public void testMerged() throws Exception {
+ final Merge activity = getActivity();
+ final ViewGroup layout = activity.getLayout();
+
+ assertEquals("The layout wasn't merged", 7, layout.getChildCount());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/MutateDrawable.java b/core/tests/coretests/src/android/view/MutateDrawable.java
new file mode 100644
index 0000000..39b5789
--- /dev/null
+++ b/core/tests/coretests/src/android/view/MutateDrawable.java
@@ -0,0 +1,49 @@
+/*
+ * 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.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import com.android.frameworks.coretests.R;
+
+public class MutateDrawable extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout layout = new LinearLayout(this);
+
+ Button ok = new Button(this);
+ ok.setId(R.id.a);
+ ok.setBackgroundDrawable(getResources().getDrawable(
+ R.drawable.sym_now_playing_skip_forward_1));
+
+ Button cancel = new Button(this);
+ cancel.setId(R.id.b);
+ cancel.setBackgroundDrawable(getResources().getDrawable(
+ R.drawable.sym_now_playing_skip_forward_1));
+
+ layout.addView(ok);
+ layout.addView(cancel);
+
+ ok.getBackground().mutate().setAlpha(127);
+
+ setContentView(layout);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/MutateDrawableTest.java b/core/tests/coretests/src/android/view/MutateDrawableTest.java
new file mode 100644
index 0000000..74e011d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/MutateDrawableTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.view;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.MutateDrawable;
+
+public class MutateDrawableTest extends ActivityInstrumentationTestCase2<MutateDrawable> {
+ private View mFirstButton;
+ private View mSecondButton;
+
+ public MutateDrawableTest() {
+ super("com.android.frameworks.coretests", MutateDrawable.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mFirstButton = getActivity().findViewById(com.android.frameworks.coretests.R.id.a);
+ mSecondButton = getActivity().findViewById(com.android.frameworks.coretests.R.id.b);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mFirstButton);
+ assertNotNull(mSecondButton);
+ assertNotSame(mFirstButton.getBackground(), mSecondButton.getBackground());
+ }
+
+ @MediumTest
+ public void testDrawableCanMutate() throws Exception {
+ assertNotSame(mFirstButton.getBackground().getConstantState(),
+ mSecondButton.getBackground().getConstantState());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/PopupWindowVisibility.java b/core/tests/coretests/src/android/view/PopupWindowVisibility.java
new file mode 100644
index 0000000..7eb0468
--- /dev/null
+++ b/core/tests/coretests/src/android/view/PopupWindowVisibility.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.Spinner;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Tests views with popupWindows becoming invisible
+ */
+public class PopupWindowVisibility extends Activity implements OnClickListener {
+
+ private View mFrame;
+ private Button mHide;
+ private Button mShow;
+
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.popup_window_visibility);
+
+ mFrame = findViewById(R.id.frame);
+
+ mHide = (Button) findViewById(R.id.hide);
+ mHide.setOnClickListener(this);
+
+ mShow = (Button) findViewById(R.id.show);
+ mShow.setOnClickListener(this);
+
+ Spinner spinner = (Spinner) findViewById(R.id.spinner);
+ ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_spinner_item, mStrings);
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spinner.setAdapter(spinnerAdapter);
+
+ ArrayAdapter<String> autoAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_dropdown_item_1line, COUNTRIES);
+ AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.auto);
+ textView.setAdapter(autoAdapter);
+ }
+
+
+ public void onClick(View v) {
+ mFrame.setVisibility(v == mHide ? View.INVISIBLE : View.VISIBLE);
+ }
+ private static final String[] mStrings = {
+ "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"
+ };
+
+ static final String[] COUNTRIES = new String[] {
+ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+ "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+ "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+ "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+ "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+ "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory",
+ "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi",
+ "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+ "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+ "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+ "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+ "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+ "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+ "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+ "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+ "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+ "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+ "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+ "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+ "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+ "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+ "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+ "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+ "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+ "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+ "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+ "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+ "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+ "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+ "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+ "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+ "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+ "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+ "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+ "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+ "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+ };
+}
diff --git a/core/tests/coretests/src/android/view/PreDrawListener.java b/core/tests/coretests/src/android/view/PreDrawListener.java
new file mode 100644
index 0000000..981c6c0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/PreDrawListener.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.android.frameworks.coretests.R;
+
+
+/**
+ * Tests views with popupWindows becoming invisible
+ */
+public class PreDrawListener extends Activity implements OnClickListener {
+
+ private MyLinearLayout mFrame;
+
+
+ static public class MyLinearLayout extends LinearLayout implements
+ ViewTreeObserver.OnPreDrawListener {
+
+ public boolean mCancelNextDraw;
+
+ public MyLinearLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyLinearLayout(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ getViewTreeObserver().addOnPreDrawListener(this);
+ }
+
+ public boolean onPreDraw() {
+ if (mCancelNextDraw) {
+ Button b = new Button(this.getContext());
+ b.setText("Hello");
+ addView(b, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.WRAP_CONTENT));
+ mCancelNextDraw = false;
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.pre_draw_listener);
+
+ mFrame = (MyLinearLayout) findViewById(R.id.frame);
+
+ Button mGoButton = (Button) findViewById(R.id.go);
+ mGoButton.setOnClickListener(this);
+ }
+
+
+ public void onClick(View v) {
+ mFrame.mCancelNextDraw = true;
+ mFrame.invalidate();
+ }
+
+
+
+}
diff --git a/core/tests/coretests/src/android/view/RemoteViewsActivity.java b/core/tests/coretests/src/android/view/RemoteViewsActivity.java
new file mode 100644
index 0000000..6f3ba04
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RemoteViewsActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercise RemoteViews -- especially filtering
+ */
+public class RemoteViewsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.remote_view_host);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/RunQueue.java b/core/tests/coretests/src/android/view/RunQueue.java
new file mode 100644
index 0000000..85dd32e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RunQueue.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import com.android.frameworks.coretests.R;
+
+/**
+ * Tests views using post*() and getViewTreeObserver() before onAttachedToWindow().
+ */
+public class RunQueue extends Activity implements ViewTreeObserver.OnGlobalLayoutListener {
+ public boolean runnableRan = false;
+ public boolean runnableCancelled = true;
+ public boolean globalLayout = false;
+ public ViewTreeObserver viewTreeObserver;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ TextView textView = new TextView(this);
+ textView.setText("RunQueue");
+ textView.setId(R.id.simple_view);
+
+ setContentView(textView);
+ final View view = findViewById(R.id.simple_view);
+
+ view.post(new Runnable() {
+ public void run() {
+ runnableRan = true;
+ }
+ });
+
+ final Runnable runnable = new Runnable() {
+ public void run() {
+ runnableCancelled = false;
+ }
+ };
+ view.post(runnable);
+ view.post(runnable);
+ view.post(runnable);
+ view.post(runnable);
+ view.removeCallbacks(runnable);
+
+ viewTreeObserver = view.getViewTreeObserver();
+ viewTreeObserver.addOnGlobalLayoutListener(this);
+ }
+
+ public void onGlobalLayout() {
+ globalLayout = true;
+ }
+}
diff --git a/core/tests/coretests/src/android/view/RunQueueTest.java b/core/tests/coretests/src/android/view/RunQueueTest.java
new file mode 100644
index 0000000..d69860b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RunQueueTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+public class RunQueueTest extends ActivityInstrumentationTestCase<RunQueue> {
+ public RunQueueTest() {
+ super("com.android.frameworks.coretests", RunQueue.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @MediumTest
+ public void testRunnableRan() throws Exception {
+ final RunQueue activity = getActivity();
+ getInstrumentation().waitForIdleSync();
+ assertTrue("The runnable did not run", activity.runnableRan);
+ }
+
+ @MediumTest
+ public void testRunnableCancelled() throws Exception {
+ final RunQueue activity = getActivity();
+ getInstrumentation().waitForIdleSync();
+ assertTrue("The runnable was not cancelled", activity.runnableCancelled);
+ }
+
+ @MediumTest
+ public void testListenerFired() throws Exception {
+ final RunQueue activity = getActivity();
+ getInstrumentation().waitForIdleSync();
+ assertTrue("The global layout listener did not fire", activity.globalLayout);
+ }
+
+ @MediumTest
+ public void testTreeObserverKilled() throws Exception {
+ final RunQueue activity = getActivity();
+ getInstrumentation().waitForIdleSync();
+ assertFalse("The view tree observer is still alive", activity.viewTreeObserver.isAlive());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/SetTagsTest.java b/core/tests/coretests/src/android/view/SetTagsTest.java
new file mode 100644
index 0000000..373dce6
--- /dev/null
+++ b/core/tests/coretests/src/android/view/SetTagsTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.widget.Button;
+
+/**
+ * Exercises {@link android.view.View}'s tags property.
+ */
+public class SetTagsTest extends ActivityInstrumentationTestCase2<Disabled> {
+ private Button mView;
+
+ public SetTagsTest() {
+ super("com.android.frameworks.coretests", Disabled.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mView = (Button) getActivity().findViewById(R.id.disabledButton);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mView);
+ }
+
+ @MediumTest
+ public void testSetTag() throws Exception {
+ mView.setTag("1");
+ }
+
+ @MediumTest
+ public void testGetTag() throws Exception {
+ Object o = new Object();
+ mView.setTag(o);
+
+ final Object stored = mView.getTag();
+ assertNotNull(stored);
+ assertSame("The stored tag is inccorect", o, stored);
+ }
+
+ @MediumTest
+ public void testSetTagWithKey() throws Exception {
+ mView.setTag(R.id.a, "2");
+ }
+
+ @MediumTest
+ public void testGetTagWithKey() throws Exception {
+ Object o = new Object();
+ mView.setTag(R.id.a, o);
+
+ final Object stored = mView.getTag(R.id.a);
+ assertNotNull(stored);
+ assertSame("The stored tag is inccorect", o, stored);
+ }
+
+ @MediumTest
+ public void testSetTagWithFrameworkId() throws Exception {
+ boolean result = false;
+ try {
+ mView.setTag(android.R.id.list, "2");
+ } catch (IllegalArgumentException e) {
+ result = true;
+ }
+ assertTrue("Setting a tag with a framework id did not throw an exception", result);
+ }
+
+ @MediumTest
+ public void testSetTagWithNoPackageId() throws Exception {
+ boolean result = false;
+ try {
+ mView.setTag(0x000000AA, "2");
+ } catch (IllegalArgumentException e) {
+ result = true;
+ }
+ assertTrue("Setting a tag with an id with no package did not throw an exception", result);
+ }
+
+ @MediumTest
+ public void testSetTagInternalWithFrameworkId() throws Exception {
+ mView.setTagInternal(android.R.id.list, "2");
+ }
+
+ @MediumTest
+ public void testSetTagInternalWithApplicationId() throws Exception {
+ boolean result = false;
+ try {
+ mView.setTagInternal(R.id.a, "2");
+ } catch (IllegalArgumentException e) {
+ result = true;
+ }
+ assertTrue("Setting a tag with an id with app package did not throw an exception", result);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/StubbedView.java b/core/tests/coretests/src/android/view/StubbedView.java
new file mode 100644
index 0000000..612095c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/StubbedView.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.view.View;
+
+/**
+ * Exercise <ViewStub /> tag in XML files.
+ */
+public class StubbedView extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.viewstub);
+
+ findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ final View view = findViewById(R.id.viewStub);
+ if (view != null) {
+ view.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupAttributesTest.java b/core/tests/coretests/src/android/view/ViewGroupAttributesTest.java
new file mode 100644
index 0000000..b4ef0e7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupAttributesTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class ViewGroupAttributesTest extends AndroidTestCase {
+
+ private MyViewGroup mViewGroup;
+
+ private static final class MyViewGroup extends ViewGroup {
+
+ public MyViewGroup(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ }
+
+ @Override
+ public boolean isChildrenDrawnWithCacheEnabled() {
+ return super.isChildrenDrawnWithCacheEnabled();
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mViewGroup = new MyViewGroup(getContext());
+ }
+
+ @SmallTest
+ public void testDescendantFocusabilityEnum() {
+ assertEquals("expected ViewGroup.FOCUS_BEFORE_DESCENDANTS to be default",
+ ViewGroup.FOCUS_BEFORE_DESCENDANTS, mViewGroup.getDescendantFocusability());
+
+ // remember some state before we muck with flags
+ final boolean isAnimationCachEnabled = mViewGroup.isAnimationCacheEnabled();
+ final boolean isAlwaysDrawnWithCacheEnabled = mViewGroup.isAlwaysDrawnWithCacheEnabled();
+ final boolean isChildrenDrawnWithCacheEnabled = mViewGroup.isChildrenDrawnWithCacheEnabled();
+
+ mViewGroup.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ assertEquals(ViewGroup.FOCUS_AFTER_DESCENDANTS, mViewGroup.getDescendantFocusability());
+
+ mViewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ assertEquals(ViewGroup.FOCUS_BLOCK_DESCENDANTS, mViewGroup.getDescendantFocusability());
+
+ mViewGroup.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ assertEquals(ViewGroup.FOCUS_BEFORE_DESCENDANTS, mViewGroup.getDescendantFocusability());
+
+ // verify we didn't change something unrelated
+ final String msg = "setDescendantFocusability messed with an unrelated flag";
+ assertEquals(msg, isAnimationCachEnabled, mViewGroup.isAnimationCacheEnabled());
+ assertEquals(msg, isAlwaysDrawnWithCacheEnabled, mViewGroup.isAlwaysDrawnWithCacheEnabled());
+ assertEquals(msg, isChildrenDrawnWithCacheEnabled, mViewGroup.isChildrenDrawnWithCacheEnabled());
+ }
+
+ @SmallTest
+ public void testWrongIntSetForDescendantFocusabilityEnum() {
+ try {
+ mViewGroup.setDescendantFocusability(0);
+ fail("expected setting wrong flag to throw an exception");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupChildren.java b/core/tests/coretests/src/android/view/ViewGroupChildren.java
new file mode 100644
index 0000000..f39720b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupChildren.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.view.View;
+import android.app.Activity;
+
+/**
+ * Exercise ViewGroup's ability to add and remove children.
+ */
+public class ViewGroupChildren extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.viewgroupchildren);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupChildrenTest.java b/core/tests/coretests/src/android/view/ViewGroupChildrenTest.java
new file mode 100644
index 0000000..d1665ef
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupChildrenTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+import android.view.ViewGroupChildren;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Exercises {@link android.view.ViewGroup}'s ability to add/remove children.
+ */
+public class ViewGroupChildrenTest extends ActivityInstrumentationTestCase<ViewGroupChildren> {
+ private ViewGroup mGroup;
+
+ public ViewGroupChildrenTest() {
+ super("com.android.frameworks.coretests", ViewGroupChildren.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final ViewGroupChildren a = getActivity();
+ mGroup = (ViewGroup) a.findViewById(R.id.group);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mGroup);
+ }
+
+ @MediumTest
+ public void testStartsEmpty() throws Exception {
+ assertEquals("A ViewGroup should have no child by default", 0, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testAddChild() throws Exception {
+ View view = createView("1");
+ mGroup.addView(view);
+
+ assertEquals(1, mGroup.getChildCount());
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupContains(mGroup, view);
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testAddChildAtFront() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ for (int i = 0; i < 24; i++) {
+ View view = createView(String.valueOf(i + 1));
+ mGroup.addView(view);
+ }
+
+ View view = createView("X");
+ mGroup.addView(view, 0);
+
+ assertEquals(25, mGroup.getChildCount());
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupContains(mGroup, view);
+ assertSame(view, mGroup.getChildAt(0));
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testAddChildInMiddle() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ for (int i = 0; i < 24; i++) {
+ View view = createView(String.valueOf(i + 1));
+ mGroup.addView(view);
+ }
+
+ View view = createView("X");
+ mGroup.addView(view, 12);
+
+ assertEquals(25, mGroup.getChildCount());
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupContains(mGroup, view);
+ assertSame(view, mGroup.getChildAt(12));
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testAddChildren() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ for (int i = 0; i < 24; i++) {
+ View view = createView(String.valueOf(i + 1));
+ mGroup.addView(view);
+
+ ViewAsserts.assertGroupContains(mGroup, view);
+ }
+ assertEquals(24, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChild() throws Exception {
+ View view = createView("1");
+ mGroup.addView(view);
+
+ ViewAsserts.assertGroupIntegrity(mGroup);
+
+ mGroup.removeView(view);
+
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupNotContains(mGroup, view);
+
+ assertEquals(0, mGroup.getChildCount());
+ assertNull(view.getParent());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildren() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ for (int i = views.length - 1; i >= 0; i--) {
+ mGroup.removeViewAt(i);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupNotContains(mGroup, views[i]);
+ assertNull(views[i].getParent());
+ }
+
+ assertEquals(0, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildrenBulk() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ mGroup.removeViews(6, 7);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ for (int i = 6; i < 13; i++) {
+ ViewAsserts.assertGroupNotContains(mGroup, views[i]);
+ assertNull(views[i].getParent());
+ }
+
+ assertEquals(17, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildrenBulkAtFront() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ mGroup.removeViews(0, 7);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ for (int i = 0; i < 7; i++) {
+ ViewAsserts.assertGroupNotContains(mGroup, views[i]);
+ assertNull(views[i].getParent());
+ }
+
+ assertEquals("8", ((TextView) mGroup.getChildAt(0)).getText());
+ assertEquals(17, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildrenBulkAtEnd() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ mGroup.removeViews(17, 7);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ for (int i = 17; i < 24; i++) {
+ ViewAsserts.assertGroupNotContains(mGroup, views[i]);
+ assertNull(views[i].getParent());
+ }
+ assertEquals("17", ((TextView) mGroup.getChildAt(mGroup.getChildCount() - 1)).getText());
+ assertEquals(17, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildAtFront() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ mGroup.removeViewAt(0);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupNotContains(mGroup, views[0]);
+ assertNull(views[0].getParent());
+
+ assertEquals(views.length - 1, mGroup.getChildCount());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testRemoveChildInMiddle() throws Exception {
+ // 24 should be greater than ViewGroup.ARRAY_CAPACITY_INCREMENT
+ final View[] views = new View[24];
+
+ for (int i = 0; i < views.length; i++) {
+ views[i] = createView(String.valueOf(i + 1));
+ mGroup.addView(views[i]);
+ }
+
+ mGroup.removeViewAt(12);
+ ViewAsserts.assertGroupIntegrity(mGroup);
+ ViewAsserts.assertGroupNotContains(mGroup, views[12]);
+ assertNull(views[12].getParent());
+
+ assertEquals(views.length - 1, mGroup.getChildCount());
+ }
+
+ private TextView createView(String text) {
+ TextView view = new TextView(getActivity());
+ view.setText(text);
+ view.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+ return view;
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewStubTest.java b/core/tests/coretests/src/android/view/ViewStubTest.java
new file mode 100644
index 0000000..ebd52a6
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewStubTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.StubbedView;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.view.ViewStub;
+
+public class ViewStubTest extends ActivityInstrumentationTestCase<StubbedView> {
+ public ViewStubTest() {
+ super("com.android.frameworks.coretests", StubbedView.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @MediumTest
+ public void testStubbed() throws Exception {
+ final StubbedView activity = getActivity();
+
+ final View stub = activity.findViewById(R.id.viewStub);
+ assertNotNull("The ViewStub does not exist", stub);
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testInflated() throws Exception {
+ final StubbedView activity = getActivity();
+
+ final ViewStub stub = (ViewStub) activity.findViewById(R.id.viewStub);
+ final View swapped = stub.inflate();
+
+ assertNotNull("The inflated view is null", swapped);
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testInflatedId() throws Exception {
+ final StubbedView activity = getActivity();
+
+ final ViewStub stub = (ViewStub) activity.findViewById(R.id.viewStubWithId);
+ final View swapped = stub.inflate();
+
+ assertNotNull("The inflated view is null", swapped);
+ assertTrue("The inflated view has no id", swapped.getId() != View.NO_ID);
+ assertTrue("The inflated view has the wrong id", swapped.getId() == R.id.stub_inflated);
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testInflatedLayoutParams() throws Exception {
+ final StubbedView activity = getActivity();
+
+ final ViewStub stub = (ViewStub) activity.findViewById(R.id.viewStubWithId);
+ final View swapped = stub.inflate();
+
+ assertNotNull("The inflated view is null", swapped);
+
+ assertEquals("Both stub and inflated should same width",
+ stub.getLayoutParams().width, swapped.getLayoutParams().width);
+ assertEquals("Both stub and inflated should same height",
+ stub.getLayoutParams().height, swapped.getLayoutParams().height);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/Visibility.java b/core/tests/coretests/src/android/view/Visibility.java
new file mode 100644
index 0000000..97ff252
--- /dev/null
+++ b/core/tests/coretests/src/android/view/Visibility.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.view.View;
+import android.app.Activity;
+
+/**
+ * Exercise View's ability to change their visibility: GONE, INVISIBLE and
+ * VISIBLE.
+ */
+public class Visibility extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.visibility);
+
+ // Find the view whose visibility will change
+ mVictim = findViewById(R.id.victim);
+
+ // Find our buttons
+ Button visibleButton = (Button) findViewById(R.id.vis);
+ Button invisibleButton = (Button) findViewById(R.id.invis);
+ Button goneButton = (Button) findViewById(R.id.gone);
+
+ // Wire each button to a click listener
+ visibleButton.setOnClickListener(mVisibleListener);
+ invisibleButton.setOnClickListener(mInvisibleListener);
+ goneButton.setOnClickListener(mGoneListener);
+ }
+
+
+ View.OnClickListener mVisibleListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.VISIBLE);
+ }
+ };
+
+ View.OnClickListener mInvisibleListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.INVISIBLE);
+ }
+ };
+
+ View.OnClickListener mGoneListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.GONE);
+ }
+ };
+
+ private View mVictim;
+}
diff --git a/core/tests/coretests/src/android/view/VisibilityCallback.java b/core/tests/coretests/src/android/view/VisibilityCallback.java
new file mode 100644
index 0000000..7290a62
--- /dev/null
+++ b/core/tests/coretests/src/android/view/VisibilityCallback.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.TextView;
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.view.View;
+import android.app.Activity;
+
+/**
+ * Exercise View's ability to change their visibility: GONE, INVISIBLE and
+ * VISIBLE.
+ */
+public class VisibilityCallback extends Activity {
+ private static final boolean DEBUG = false;
+
+ private MonitoredTextView mVictim;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.visibility_callback);
+
+ // Find the view whose visibility will change
+ mVictim = (MonitoredTextView)findViewById(R.id.victim);
+
+ // Find our buttons
+ Button visibleButton = (Button) findViewById(R.id.vis);
+ Button invisibleButton = (Button) findViewById(R.id.invis);
+ Button goneButton = (Button) findViewById(R.id.gone);
+
+ // Wire each button to a click listener
+ visibleButton.setOnClickListener(mVisibleListener);
+ invisibleButton.setOnClickListener(mInvisibleListener);
+ goneButton.setOnClickListener(mGoneListener);
+ }
+
+
+ View.OnClickListener mVisibleListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.VISIBLE);
+ }
+ };
+
+ View.OnClickListener mInvisibleListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.INVISIBLE);
+ }
+ };
+
+ View.OnClickListener mGoneListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ mVictim.setVisibility(View.GONE);
+ }
+ };
+
+ public static class MonitoredTextView extends TextView {
+ private View mLastVisChangedView;
+ private int mLastChangedVisibility;
+
+ public MonitoredTextView(Context context) {
+ super(context);
+ }
+
+ public MonitoredTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MonitoredTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public View getLastVisChangedView() {
+ return mLastVisChangedView;
+ }
+
+ public int getLastChangedVisibility() {
+ return mLastChangedVisibility;
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ mLastVisChangedView = changedView;
+ mLastChangedVisibility = visibility;
+
+ if (DEBUG) {
+ Log.d("viewVis", "visibility: " + visibility);
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/VisibilityCallbackTest.java b/core/tests/coretests/src/android/view/VisibilityCallbackTest.java
new file mode 100644
index 0000000..ec956d2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/VisibilityCallbackTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises {@link android.view.View}'s ability to change visibility between
+ * GONE, VISIBLE and INVISIBLE.
+ */
+public class VisibilityCallbackTest extends ActivityInstrumentationTestCase2<VisibilityCallback> {
+ private TextView mRefUp;
+ private TextView mRefDown;
+ private VisibilityCallback.MonitoredTextView mVictim;
+ private ViewGroup mParent;
+ private Button mVisible;
+ private Button mInvisible;
+ private Button mGone;
+
+ public VisibilityCallbackTest() {
+ super("com.android.frameworks.coretests", VisibilityCallback.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final VisibilityCallback a = getActivity();
+ mRefUp = (TextView) a.findViewById(R.id.refUp);
+ mRefDown = (TextView) a.findViewById(R.id.refDown);
+ mVictim = (VisibilityCallback.MonitoredTextView) a.findViewById(R.id.victim);
+ mParent = (ViewGroup) a.findViewById(R.id.parent);
+ mVisible = (Button) a.findViewById(R.id.vis);
+ mInvisible = (Button) a.findViewById(R.id.invis);
+ mGone = (Button) a.findViewById(R.id.gone);
+
+ mVictim.post(new Runnable() {
+ public void run() {
+ mVictim.setVisibility(View.INVISIBLE);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mRefUp);
+ assertNotNull(mRefDown);
+ assertNotNull(mVictim);
+ assertNotNull(mVisible);
+ assertNotNull(mInvisible);
+ assertNotNull(mGone);
+
+ assertTrue(mVisible.hasFocus());
+ assertEquals(View.INVISIBLE, mVictim.getVisibility());
+ assertEquals(View.VISIBLE, mParent.getVisibility());
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testDirect() throws Exception {
+ mVictim.setVisibility(View.VISIBLE);
+ assertEquals(View.VISIBLE, mVictim.getLastChangedVisibility());
+ assertEquals(mVictim, mVictim.getLastVisChangedView());
+
+ mVictim.setVisibility(View.INVISIBLE);
+ assertEquals(View.INVISIBLE, mVictim.getLastChangedVisibility());
+ assertEquals(mVictim, mVictim.getLastVisChangedView());
+
+ mVictim.setVisibility(View.GONE);
+ assertEquals(View.GONE, mVictim.getLastChangedVisibility());
+ assertEquals(mVictim, mVictim.getLastVisChangedView());
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testChild() throws Exception {
+ mParent.setVisibility(View.INVISIBLE);
+ assertEquals(View.INVISIBLE, mVictim.getLastChangedVisibility());
+ assertEquals(mParent, mVictim.getLastVisChangedView());
+
+ mParent.setVisibility(View.GONE);
+ assertEquals(View.GONE, mVictim.getLastChangedVisibility());
+ assertEquals(mParent, mVictim.getLastVisChangedView());
+
+ mParent.setVisibility(View.VISIBLE);
+ assertEquals(View.VISIBLE, mVictim.getLastChangedVisibility());
+ assertEquals(mParent, mVictim.getLastVisChangedView());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/VisibilityTest.java b/core/tests/coretests/src/android/view/VisibilityTest.java
new file mode 100644
index 0000000..17d2e3e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/VisibilityTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.Visibility;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.widget.TextView;
+import android.view.View;
+import static android.view.KeyEvent.*;
+
+/**
+ * Exercises {@link android.view.View}'s ability to change visibility between
+ * GONE, VISIBLE and INVISIBLE.
+ */
+public class VisibilityTest extends ActivityInstrumentationTestCase<Visibility> {
+ private TextView mRefUp;
+ private TextView mRefDown;
+ private TextView mVictim;
+ private Button mVisible;
+ private Button mInvisible;
+ private Button mGone;
+
+ public VisibilityTest() {
+ super("com.android.frameworks.coretests", Visibility.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final Visibility a = getActivity();
+ mRefUp = (TextView) a.findViewById(R.id.refUp);
+ mRefDown = (TextView) a.findViewById(R.id.refDown);
+ mVictim = (TextView) a.findViewById(R.id.victim);
+ mVisible = (Button) a.findViewById(R.id.vis);
+ mInvisible = (Button) a.findViewById(R.id.invis);
+ mGone = (Button) a.findViewById(R.id.gone);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mRefUp);
+ assertNotNull(mRefDown);
+ assertNotNull(mVictim);
+ assertNotNull(mVisible);
+ assertNotNull(mInvisible);
+ assertNotNull(mGone);
+
+ assertTrue(mVisible.hasFocus());
+ }
+
+ @MediumTest
+ public void testVisibleToInvisible() throws Exception {
+ sendKeys("DPAD_RIGHT");
+ assertTrue(mInvisible.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.INVISIBLE, mVictim.getVisibility());
+
+ int newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+ }
+
+ @MediumTest
+ public void testVisibleToGone() throws Exception {
+ //sendKeys("2*DPAD_RIGHT");
+ sendRepeatedKeys(2, KEYCODE_DPAD_RIGHT);
+ assertTrue(mGone.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.GONE, mVictim.getVisibility());
+
+ int refDownTop = mRefDown.getTop();
+ assertEquals(oldTop, refDownTop);
+ }
+
+ @LargeTest
+ public void testGoneToVisible() throws Exception {
+ sendKeys("2*DPAD_RIGHT");
+ assertTrue(mGone.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.GONE, mVictim.getVisibility());
+
+ int refDownTop = mRefDown.getTop();
+ assertEquals(oldTop, refDownTop);
+
+ sendKeys("2*DPAD_LEFT DPAD_CENTER");
+ assertEquals(View.VISIBLE, mVictim.getVisibility());
+
+ int newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+ }
+
+ @MediumTest
+ public void testGoneToInvisible() throws Exception {
+ sendKeys("2*DPAD_RIGHT");
+ assertTrue(mGone.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.GONE, mVictim.getVisibility());
+
+ int refDownTop = mRefDown.getTop();
+ assertEquals(oldTop, refDownTop);
+
+ sendKeys(KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER);
+ assertEquals(View.INVISIBLE, mVictim.getVisibility());
+
+ int newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+ }
+
+ @MediumTest
+ public void testInvisibleToVisible() throws Exception {
+ sendKeys("DPAD_RIGHT");
+ assertTrue(mInvisible.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.INVISIBLE, mVictim.getVisibility());
+
+ int newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+
+ sendKeys("DPAD_LEFT DPAD_CENTER");
+ assertEquals(View.VISIBLE, mVictim.getVisibility());
+
+ newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+ }
+
+ @MediumTest
+ public void testInvisibleToGone() throws Exception {
+ sendKeys("DPAD_RIGHT");
+ assertTrue(mInvisible.hasFocus());
+
+ int oldTop = mVictim.getTop();
+
+ sendKeys("DPAD_CENTER");
+ assertEquals(View.INVISIBLE, mVictim.getVisibility());
+
+ int newTop = mVictim.getTop();
+ assertEquals(oldTop, newTop);
+
+ sendKeys("DPAD_RIGHT DPAD_CENTER");
+ assertEquals(View.GONE, mVictim.getVisibility());
+
+ int refDownTop = mRefDown.getTop();
+ assertEquals(oldTop, refDownTop);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ZeroSized.java b/core/tests/coretests/src/android/view/ZeroSized.java
new file mode 100644
index 0000000..f2a6b3e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ZeroSized.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.frameworks.coretests.R;
+
+import android.os.Bundle;
+import android.app.Activity;
+
+/**
+ * This activity contains Views with various widths and heights. The goal is to exercise the
+ * drawing cache when width is null, height is null or both.
+ */
+public class ZeroSized extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.zero_sized);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ZeroSizedTest.java b/core/tests/coretests/src/android/view/ZeroSizedTest.java
new file mode 100644
index 0000000..193fc98
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ZeroSizedTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.ZeroSized;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.graphics.Bitmap;
+
+/**
+ * Builds the drawing cache of Views of various dimension. The assumption is that
+ * a View with a 0-sized dimension (width or height) will always have a null
+ * drawing cache.
+ */
+public class ZeroSizedTest extends ActivityInstrumentationTestCase<ZeroSized> {
+ private View mWithDimension;
+ private View mWithNoWdith;
+ private View mWithNoHeight;
+ private View mWithNoDimension;
+
+ public ZeroSizedTest() {
+ super("com.android.frameworks.coretests", ZeroSized.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final ZeroSized activity = getActivity();
+ mWithDimension = activity.findViewById(R.id.dimension);
+ mWithNoWdith = activity.findViewById(R.id.noWidth);
+ mWithNoHeight = activity.findViewById(R.id.noHeight);
+ mWithNoDimension = activity.findViewById(R.id.noDimension);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mWithDimension);
+ assertNotNull(mWithNoWdith);
+ assertNotNull(mWithNoHeight);
+ assertNotNull(mWithNoDimension);
+ }
+
+ @MediumTest
+ public void testDrawingCacheWithDimension() throws Exception {
+ assertTrue(mWithDimension.getWidth() > 0);
+ assertTrue(mWithDimension.getHeight() > 0);
+ assertNotNull(createCacheForView(mWithDimension));
+ }
+
+ @MediumTest
+ public void testDrawingCacheWithNoWidth() throws Exception {
+ assertTrue(mWithNoWdith.getWidth() == 0);
+ assertTrue(mWithNoWdith.getHeight() > 0);
+ assertNull(createCacheForView(mWithNoWdith));
+ }
+
+ @MediumTest
+ public void testDrawingCacheWithNoHeight() throws Exception {
+ assertTrue(mWithNoHeight.getWidth() > 0);
+ assertTrue(mWithNoHeight.getHeight() == 0);
+ assertNull(createCacheForView(mWithNoHeight));
+ }
+
+ @MediumTest
+ public void testDrawingCacheWithNoDimension() throws Exception {
+ assertTrue(mWithNoDimension.getWidth() == 0);
+ assertTrue(mWithNoDimension.getHeight() == 0);
+ assertNull(createCacheForView(mWithNoDimension));
+ }
+
+ private Bitmap createCacheForView(final View view) {
+ final Bitmap[] cache = new Bitmap[1];
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ view.setDrawingCacheEnabled(true);
+ view.invalidate();
+ view.buildDrawingCache();
+ cache[0] = view.getDrawingCache();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ return cache[0];
+ }
+}
diff --git a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java
new file mode 100644
index 0000000..df8d836
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java
@@ -0,0 +1,81 @@
+/**
+ * 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.view.accessibility;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.accessibility.AccessibilityEvent;
+
+import junit.framework.TestCase;
+
+/**
+ * This class exercises the caching and recycling of {@link AccessibilityEvent}s.
+ */
+public class RecycleAccessibilityEventTest extends TestCase {
+
+ private static final String CLASS_NAME = "foo.bar.baz.Test";
+ private static final String PACKAGE_NAME = "foo.bar.baz";
+ private static final String TEXT = "Some stuff";
+
+ private static final String CONTENT_DESCRIPTION = "Content description";
+ private static final int ITEM_COUNT = 10;
+ private static final int CURRENT_ITEM_INDEX = 1;
+
+ private static final int FROM_INDEX = 1;
+ private static final int ADDED_COUNT = 2;
+ private static final int REMOVED_COUNT = 1;
+
+ /**
+ * If an {@link AccessibilityEvent} is marshaled/unmarshaled correctly
+ */
+ @MediumTest
+ public void testAccessibilityEventViewTextChangedType() {
+ AccessibilityEvent first =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ assertNotNull(first);
+
+ first.setClassName(CLASS_NAME);
+ first.setPackageName(PACKAGE_NAME);
+ first.getText().add(TEXT);
+ first.setFromIndex(FROM_INDEX);
+ first.setAddedCount(ADDED_COUNT);
+ first.setRemovedCount(REMOVED_COUNT);
+ first.setChecked(true);
+ first.setContentDescription(CONTENT_DESCRIPTION);
+ first.setItemCount(ITEM_COUNT);
+ first.setCurrentItemIndex(CURRENT_ITEM_INDEX);
+ first.setEnabled(true);
+ first.setPassword(true);
+
+ first.recycle();
+
+ assertNotNull(first);
+ assertNull(first.getClassName());
+ assertNull(first.getPackageName());
+ assertEquals(0, first.getText().size());
+ assertFalse(first.isChecked());
+ assertNull(first.getContentDescription());
+ assertEquals(0, first.getItemCount());
+ assertEquals(AccessibilityEvent.INVALID_POSITION, first.getCurrentItemIndex());
+ assertFalse(first.isEnabled());
+ assertFalse(first.isPassword());
+ assertEquals(0, first.getFromIndex());
+ assertEquals(0, first.getAddedCount());
+ assertEquals(0, first.getRemovedCount());
+
+ // get another event from the pool (this must be the recycled first)
+ AccessibilityEvent second = AccessibilityEvent.obtain();
+ assertEquals(first, second);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayout.java b/core/tests/coretests/src/android/view/menu/MenuLayout.java
new file mode 100644
index 0000000..356c948
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuLayout.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.view.menu;
+
+import android.view.menu.MenuScenario.Params;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.widget.Button;
+
+public class MenuLayout extends MenuScenario {
+ private static final String LONG_TITLE = "Really really really really really really really really really really long title";
+ private static final String SHORT_TITLE = "Item";
+
+ private Button mButton;
+
+ @Override
+ protected void onInitParams(Params params) {
+ super.onInitParams(params);
+ params
+ .setNumItems(2)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+
+ /*
+ * This activity is meant to try a bunch of different menu layouts. So,
+ * we recreate the menu every time it is prepared.
+ */
+ menu.clear();
+ onCreateOptionsMenu(menu);
+
+ return true;
+ }
+
+ public Button getButton() {
+ return mButton;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mButton = new Button(this);
+ setContentView(mButton);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscape.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscape.java
new file mode 100644
index 0000000..662cb6a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscape.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.view.menu;
+
+/**
+ * An activity (inherits from MenuLayout) that shows in landscape.
+ */
+public class MenuLayoutLandscape extends MenuLayout {
+
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
new file mode 100644
index 0000000..d9bf860
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.view.menu;
+
+import android.util.KeyUtils;
+import com.android.internal.view.menu.IconMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.content.pm.ActivityInfo;
+import android.test.ActivityInstrumentationTestCase;
+
+public class MenuLayoutLandscapeTest extends ActivityInstrumentationTestCase<MenuLayoutLandscape> {
+ private static final String LONG_TITLE = "Really really really really really really really really really really long title";
+ private static final String SHORT_TITLE = "Item";
+
+ private MenuLayout mActivity;
+
+ public MenuLayoutLandscapeTest() {
+ super("com.android.frameworks.coretests", MenuLayoutLandscape.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ }
+
+ private void toggleMenu() {
+ getInstrumentation().waitForIdleSync();
+ KeyUtils.tapMenuKey(this);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * Asserts the layout of the menu.
+ *
+ * @param expectedLayout The number of parameters is the number of rows, and
+ * each parameter is how many items on that row (the first
+ * parameter is the top-most row).
+ */
+ private void assertLayout(Integer... expectedLayout) {
+ toggleMenu();
+
+ IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
+ int[] layout = iconMenuView.getLayout();
+ int layoutNumRows = iconMenuView.getLayoutNumRows();
+
+ int expectedRows = expectedLayout.length;
+ assertEquals("Row mismatch", expectedRows, layoutNumRows);
+
+ for (int row = 0; row < expectedRows; row++) {
+ assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
+ layout[row]);
+ }
+ }
+
+ public void test1ShortItem() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(1)
+ .setItemTitle(0, SHORT_TITLE));
+ assertLayout(1);
+ }
+
+ public void test1LongItem() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(1)
+ .setItemTitle(0, LONG_TITLE));
+ assertLayout(1);
+ }
+
+ public void test2LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(2)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE));
+ assertLayout(1, 1);
+ }
+
+ public void test2ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(2)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE));
+ assertLayout(2);
+ }
+
+ public void test3ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ assertLayout(3);
+ }
+
+ public void test3VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ assertLayout(1, 2);
+ }
+
+ public void test3VarietyItems2() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ assertLayout(1, 2);
+ }
+
+ public void test4LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE));
+ assertLayout(2, 2);
+ }
+
+ public void test4ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE));
+ assertLayout(4);
+ }
+
+ public void test4VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE));
+ assertLayout(2, 2);
+ }
+
+ public void test5ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, SHORT_TITLE));
+ assertLayout(5);
+ }
+
+ public void test5LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, LONG_TITLE));
+ assertLayout(2, 3);
+ }
+
+ public void test5VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, LONG_TITLE));
+ assertLayout(2, 3);
+ }
+
+ public void test6LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, LONG_TITLE)
+ .setItemTitle(5, LONG_TITLE));
+ assertLayout(3, 3);
+ }
+
+ public void test6ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, SHORT_TITLE)
+ .setItemTitle(5, SHORT_TITLE));
+ assertLayout(6);
+ }
+
+ public void test6VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, SHORT_TITLE)
+ .setItemTitle(5, SHORT_TITLE));
+ assertLayout(3, 3);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortrait.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortrait.java
new file mode 100644
index 0000000..5e94bd7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortrait.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.view.menu;
+
+/**
+ * An activity (inherits from MenuLayout) that shows in portrait.
+ */
+public class MenuLayoutPortrait extends MenuLayout {
+
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
new file mode 100644
index 0000000..ad746b0
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -0,0 +1,231 @@
+/**
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package android.view.menu;
+
+import android.util.KeyUtils;
+import com.android.internal.view.menu.IconMenuView;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.content.pm.ActivityInfo;
+import android.test.ActivityInstrumentationTestCase;
+
+public class MenuLayoutPortraitTest extends ActivityInstrumentationTestCase<MenuLayoutPortrait> {
+ private static final String LONG_TITLE = "Really really really really really really really really really really long title";
+ private static final String SHORT_TITLE = "Item";
+
+ private MenuLayout mActivity;
+
+ public MenuLayoutPortraitTest() {
+ super("com.android.frameworks.coretests", MenuLayoutPortrait.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ }
+
+ private void toggleMenu() {
+ getInstrumentation().waitForIdleSync();
+ KeyUtils.tapMenuKey(this);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * Asserts the layout of the menu.
+ *
+ * @param expectedLayout The number of parameters is the number of rows, and
+ * each parameter is how many items on that row.
+ */
+ private void assertLayout(Integer... expectedLayout) {
+ toggleMenu();
+
+ IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
+ int[] layout = iconMenuView.getLayout();
+ int layoutNumRows = iconMenuView.getLayoutNumRows();
+
+ int expectedRows = expectedLayout.length;
+ assertEquals("Row mismatch", expectedRows, layoutNumRows);
+
+ for (int row = 0; row < expectedRows; row++) {
+ assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
+ layout[row]);
+ }
+ }
+
+ public void test1ShortItem() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(1)
+ .setItemTitle(0, SHORT_TITLE));
+ assertLayout(1);
+ }
+
+ public void test1LongItem() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(1)
+ .setItemTitle(0, LONG_TITLE));
+ assertLayout(1);
+ }
+
+ public void test2LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(2)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE));
+ assertLayout(1, 1);
+ }
+
+ public void test2ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(2)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE));
+ assertLayout(2);
+ }
+
+ public void test3ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ assertLayout(3);
+ }
+
+ public void test3VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ // We maintain the order added, so there must be 3 rows
+ assertLayout(1, 1, 1);
+ }
+
+ public void test3VarietyItems2() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(3)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE));
+ // The long can fit in first row, and two shorts in second
+ assertLayout(1, 2);
+ }
+
+ public void test4LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE));
+ assertLayout(1, 1, 2);
+ }
+
+ public void test4ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE));
+ assertLayout(2, 2);
+ }
+
+ public void test4VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(4)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE));
+ assertLayout(1, 1, 2);
+ }
+
+ public void test5ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, SHORT_TITLE));
+ assertLayout(2, 3);
+ }
+
+ public void test5LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, LONG_TITLE));
+ assertLayout(1, 2, 2);
+ }
+
+ public void test5VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(5)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, LONG_TITLE));
+ assertLayout(1, 2, 2);
+ }
+
+ public void test6LongItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, LONG_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, LONG_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, LONG_TITLE)
+ .setItemTitle(5, LONG_TITLE));
+ assertLayout(2, 2, 2);
+ }
+
+ public void test6ShortItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, SHORT_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, SHORT_TITLE)
+ .setItemTitle(4, SHORT_TITLE)
+ .setItemTitle(5, SHORT_TITLE));
+ assertLayout(3, 3);
+ }
+
+ public void test6VarietyItems() {
+ mActivity.setParams(new MenuScenario.Params()
+ .setNumItems(6)
+ .setItemTitle(0, SHORT_TITLE)
+ .setItemTitle(1, LONG_TITLE)
+ .setItemTitle(2, SHORT_TITLE)
+ .setItemTitle(3, LONG_TITLE)
+ .setItemTitle(4, SHORT_TITLE)
+ .setItemTitle(5, SHORT_TITLE));
+ assertLayout(2, 2, 2);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuScenario.java b/core/tests/coretests/src/android/view/menu/MenuScenario.java
new file mode 100644
index 0000000..b0b8802
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuScenario.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.menu;
+
+import android.util.ListScenario;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * Utility base class for creating various Menu scenarios. Configurable by the
+ * number of menu items. Used @link {@link ListScenario} as a reference.
+ */
+public class MenuScenario extends Activity implements MenuItem.OnMenuItemClickListener {
+ private Params mParams = new Params();
+ private Menu mMenu;
+ private MenuItem[] mItems;
+ private boolean[] mWasItemClicked;
+ private MenuAdapter[] mMenuAdapters = new MenuAdapter[MenuBuilder.NUM_TYPES];
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ dispatchInitParams();
+ }
+
+ private void dispatchInitParams() {
+ onInitParams(mParams);
+ onParamsChanged();
+ }
+
+ public void setParams(Params params) {
+ mParams = params;
+ onParamsChanged();
+ }
+
+ public void onParamsChanged() {
+ mItems = new MenuItem[mParams.numItems];
+ mWasItemClicked = new boolean[mParams.numItems];
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Safe to hold on to
+ mMenu = menu;
+
+ if (!mParams.shouldShowMenu) return false;
+
+ MenuItem item;
+ for (int i = 0; i < mParams.numItems; i++) {
+ if ((item = onAddMenuItem(menu, i)) == null) {
+ // Add a default item for this position if the subclasses
+ // haven't
+ CharSequence givenTitle = mParams.itemTitles.get(i);
+ item = menu.add(0, 0, 0, (givenTitle != null) ? givenTitle : ("Item " + i));
+ }
+
+ if (item != null) {
+ mItems[i] = item;
+
+ if (mParams.listenForClicks) {
+ item.setOnMenuItemClickListener(this);
+ }
+ }
+
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // Safe to hold on to
+ mMenu = menu;
+
+ return mParams.shouldShowMenu;
+ }
+
+ /**
+ * Override this to add an item to the menu.
+ *
+ * @param itemPosition The position of the item to add (only for your
+ * reference).
+ * @return The item that was added to the menu, or null if nothing was
+ * added.
+ */
+ protected MenuItem onAddMenuItem(Menu menu, int itemPosition) {
+ return null;
+ }
+
+ /**
+ * Override this to set the parameters for the scenario. Call through to super first.
+ *
+ * @param params
+ */
+ protected void onInitParams(Params params) {
+ }
+
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ public boolean onMenuItemClick(MenuItem item) {
+ final int position = findItemPosition(item);
+ if (position < 0) return false;
+
+ mWasItemClicked[position] = true;
+
+ return true;
+ }
+
+ public boolean wasItemClicked(int position) {
+ return mWasItemClicked[position];
+ }
+
+ /**
+ * Finds the position for a given Item.
+ *
+ * @param item The item to find.
+ * @return The position, or -1 if not found.
+ */
+ public int findItemPosition(MenuItem item) {
+ // Could create reverse mapping, but optimizations aren't important (yet :P)
+ for (int i = 0; i < mParams.numItems; i++) {
+ if (mItems[i] == item) return i;
+ }
+
+ return -1;
+ }
+
+ /**
+ * @see MenuBuilder#getMenuAdapter(int)
+ */
+ public MenuAdapter getMenuAdapter(int menuType) {
+ if (mMenuAdapters[menuType] == null) {
+ mMenuAdapters[menuType] = ((MenuBuilder) mMenu).getMenuAdapter(menuType);
+ }
+
+ return mMenuAdapters[menuType];
+ }
+
+ /**
+ * Gets a menu view. Call this after you're sure it has been shown,
+ * otherwise it may not have the proper layout_* attributes set.
+ *
+ * @param menuType The type of menu.
+ * @return The MenuView for that type.
+ */
+ public View getMenuView(int menuType) {
+ return ((MenuBuilder) mMenu).getMenuView(menuType, null);
+ }
+
+ /**
+ * Gets the menu item view for a given position.
+ *
+ * @param menuType The type of menu.
+ * @param position The position of the item.
+ * @return The menu item view for the given item in the given menu type.
+ */
+ public View getItemView(int menuType, int position) {
+ return getMenuAdapter(menuType).getView(position, null, null);
+ }
+
+ public static class Params {
+ // Using as data structure, so no m prefix
+ private boolean shouldShowMenu = true;
+ private int numItems = 10;
+ private boolean listenForClicks = true;
+ private SparseArray<CharSequence> itemTitles = new SparseArray<CharSequence>();
+
+ public Params setShouldShowMenu(boolean shouldShowMenu) {
+ this.shouldShowMenu = shouldShowMenu;
+ return this;
+ }
+
+ public Params setNumItems(int numItems) {
+ this.numItems = numItems;
+ return this;
+ }
+
+ public Params setListenForClicks(boolean listenForClicks) {
+ this.listenForClicks = listenForClicks;
+ return this;
+ }
+
+ public Params setItemTitle(int itemPos, CharSequence title) {
+ itemTitles.put(itemPos, title);
+ return this;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuWith1Item.java b/core/tests/coretests/src/android/view/menu/MenuWith1Item.java
new file mode 100644
index 0000000..293c44b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuWith1Item.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.menu;
+
+import android.os.Bundle;
+import android.widget.Button;
+
+public class MenuWith1Item extends MenuScenario {
+
+ private Button mButton;
+
+ @Override
+ protected void onInitParams(Params params) {
+ super.onInitParams(params);
+
+ params.setNumItems(1);
+ }
+
+ public Button getButton() {
+ return mButton;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mButton = new Button(this);
+ setContentView(mButton);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
new file mode 100644
index 0000000..4e71053
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.menu;
+
+import android.view.menu.MenuWith1Item;
+import android.util.KeyUtils;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.view.KeyEvent;
+import android.view.View;
+
+public class MenuWith1ItemTest extends ActivityInstrumentationTestCase<MenuWith1Item> {
+ private MenuWith1Item mActivity;
+
+ public MenuWith1ItemTest() {
+ super("com.android.frameworks.coretests", MenuWith1Item.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertFalse(mActivity.getButton().isInTouchMode());
+ }
+
+ @MediumTest
+ public void testItemClick() {
+
+ // Open menu, click on an item
+ KeyUtils.tapMenuKey(this);
+ getInstrumentation().waitForIdleSync();
+ assertFalse("Item seems to have been clicked before we clicked on it", mActivity
+ .wasItemClicked(0));
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ assertTrue("Item doesn't seem to have registered our click", mActivity.wasItemClicked(0));
+ }
+
+ @LargeTest
+ public void testTouchModeTransfersRemovesFocus() throws Exception {
+ // open menu, move around to give it focus
+ sendKeys(KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_DPAD_LEFT);
+ final View menuItem = mActivity.getItemView(MenuBuilder.TYPE_ICON, 0);
+ assertTrue("menuItem.isFocused()", menuItem.isFocused());
+
+ // close the menu
+ sendKeys(KeyEvent.KEYCODE_MENU);
+ Thread.sleep(500);
+
+ // touch the screen
+ TouchUtils.clickView(this, mActivity.getButton());
+ assertTrue("should be in touch mode after touching button",
+ mActivity.getButton().isInTouchMode());
+
+ // open the menu, menu item shouldn't be focused, because we are not
+ // in touch mode
+ sendKeys(KeyEvent.KEYCODE_MENU);
+ assertTrue("menuItem.isInTouchMode()", menuItem.isInTouchMode());
+ assertFalse("menuItem.isFocused()", menuItem.isFocused());
+ }
+}
diff --git a/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java b/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java
new file mode 100644
index 0000000..7504449
--- /dev/null
+++ b/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.webkit;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.webkit.CacheManager.CacheResult;
+import android.webkit.PluginData;
+import android.webkit.UrlInterceptHandler;
+
+import java.util.LinkedList;
+import java.util.Map;
+
+public class UrlInterceptRegistryTest extends AndroidTestCase {
+
+ /**
+ * To run these tests: $ mmm
+ * frameworks/base/tests/CoreTests/android && adb remount && adb
+ * sync $ adb shell am instrument -w -e class \
+ * android.webkit.UrlInterceptRegistryTest \
+ * android.core/android.test.InstrumentationTestRunner
+ */
+
+ private static class MockUrlInterceptHandler implements UrlInterceptHandler {
+ private PluginData mData;
+ private String mUrl;
+
+ public MockUrlInterceptHandler(PluginData data, String url) {
+ mData = data;
+ mUrl = url;
+ }
+
+ public CacheResult service(String url, Map<String, String> headers) {
+ return null;
+ }
+
+ public PluginData getPluginData(String url,
+ Map<String,
+ String> headers) {
+ if (mUrl.equals(url)) {
+ return mData;
+ }
+
+ return null;
+ }
+ }
+
+ public void testGetPluginData() {
+ PluginData data = new PluginData(null, 0 , null, 200);
+ String url = new String("url1");
+ MockUrlInterceptHandler handler1 =
+ new MockUrlInterceptHandler(data, url);
+
+ data = new PluginData(null, 0 , null, 404);
+ url = new String("url2");
+ MockUrlInterceptHandler handler2 =
+ new MockUrlInterceptHandler(data, url);
+
+ assertTrue(UrlInterceptRegistry.registerHandler(handler1));
+ assertTrue(UrlInterceptRegistry.registerHandler(handler2));
+
+ data = UrlInterceptRegistry.getPluginData("url1", null);
+ assertTrue(data != null);
+ assertTrue(data.getStatusCode() == 200);
+
+ data = UrlInterceptRegistry.getPluginData("url2", null);
+ assertTrue(data != null);
+ assertTrue(data.getStatusCode() == 404);
+
+ assertTrue(UrlInterceptRegistry.unregisterHandler(handler1));
+ assertTrue(UrlInterceptRegistry.unregisterHandler(handler2));
+
+ }
+}
diff --git a/core/tests/coretests/src/android/webkit/WebkitTest.java b/core/tests/coretests/src/android/webkit/WebkitTest.java
new file mode 100644
index 0000000..17b4088
--- /dev/null
+++ b/core/tests/coretests/src/android/webkit/WebkitTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.webkit;
+
+import android.test.AndroidTestCase;
+import android.text.format.DateFormat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.webkit.DateSorter;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class WebkitTest extends AndroidTestCase {
+
+ private static final String LOGTAG = WebkitTest.class.getName();
+
+ @MediumTest
+ public void testDateSorter() throws Exception {
+ /**
+ * Note: check the logging output manually to test
+ * nothing automated yet, besides object creation
+ */
+ DateSorter dateSorter = new DateSorter(mContext);
+ Date date = new Date();
+
+ for (int i = 0; i < DateSorter.DAY_COUNT; i++) {
+ Log.i(LOGTAG, "Boundary " + i + " " + dateSorter.getBoundary(i));
+ Log.i(LOGTAG, "Label " + i + " " + dateSorter.getLabel(i));
+ }
+
+ Calendar c = Calendar.getInstance();
+ long time = c.getTimeInMillis();
+ int index;
+ Log.i(LOGTAG, "now: " + dateSorter.getIndex(time));
+ for (int i = 0; i < 20; i++) {
+ time -= 8 * 60 * 60 * 1000; // 8 hours
+ date.setTime(time);
+ c.setTime(date);
+ index = dateSorter.getIndex(time);
+ Log.i(LOGTAG, "time: " + DateFormat.format("yyyy/MM/dd kk:mm:ss", c).toString() +
+ " " + index + " " + dateSorter.getLabel(index));
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/AutoCompleteTextViewCallbacks.java b/core/tests/coretests/src/android/widget/AutoCompleteTextViewCallbacks.java
new file mode 100644
index 0000000..8e73b52
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AutoCompleteTextViewCallbacks.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.FlakyTest;
+
+// TODO: tests fail intermittently. Add back MediumTest annotation when fixed
+public class AutoCompleteTextViewCallbacks
+ extends ActivityInstrumentationTestCase2<AutoCompleteTextViewSimple> {
+
+ private static final int WAIT_TIME = 200;
+
+ public AutoCompleteTextViewCallbacks() {
+ super("com.android.frameworks.coretests", AutoCompleteTextViewSimple.class);
+ }
+
+ /** Test that the initial popup of the suggestions does not select anything.
+ */
+ @FlakyTest(tolerance=3)
+ public void testPopupNoSelection() throws Exception {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+ instrumentation.waitForIdleSync();
+ // give UI time to settle
+ Thread.sleep(WAIT_TIME);
+
+ // now check for selection callbacks. Nothing should be clicked or selected.
+ assertFalse("onItemClick should not be called", theActivity.mItemClickCalled);
+ assertFalse("onItemSelected should not be called", theActivity.mItemSelectedCalled);
+
+ // arguably, this should be "false", because we aren't deselecting - we shouldn't
+ // really be calling it. But it's not the end of the world, and we might wind up
+ // breaking something if we change this.
+ //assertTrue("onNothingSelected should be called", theActivity.mNothingSelectedCalled);
+ }
+
+ /** Test that arrow-down into the popup calls the onSelected callback. */
+ @FlakyTest(tolerance=3)
+ public void testPopupEnterSelection() throws Exception {
+ final AutoCompleteTextViewSimple theActivity = getActivity();
+ AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ textView.post(new Runnable() {
+ public void run() {
+ // prepare to move down into the popup
+ theActivity.resetItemListeners();
+ }
+ });
+
+ sendKeys("DPAD_DOWN");
+ instrumentation.waitForIdleSync();
+ // give UI time to settle
+ Thread.sleep(WAIT_TIME);
+
+ // now check for selection callbacks.
+ assertFalse("onItemClick should not be called", theActivity.mItemClickCalled);
+ assertTrue("onItemSelected should be called", theActivity.mItemSelectedCalled);
+ assertEquals("onItemSelected position", 0, theActivity.mItemSelectedPosition);
+ assertFalse("onNothingSelected should not be called", theActivity.mNothingSelectedCalled);
+
+ textView.post(new Runnable() {
+ public void run() {
+ // try one more time - should move from 0 to 1
+ theActivity.resetItemListeners();
+ }
+ });
+
+ sendKeys("DPAD_DOWN");
+ instrumentation.waitForIdleSync();
+ // give UI time to settle
+ Thread.sleep(WAIT_TIME);
+
+ // now check for selection callbacks.
+ assertFalse("onItemClick should not be called", theActivity.mItemClickCalled);
+ assertTrue("onItemSelected should be called", theActivity.mItemSelectedCalled);
+ assertEquals("onItemSelected position", 1, theActivity.mItemSelectedPosition);
+ assertFalse("onNothingSelected should not be called", theActivity.mNothingSelectedCalled);
+ }
+
+ /** Test that arrow-up out of the popup calls the onNothingSelected callback */
+ @FlakyTest(tolerance=3)
+ public void testPopupLeaveSelection() {
+ final AutoCompleteTextViewSimple theActivity = getActivity();
+ AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+ instrumentation.waitForIdleSync();
+
+ // move down into the popup
+ sendKeys("DPAD_DOWN");
+ instrumentation.waitForIdleSync();
+
+ textView.post(new Runnable() {
+ public void run() {
+ // prepare to move down into the popup
+ theActivity.resetItemListeners();
+ }
+ });
+
+ sendKeys("DPAD_UP");
+ instrumentation.waitForIdleSync();
+
+ // now check for selection callbacks.
+ assertFalse("onItemClick should not be called", theActivity.mItemClickCalled);
+ assertFalse("onItemSelected should not be called", theActivity.mItemSelectedCalled);
+ assertTrue("onNothingSelected should be called", theActivity.mNothingSelectedCalled);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/AutoCompleteTextViewPopup.java b/core/tests/coretests/src/android/widget/AutoCompleteTextViewPopup.java
new file mode 100644
index 0000000..ee0abae
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AutoCompleteTextViewPopup.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * A collection of tests on aspects of the AutoCompleteTextView's popup
+ *
+ * TODO: tests fail intermittently. Add back MediumTest annotation when fixed
+ */
+public class AutoCompleteTextViewPopup
+ extends ActivityInstrumentationTestCase2<AutoCompleteTextViewSimple> {
+
+ // ms to sleep when checking for intermittent UI state
+ private static final int SLEEP_TIME = 50;
+ // number of times to poll when checking expected UI state
+ // total wait time will be LOOP_AMOUNT * SLEEP_TIME
+ private static final int LOOP_AMOUNT = 10;
+
+
+ public AutoCompleteTextViewPopup() {
+ super("com.android.frameworks.coretests", AutoCompleteTextViewSimple.class);
+ }
+
+ /** Test that we can move the selection and it responds as expected */
+ @FlakyTest(tolerance=3)
+ public void testPopupSetListSelection() throws Throwable {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ final AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ // No initial selection
+ waitAssertListSelection(textView, ListView.INVALID_POSITION);
+
+ // set and check
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setListSelection(0);
+ }
+ });
+ instrumentation.waitForIdleSync();
+ waitAssertListSelection("set selection to (0)", textView, 0);
+
+ // Use movement to cross-check the movement
+ sendKeys("DPAD_DOWN");
+ waitAssertListSelection("move selection to (1)", textView, 1);
+
+ // TODO: FlakyTest repeat runs will not currently call setUp, clear state
+ clearText(textView);
+ }
+
+ /** Test that we can look at the selection as we move around */
+ @FlakyTest(tolerance=3)
+ public void testPopupGetListSelection() throws Throwable {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ final AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ // No initial selection
+ waitAssertListSelection(textView, ListView.INVALID_POSITION);
+
+ // check for selection position as expected
+ sendKeys("DPAD_DOWN");
+ waitAssertListSelection("move selection to (0)", textView, 0);
+
+ // Repeat for one more movement
+ sendKeys("DPAD_DOWN");
+ waitAssertListSelection("move selection to (1)", textView, 1);
+
+ // TODO: FlakyTest repeat runs will not currently call setUp, clear state
+ clearText(textView);
+ }
+
+ /** Test that we can clear the selection */
+ @FlakyTest(tolerance=3)
+ public void testPopupClearListSelection() throws Throwable {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ final AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ // No initial selection
+ waitAssertListSelection(textView, ListView.INVALID_POSITION);
+
+ // check for selection position as expected
+ sendKeys("DPAD_DOWN");
+ waitAssertListSelection(textView, 0);
+
+ // clear it
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.clearListSelection();
+ }
+ });
+ instrumentation.waitForIdleSync();
+ waitAssertListSelection("setListSelection(ListView.INVALID_POSITION)", textView,
+ ListView.INVALID_POSITION);
+
+ // TODO: FlakyTest repeat runs will not currently call setUp, clear state
+ clearText(textView);
+ }
+
+ /** Make sure we handle an empty adapter properly */
+ @FlakyTest(tolerance=3)
+ public void testPopupNavigateNoAdapter() throws Throwable {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ final AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ // No initial selection
+ waitAssertListSelection(textView, ListView.INVALID_POSITION);
+
+ // check for selection position as expected
+ sendKeys("DPAD_DOWN");
+ waitAssertListSelection(textView, 0);
+
+ // Now get rid of the adapter
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setAdapter((ArrayAdapter<?>) null);
+ }
+ });
+ instrumentation.waitForIdleSync();
+
+ // now try moving "down" - nothing should happen since there's no longer an adapter
+ sendKeys("DPAD_DOWN");
+
+ // TODO: FlakyTest repeat runs will not currently call setUp, clear state
+ clearText(textView);
+ }
+
+ /** Test the show/hide behavior of the drop-down. */
+ @FlakyTest(tolerance=3)
+ public void testPopupShow() throws Throwable {
+ AutoCompleteTextViewSimple theActivity = getActivity();
+ final AutoCompleteTextView textView = theActivity.getTextView();
+ final Instrumentation instrumentation = getInstrumentation();
+
+ // Drop-down should not be showing when no text has been entered
+ assertFalse("isPopupShowing() on start", textView.isPopupShowing());
+
+ // focus and type
+ textView.requestFocus();
+ instrumentation.waitForIdleSync();
+ sendKeys("A");
+
+ // Drop-down should now be visible
+ waitAssertPopupShowState("isPopupShowing() after typing", textView, true);
+
+ // Clear the text
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setText("");
+ }
+ });
+ instrumentation.waitForIdleSync();
+
+ // Drop-down should be hidden when text is cleared
+ waitAssertPopupShowState("isPopupShowing() after text cleared", textView, false);
+
+ // Set the text, without filtering
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setText("a", false);
+ }
+ });
+ instrumentation.waitForIdleSync();
+
+ // Drop-down should still be hidden
+ waitAssertPopupShowState("isPopupShowing() after setText(\"a\", false)", textView, false);
+
+ // Set the text, now with filtering
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setText("a");
+ }
+ });
+ instrumentation.waitForIdleSync();
+
+ // Drop-down should show up after setText() with filtering
+ waitAssertPopupShowState("isPopupShowing() after text set", textView, true);
+
+ // TODO: FlakyTest repeat runs will not currently call setUp, clear state
+ clearText(textView);
+ }
+
+ private void waitAssertPopupShowState(String message, AutoCompleteTextView textView,
+ boolean expected) throws InterruptedException {
+ for (int i = 0; i < LOOP_AMOUNT; i++) {
+ if (textView.isPopupShowing() == expected) {
+ return;
+ }
+ Thread.sleep(SLEEP_TIME);
+ }
+ assertEquals(message, expected, textView.isPopupShowing());
+ }
+
+ private void waitAssertListSelection(AutoCompleteTextView textView, int expected)
+ throws Exception {
+ waitAssertListSelection("getListSelection()", textView, expected);
+ }
+
+ private void waitAssertListSelection(String message, AutoCompleteTextView textView,
+ int expected) throws Exception {
+ int currentSelection = ListView.INVALID_POSITION;
+ for (int i = 0; i < LOOP_AMOUNT; i++) {
+ currentSelection = textView.getListSelection();
+ if (expected == currentSelection) {
+ return;
+ }
+ Thread.sleep(SLEEP_TIME);
+ }
+ assertEquals(message, expected, textView.getListSelection());
+ }
+
+ private void clearText(final AutoCompleteTextView textView) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ textView.setText("");
+ }
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/AutoCompleteTextViewSimple.java b/core/tests/coretests/src/android/widget/AutoCompleteTextViewSimple.java
new file mode 100644
index 0000000..f6cec26
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AutoCompleteTextViewSimple.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
+
+public class AutoCompleteTextViewSimple extends Activity
+ implements OnItemClickListener, OnItemSelectedListener {
+
+ private final String LOG_TAG = "AutoCompleteTextViewSimple";
+
+ private AutoCompleteTextView mTextView;
+
+ /** These are cleared by resetItemListeners(), and set by the callback listeners */
+ public boolean mItemClickCalled;
+ public int mItemClickPosition;
+ public boolean mItemSelectedCalled;
+ public int mItemSelectedPosition;
+ public boolean mNothingSelectedCalled;
+
+ @Override
+ protected void onCreate(Bundle icicle)
+ {
+ // Be sure to call the super class.
+ super.onCreate(icicle);
+
+ // setup layout & views
+ setContentView(R.layout.autocompletetextview_simple);
+ mTextView = (AutoCompleteTextView) findViewById(R.id.autocompletetextview1);
+
+ // configure callbacks used for monitoring
+ mTextView.setOnItemClickListener(this);
+ mTextView.setOnItemSelectedListener(this);
+ resetItemListeners();
+
+ setStringAdapter(5, "a");
+ }
+
+ /**
+ * @return The AutoCompleteTextView used in this test activity.
+ */
+ public AutoCompleteTextView getTextView() {
+ return mTextView;
+ }
+
+ /**
+ * Set the autocomplete data to an adapter containing 0..n strings with a consistent prefix.
+ */
+ public void setStringAdapter(int numSuggestions, String prefix) {
+ // generate the string array
+ String[] strings = new String[numSuggestions];
+ for (int i = 0; i < numSuggestions; ++i) {
+ strings[i] = prefix + String.valueOf(i);
+ }
+
+ // install it with an adapter
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_dropdown_item_1line, strings);
+ mTextView.setAdapter(adapter);
+ }
+
+ /**
+ * For monitoring OnItemClickListener & OnItemSelectedListener
+ *
+ * An alternative here would be to provide a set of pass-through callbacks
+ */
+ public void resetItemListeners() {
+ mItemClickCalled = false;
+ mItemClickPosition = -1;
+ mItemSelectedCalled = false;
+ mItemSelectedPosition = -1;
+ mNothingSelectedCalled = false;
+ }
+
+ /**
+ * Implements OnItemClickListener
+ */
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Log.d(LOG_TAG, "onItemClick() position " + position);
+ mItemClickCalled = true;
+ mItemClickPosition = position;
+ }
+
+ /**
+ * Implements OnItemSelectedListener
+ */
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ Log.d(LOG_TAG, "onItemSelected() position " + position);
+ mItemSelectedCalled = true;
+ mItemSelectedPosition = position;
+ }
+
+ /**
+ * Implements OnItemSelectedListener
+ */
+ public void onNothingSelected(AdapterView<?> parent) {
+ Log.d(LOG_TAG, "onNothingSelected()");
+ mNothingSelectedCalled = true;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/LabelView.java b/core/tests/coretests/src/android/widget/LabelView.java
new file mode 100644
index 0000000..4661c01
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/LabelView.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+
+/**
+ * Example of how to write a custom subclass of View. LabelView
+ * is used to draw simple text views. Note that it does not handle
+ * styled text or right-to-left writing systems.
+ *
+ */
+public class LabelView extends View {
+ /**
+ * Constructor. This version is only needed if you will be instantiating
+ * the object manually (not from a layout XML file).
+ * @param context the application environment
+ */
+ public LabelView(Context context) {
+ super(context);
+ initLabelView();
+ }
+
+ /**
+ * Construct object, initializing with any attributes we understand from a
+ * layout file. These attributes are defined in
+ * SDK/assets/res/any/classes.xml.
+ *
+ * @see android.view.View#View(android.content.Context, android.util.AttributeSet)
+ public LabelView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initLabelView();
+
+ Resources.StyledAttributes a = context.obtainStyledAttributes(attrs,
+ R.styleable.LabelView);
+
+ CharSequence s = a.getString(R.styleable.LabelView_text);
+ if (s != null) {
+ setText(s.toString());
+ }
+
+ ColorStateList textColor = a.getColorList(R.styleable.
+ LabelView_textColor);
+ if (textColor != null) {
+ setTextColor(textColor.getDefaultColor(0));
+ }
+
+ int textSize = a.getInt(R.styleable.LabelView_textSize, 0);
+ if (textSize > 0) {
+ setTextSize(textSize);
+ }
+
+ a.recycle();
+ }
+
+ */
+ private void initLabelView() {
+ mTextPaint = new Paint();
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setTextSize(16);
+ mTextPaint.setColor(0xFF000000);
+
+ mPaddingLeft = 3;
+ mPaddingTop = 3;
+ mPaddingRight = 3;
+ mPaddingBottom = 3;
+ }
+
+ /**
+ * Sets the text to display in this label
+ * @param text The text to display. This will be drawn as one line.
+ */
+ public void setText(String text) {
+ mText = text;
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Sets the text size for this label
+ * @param size Font size
+ */
+ public void setTextSize(int size) {
+ mTextPaint.setTextSize(size);
+ requestLayout();
+ invalidate();
+ }
+
+ /**
+ * Sets the text color for this label
+ * @param color ARGB value for the text
+ */
+ public void setTextColor(int color) {
+ mTextPaint.setColor(color);
+ invalidate();
+ }
+
+
+ /**
+ * @see android.view.View#measure(int, int)
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(measureWidth(widthMeasureSpec),
+ measureHeight(heightMeasureSpec));
+ }
+
+ /**
+ * Determines the width of this view
+ * @param measureSpec A measureSpec packed into an int
+ * @return The width of the view, honoring constraints from measureSpec
+ */
+ private int measureWidth(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.EXACTLY) {
+ // We were told how big to be
+ result = specSize;
+ } else {
+ // Measure the text
+ result = (int) mTextPaint.measureText(mText) + mPaddingLeft
+ + mPaddingRight;
+ if (specMode == MeasureSpec.AT_MOST) {
+ // Respect AT_MOST value if that was what is called for by measureSpec
+ result = Math.min(result, specSize);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Determines the height of this view
+ * @param measureSpec A measureSpec packed into an int
+ * @return The height of the view, honoring constraints from measureSpec
+ */
+ private int measureHeight(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ mAscent = (int) mTextPaint.ascent();
+ if (specMode == MeasureSpec.EXACTLY) {
+ // We were told how big to be
+ result = specSize;
+ } else {
+ // Measure the text (beware: ascent is a negative number)
+ result = (int) (-mAscent + mTextPaint.descent()) + mPaddingTop
+ + mPaddingBottom;
+ if (specMode == MeasureSpec.AT_MOST) {
+ // Respect AT_MOST value if that was what is called for by measureSpec
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Render the text
+ *
+ * @see android.view.View#onDraw(android.graphics.Canvas)
+ */
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawText(mText, mPaddingLeft, mPaddingTop - mAscent, mTextPaint);
+ }
+
+ private Paint mTextPaint;
+ private String mText;
+ private int mAscent;
+}
diff --git a/core/tests/coretests/src/android/widget/ListViewTest.java b/core/tests/coretests/src/android/widget/ListViewTest.java
new file mode 100644
index 0000000..94b19f0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ListViewTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.google.android.collect.Lists;
+
+import junit.framework.Assert;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.InstrumentationTestCase;
+import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.List;
+
+public class ListViewTest extends InstrumentationTestCase {
+
+ /**
+ * If a view in a ListView requests a layout it should be remeasured.
+ */
+ @MediumTest
+ public void testRequestLayout() throws Exception {
+ MockContext context = new MockContext2();
+ ListView listView = new ListView(context);
+ List<String> items = Lists.newArrayList("hello");
+ Adapter<String> adapter = new Adapter<String>(context, 0, items);
+ listView.setAdapter(adapter);
+
+ int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
+
+ adapter.notifyDataSetChanged();
+ listView.measure(measureSpec, measureSpec);
+ listView.layout(0, 0, 100, 100);
+
+ MockView childView = (MockView) listView.getChildAt(0);
+
+ childView.requestLayout();
+ childView.onMeasureCalled = false;
+ listView.measure(measureSpec, measureSpec);
+ listView.layout(0, 0, 100, 100);
+ Assert.assertTrue(childView.onMeasureCalled);
+ }
+
+ /**
+ * The list view should handle the disappearance of the only selected item, even when that item
+ * was selected before its disappearance.
+ *
+ */
+ @MediumTest
+ public void testNoSelectableItems() throws Exception {
+ MockContext context = new MockContext2();
+ ListView listView = new ListView(context);
+ // We use a header as the unselectable item to remain after the selectable one is removed.
+ listView.addHeaderView(new View(context), null, false);
+ List<String> items = Lists.newArrayList("hello");
+ Adapter<String> adapter = new Adapter<String>(context, 0, items);
+ listView.setAdapter(adapter);
+
+ listView.setSelection(1);
+
+ int measureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
+
+ adapter.notifyDataSetChanged();
+ listView.measure(measureSpec, measureSpec);
+ listView.layout(0, 0, 100, 100);
+
+ items.remove(0);
+
+ adapter.notifyDataSetChanged();
+ listView.measure(measureSpec, measureSpec);
+ listView.layout(0, 0, 100, 100);
+ }
+
+ private class MockContext2 extends MockContext {
+
+ @Override
+ public Resources getResources() {
+ return getInstrumentation().getTargetContext().getResources();
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return getInstrumentation().getTargetContext().getTheme();
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
+ return getInstrumentation().getTargetContext().getSystemService(name);
+ }
+ return super.getSystemService(name);
+ }
+ }
+
+ private class MockView extends View {
+
+ public boolean onMeasureCalled = false;
+
+ public MockView(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ onMeasureCalled = true;
+ }
+ }
+
+ private class Adapter<T> extends ArrayAdapter<T> {
+
+ public Adapter(Context context, int resource, List<T> objects) {
+ super(context, resource, objects);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ return new MockView(getContext());
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/RadioGroupActivity.java b/core/tests/coretests/src/android/widget/RadioGroupActivity.java
new file mode 100644
index 0000000..c87aa3a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RadioGroupActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.widget;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class RadioGroupActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.radiogroup_checkedchild);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java b/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java
new file mode 100644
index 0000000..855caae
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.test.TouchUtils;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+
+/**
+ * Exercises {@link android.widget.RadioGroup}'s check feature.
+ */
+public class RadioGroupPreCheckedTest extends ActivityInstrumentationTestCase2<RadioGroupActivity> {
+ public RadioGroupPreCheckedTest() {
+ super("com.android.frameworks.coretests", RadioGroupActivity.class);
+ }
+
+ @LargeTest
+ public void testRadioButtonPreChecked() throws Exception {
+ final RadioGroupActivity activity = getActivity();
+
+ RadioButton radio = (RadioButton) activity.findViewById(R.id.value_one);
+ assertTrue("The first radio button should be checked", radio.isChecked());
+
+ RadioGroup group = (RadioGroup) activity.findViewById(R.id.group);
+ assertEquals("The first radio button should be checked", R.id.value_one,
+ group.getCheckedRadioButtonId());
+ }
+
+ @LargeTest
+ public void testRadioButtonChangePreChecked() throws Exception {
+ final RadioGroupActivity activity = getActivity();
+
+ RadioButton radio = (RadioButton) activity.findViewById(R.id.value_two);
+ TouchUtils.clickView(this, radio);
+
+ RadioButton old = (RadioButton) activity.findViewById(R.id.value_one);
+
+ assertFalse("The first radio button should not be checked", old.isChecked());
+ assertTrue("The second radio button should be checked", radio.isChecked());
+
+ RadioGroup group = (RadioGroup) activity.findViewById(R.id.group);
+ assertEquals("The second radio button should be checked", R.id.value_two,
+ group.getCheckedRadioButtonId());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/SimpleCursorAdapterTest.java b/core/tests/coretests/src/android/widget/SimpleCursorAdapterTest.java
new file mode 100644
index 0000000..7726f02
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/SimpleCursorAdapterTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.android.common.ArrayListCursor;
+import com.google.android.collect.Lists;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+/**
+ * This is a series of tests of basic API contracts for SimpleCursorAdapter. It is
+ * incomplete and can use work.
+ *
+ * NOTE: This contract holds for underlying cursor types too and these should
+ * be extracted into a set of tests that can be run on any descendant of CursorAdapter.
+ */
+public class SimpleCursorAdapterTest extends AndroidTestCase {
+
+ String[] mFrom;
+ int[] mTo;
+ int mLayout;
+ Context mContext;
+
+ ArrayList<ArrayList> mData2x2;
+ Cursor mCursor2x2;
+
+ /**
+ * Set up basic columns and cursor for the tests
+ */
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // all the pieces needed for the various tests
+ mFrom = new String[]{"Column1", "Column2"};
+ mTo = new int[]{com.android.internal.R.id.text1, com.android.internal.R.id.text2};
+ mLayout = com.android.internal.R.layout.simple_list_item_2;
+ mContext = getContext();
+
+ // raw data for building a basic test cursor
+ mData2x2 = createTestList(2, 2);
+ mCursor2x2 = new ArrayListCursor(mFrom, mData2x2);
+ }
+
+ /**
+ * Borrowed from CursorWindowTest.java
+ */
+ private ArrayList<ArrayList> createTestList(int rows, int cols) {
+ ArrayList<ArrayList> list = Lists.newArrayList();
+ Random generator = new Random();
+
+ for (int i = 0; i < rows; i++) {
+ ArrayList<Integer> col = Lists.newArrayList();
+ list.add(col);
+ for (int j = 0; j < cols; j++) {
+ // generate random number
+ Integer r = generator.nextInt();
+ col.add(r);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Test creating with a live cursor
+ */
+ @SmallTest
+ public void testCreateLive() {
+ SimpleCursorAdapter ca = new SimpleCursorAdapter(mContext, mLayout, mCursor2x2, mFrom, mTo);
+
+ // Now see if we can pull 2 rows from the adapter
+ assertEquals(2, ca.getCount());
+ }
+
+ /**
+ * Test creating with a null cursor
+ */
+ @SmallTest
+ public void testCreateNull() {
+ SimpleCursorAdapter ca = new SimpleCursorAdapter(mContext, mLayout, null, mFrom, mTo);
+
+ // The adapter should report zero rows
+ assertEquals(0, ca.getCount());
+ }
+
+ /**
+ * Test changeCursor() with live cursor
+ */
+ @SmallTest
+ public void testChangeCursorLive() {
+ SimpleCursorAdapter ca = new SimpleCursorAdapter(mContext, mLayout, mCursor2x2, mFrom, mTo);
+
+ // Now see if we can pull 2 rows from the adapter
+ assertEquals(2, ca.getCount());
+
+ // now put in a different cursor (5 rows)
+ ArrayList<ArrayList> data2 = createTestList(5, 2);
+ Cursor c2 = new ArrayListCursor(mFrom, data2);
+ ca.changeCursor(c2);
+
+ // Now see if we can pull 5 rows from the adapter
+ assertEquals(5, ca.getCount());
+ }
+
+ /**
+ * Test changeCursor() with null cursor
+ */
+ @SmallTest
+ public void testChangeCursorNull() {
+ SimpleCursorAdapter ca = new SimpleCursorAdapter(mContext, mLayout, mCursor2x2, mFrom, mTo);
+
+ // Now see if we can pull 2 rows from the adapter
+ assertEquals(2, ca.getCount());
+
+ // now put in null
+ ca.changeCursor(null);
+
+ // The adapter should report zero rows
+ assertEquals(0, ca.getCount());
+ }
+
+ /**
+ * Test changeCursor() with differing column layout. This confirms that the Adapter can
+ * deal with cursors that have the same essential data (as defined by the original mFrom
+ * array) but it's OK if the physical structure of the cursor changes (columns rearranged).
+ */
+ @SmallTest
+ public void testChangeCursorColumns() {
+ TestSimpleCursorAdapter ca = new TestSimpleCursorAdapter(mContext, mLayout, mCursor2x2,
+ mFrom, mTo);
+
+ // check columns of original - mFrom and mTo should line up
+ int[] columns = ca.getConvertedFrom();
+ assertEquals(columns[0], 0);
+ assertEquals(columns[1], 1);
+
+ // Now make a new cursor with similar data but rearrange the columns
+ String[] swappedFrom = new String[]{"Column2", "Column1"};
+ Cursor c2 = new ArrayListCursor(swappedFrom, mData2x2);
+ ca.changeCursor(c2);
+ assertEquals(2, ca.getCount());
+
+ // check columns to see if rearrangement tracked (should be swapped now)
+ columns = ca.getConvertedFrom();
+ assertEquals(columns[0], 1);
+ assertEquals(columns[1], 0);
+ }
+
+ /**
+ * Test that you can safely construct with a null cursor *and* null to/from arrays.
+ * This is new functionality added in 12/2008.
+ */
+ @SmallTest
+ public void testNullConstructor() {
+ SimpleCursorAdapter ca = new SimpleCursorAdapter(mContext, mLayout, null, null, null);
+ assertEquals(0, ca.getCount());
+ }
+
+ /**
+ * Test going from a null cursor to a non-null cursor *and* setting the to/from arrays
+ * This is new functionality added in 12/2008.
+ */
+ @SmallTest
+ public void testChangeNullToMapped() {
+ TestSimpleCursorAdapter ca = new TestSimpleCursorAdapter(mContext, mLayout, null, null, null);
+ assertEquals(0, ca.getCount());
+
+ ca.changeCursorAndColumns(mCursor2x2, mFrom, mTo);
+ assertEquals(2, ca.getCount());
+
+ // check columns of original - mFrom and mTo should line up
+ int[] columns = ca.getConvertedFrom();
+ assertEquals(2, columns.length);
+ assertEquals(0, columns[0]);
+ assertEquals(1, columns[1]);
+ int[] viewIds = ca.getTo();
+ assertEquals(2, viewIds.length);
+ assertEquals(com.android.internal.R.id.text1, viewIds[0]);
+ assertEquals(com.android.internal.R.id.text2, viewIds[1]);
+ }
+
+ /**
+ * Test going from one mapping to a different mapping
+ * This is new functionality added in 12/2008.
+ */
+ @SmallTest
+ public void testChangeMapping() {
+ TestSimpleCursorAdapter ca = new TestSimpleCursorAdapter(mContext, mLayout, mCursor2x2,
+ mFrom, mTo);
+ assertEquals(2, ca.getCount());
+
+ // Now create a new configuration with same cursor and just one column mapped
+ String[] singleFrom = new String[]{"Column1"};
+ int[] singleTo = new int[]{com.android.internal.R.id.text1};
+ ca.changeCursorAndColumns(mCursor2x2, singleFrom, singleTo);
+
+ // And examine the results, make sure they're still consistent
+ int[] columns = ca.getConvertedFrom();
+ assertEquals(1, columns.length);
+ assertEquals(0, columns[0]);
+ int[] viewIds = ca.getTo();
+ assertEquals(1, viewIds.length);
+ assertEquals(com.android.internal.R.id.text1, viewIds[0]);
+
+ // And again, same cursor, different map
+ singleFrom = new String[]{"Column2"};
+ singleTo = new int[]{com.android.internal.R.id.text2};
+ ca.changeCursorAndColumns(mCursor2x2, singleFrom, singleTo);
+
+ // And examine the results, make sure they're still consistent
+ columns = ca.getConvertedFrom();
+ assertEquals(1, columns.length);
+ assertEquals(1, columns[0]);
+ viewIds = ca.getTo();
+ assertEquals(1, viewIds.length);
+ assertEquals(com.android.internal.R.id.text2, viewIds[0]);
+ }
+
+ /**
+ * This is simply a way to sneak a look at the protected mFrom() array. A more API-
+ * friendly way to do this would be to mock out a View and a ViewBinder and exercise
+ * it via those seams.
+ */
+ private static class TestSimpleCursorAdapter extends SimpleCursorAdapter {
+
+ public TestSimpleCursorAdapter(Context context, int layout, Cursor c,
+ String[] from, int[] to) {
+ super(context, layout, c, from, to);
+ }
+
+ int[] getConvertedFrom() {
+ return mFrom;
+ }
+
+ int[] getTo() {
+ return mTo;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
new file mode 100644
index 0000000..c25df7c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannedString;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class TextViewPerformanceTest extends AndroidTestCase {
+
+ private String mString = "The quick brown fox";
+ private Canvas mCanvas;
+ private PerformanceTextView mTextView;
+ private Paint mPaint;
+ private PerformanceLabelView mLabelView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Bitmap mBitmap = Bitmap.createBitmap(320, 240, Bitmap.Config.RGB_565);
+ mCanvas = new Canvas(mBitmap);
+
+ ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(320, 240);
+
+ mLabelView = new PerformanceLabelView(mContext);
+ mLabelView.setText(mString);
+ mLabelView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+ mLabelView.mySetFrame(320, 240);
+ mLabelView.setLayoutParams(p);
+ mLabelView.myDraw(mCanvas);
+
+ mPaint = new Paint();
+ mCanvas.save();
+ mTextView = new PerformanceTextView(mContext);
+ mTextView.setLayoutParams(p);
+ mTextView.setText(mString);
+ mTextView.mySetFrame(320, 240);
+ mTextView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+ }
+
+ @MediumTest
+ public void testDrawTextViewLine() throws Exception {
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ }
+
+ @SmallTest
+ public void testSpan() throws Exception {
+ CharSequence charSeq = new SpannedString(mString);
+ mTextView.setText(charSeq);
+
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ mTextView.myDraw(mCanvas);
+ }
+
+ @SmallTest
+ public void testCanvasDrawText() throws Exception {
+ mCanvas.drawText(mString, 30, 30, mPaint);
+ }
+
+ @SmallTest
+ public void testLabelViewDraw() throws Exception {
+ mLabelView.myDraw(mCanvas);
+ }
+
+ private class PerformanceTextView extends TextView {
+ public PerformanceTextView(Context context) {
+ super(context);
+ }
+
+ final void myDraw(Canvas c) {
+ super.onDraw(c);
+ }
+
+ final void mySetFrame(int w, int h) {
+ super.setFrame(0, 0, w, h);
+ }
+ }
+
+ private class PerformanceLabelView extends LabelView {
+ public PerformanceLabelView(Context context) {
+ super(context);
+ }
+
+ final void myDraw(Canvas c) {
+ super.onDraw(c);
+ }
+
+ final void mySetFrame(int w, int h) {
+ super.setFrame(0, 0, w, h);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
new file mode 100644
index 0000000..d8d145c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.GetChars;
+import android.widget.TextView;
+
+/**
+ * TextViewTest tests {@link TextView}.
+ */
+public class TextViewTest extends AndroidTestCase {
+
+ @SmallTest
+ public void testArray() throws Exception {
+ TextView tv = new TextView(mContext);
+
+ char[] c = new char[] { 'H', 'e', 'l', 'l', 'o', ' ',
+ 'W', 'o', 'r', 'l', 'd', '!' };
+
+ tv.setText(c, 1, 4);
+ CharSequence oldText = tv.getText();
+
+ tv.setText(c, 4, 5);
+ CharSequence newText = tv.getText();
+
+ assertTrue(newText == oldText);
+
+ assertEquals(5, newText.length());
+ assertEquals('o', newText.charAt(0));
+ assertEquals("o Wor", newText.toString());
+
+ assertEquals(" Wo", newText.subSequence(1, 4));
+
+ char[] c2 = new char[7];
+ ((GetChars) newText).getChars(1, 4, c2, 2);
+ assertEquals('\0', c2[1]);
+ assertEquals(' ', c2[2]);
+ assertEquals('W', c2[3]);
+ assertEquals('o', c2[4]);
+ assertEquals('\0', c2[5]);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
new file mode 100644
index 0000000..e23b516
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListBasicTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.ExpandableListScenario;
+import android.util.ListUtil;
+import android.util.ExpandableListScenario.MyGroup;
+import android.view.KeyEvent;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+
+import java.util.List;
+
+public class ExpandableListBasicTest extends ActivityInstrumentationTestCase2<ExpandableListSimple> {
+ private ExpandableListScenario mActivity;
+ private ExpandableListView mExpandableListView;
+ private ExpandableListAdapter mAdapter;
+ private ListUtil mListUtil;
+
+ public ExpandableListBasicTest() {
+ super(ExpandableListSimple.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mExpandableListView = mActivity.getExpandableListView();
+ mAdapter = mExpandableListView.getExpandableListAdapter();
+ mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mExpandableListView);
+ }
+
+ private int expandGroup(int numChildren, boolean atLeastOneChild) {
+ final int groupPos = mActivity.findGroupWithNumChildren(numChildren, atLeastOneChild);
+ assertTrue("Could not find group to expand", groupPos >= 0);
+
+ assertFalse("Group is already expanded", mExpandableListView.isGroupExpanded(groupPos));
+ mListUtil.arrowScrollToSelectedPosition(groupPos);
+ getInstrumentation().waitForIdleSync();
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+ assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(groupPos));
+
+ return groupPos;
+ }
+
+ @MediumTest
+ public void testExpandGroup() {
+ expandGroup(-1, true);
+ }
+
+ @MediumTest
+ public void testCollapseGroup() {
+ final int groupPos = expandGroup(-1, true);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+ assertFalse("Group did not collapse", mExpandableListView.isGroupExpanded(groupPos));
+ }
+
+ @MediumTest
+ public void testExpandedGroupMovement() {
+ // Expand the first group
+ mListUtil.arrowScrollToSelectedPosition(0);
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+
+ // Ensure it expanded
+ assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
+
+ // Wait until that's all good
+ getInstrumentation().waitForIdleSync();
+
+ // Make sure it expanded
+ assertTrue("Group did not expand", mExpandableListView.isGroupExpanded(0));
+
+ // Insert a collapsed group in front of the one just expanded
+ List<MyGroup> groups = mActivity.getGroups();
+ MyGroup insertedGroup = new MyGroup(1);
+ groups.add(0, insertedGroup);
+
+ // Notify data change
+ assertTrue("Adapter is not an instance of the base adapter",
+ mAdapter instanceof BaseExpandableListAdapter);
+ final BaseExpandableListAdapter adapter = (BaseExpandableListAdapter) mAdapter;
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ adapter.notifyDataSetChanged();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ // Make sure the right group is expanded
+ assertTrue("The expanded state didn't stay with the proper group",
+ mExpandableListView.isGroupExpanded(1));
+ assertFalse("The expanded state was given to the inserted group",
+ mExpandableListView.isGroupExpanded(0));
+ }
+
+ @MediumTest
+ public void testContextMenus() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testContextMenus();
+ }
+
+ @MediumTest
+ public void testConvertionBetweenFlatAndPacked() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testConvertionBetweenFlatAndPackedOnGroups();
+ tester.testConvertionBetweenFlatAndPackedOnChildren();
+ }
+
+ @MediumTest
+ public void testSelectedPosition() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testSelectedPositionOnGroups();
+ tester.testSelectedPositionOnChildren();
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
new file mode 100644
index 0000000..78db28c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListSimple.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
+import android.widget.BaseExpandableListAdapter;
+
+import android.util.ExpandableListScenario;
+
+public class ExpandableListSimple extends ExpandableListScenario {
+ private static final int[] NUM_CHILDREN = {4, 3, 2, 1, 0};
+
+ @Override
+ protected void init(ExpandableParams params) {
+ params.setNumChildren(NUM_CHILDREN)
+ .setItemScreenSizeFactor(0.14);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+
+ menu.add("Add item").setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ public boolean onMenuItemClick(MenuItem item) {
+ mGroups.add(0, new MyGroup(2));
+ ((BaseExpandableListAdapter) mAdapter).notifyDataSetChanged();
+ return true;
+ }
+ });
+
+ return true;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
new file mode 100644
index 0000000..dfb10fb
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListTester.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.ExpandableListScenario;
+import android.util.ListUtil;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+
+import junit.framework.Assert;
+
+public class ExpandableListTester {
+ private final ExpandableListView mExpandableListView;
+ private final ExpandableListAdapter mAdapter;
+ private final ListUtil mListUtil;
+
+ private final ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
+ mActivityInstrumentation;
+
+ Instrumentation mInstrumentation;
+
+ public ExpandableListTester(
+ ExpandableListView expandableListView,
+ ActivityInstrumentationTestCase2<? extends ExpandableListScenario>
+ activityInstrumentation) {
+ mExpandableListView = expandableListView;
+ Instrumentation instrumentation = activityInstrumentation.getInstrumentation();
+ mListUtil = new ListUtil(mExpandableListView, instrumentation);
+ mAdapter = mExpandableListView.getExpandableListAdapter();
+ mActivityInstrumentation = activityInstrumentation;
+ mInstrumentation = mActivityInstrumentation.getInstrumentation();
+ }
+
+ private void expandGroup(final int groupIndex, int flatPosition) {
+ Assert.assertFalse("Group is already expanded", mExpandableListView
+ .isGroupExpanded(groupIndex));
+ mListUtil.arrowScrollToSelectedPosition(flatPosition);
+ mInstrumentation.waitForIdleSync();
+ mActivityInstrumentation.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ mActivityInstrumentation.getInstrumentation().waitForIdleSync();
+ Assert.assertTrue("Group did not expand " + groupIndex,
+ mExpandableListView.isGroupExpanded(groupIndex));
+ }
+
+ void testContextMenus() {
+ // Add a position tester ContextMenu listener to the ExpandableListView
+ PositionTesterContextMenuListener menuListener = new PositionTesterContextMenuListener();
+ mExpandableListView.setOnCreateContextMenuListener(menuListener);
+
+ int index = 0;
+
+ // Scrolling on header elements should trigger an AdapterContextMenu
+ for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
+ // Check group index in context menu
+ menuListener.expectAdapterContextMenu(i);
+ // Make sure the group is visible so that getChild finds it
+ mListUtil.arrowScrollToSelectedPosition(index);
+ View headerChild = mExpandableListView.getChildAt(index
+ - mExpandableListView.getFirstVisiblePosition());
+ mExpandableListView.showContextMenuForChild(headerChild);
+ mInstrumentation.waitForIdleSync();
+ Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
+ index++;
+ }
+
+ int groupCount = mAdapter.getGroupCount();
+ for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
+
+ // Expand group
+ expandGroup(groupIndex, index);
+
+ // Check group index in context menu
+ menuListener.expectGroupContextMenu(groupIndex);
+ // Make sure the group is visible so that getChild finds it
+ mListUtil.arrowScrollToSelectedPosition(index);
+ View groupChild = mExpandableListView.getChildAt(index
+ - mExpandableListView.getFirstVisiblePosition());
+ mExpandableListView.showContextMenuForChild(groupChild);
+ mInstrumentation.waitForIdleSync();
+ Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
+ index++;
+
+ final int childrenCount = mAdapter.getChildrenCount(groupIndex);
+ for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
+ // Check child index in context menu
+ mListUtil.arrowScrollToSelectedPosition(index);
+ menuListener.expectChildContextMenu(groupIndex, childIndex);
+ View child = mExpandableListView.getChildAt(index
+ - mExpandableListView.getFirstVisiblePosition());
+ mExpandableListView.showContextMenuForChild(child);
+ mInstrumentation.waitForIdleSync();
+ Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
+ index++;
+ }
+ }
+
+ // Scrolling on footer elements should trigger an AdapterContextMenu
+ for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
+ // Check group index in context menu
+ menuListener.expectAdapterContextMenu(index);
+ // Make sure the group is visible so that getChild finds it
+ mListUtil.arrowScrollToSelectedPosition(index);
+ View footerChild = mExpandableListView.getChildAt(index
+ - mExpandableListView.getFirstVisiblePosition());
+ mExpandableListView.showContextMenuForChild(footerChild);
+ mInstrumentation.waitForIdleSync();
+ Assert.assertNull(menuListener.getErrorMessage(), menuListener.getErrorMessage());
+ index++;
+ }
+
+ // Cleanup: remove the listener we added.
+ mExpandableListView.setOnCreateContextMenuListener(null);
+ }
+
+ private int expandAGroup() {
+ final int groupIndex = 2;
+ final int headerCount = mExpandableListView.getHeaderViewsCount();
+ Assert.assertTrue("Not enough groups", groupIndex < mAdapter.getGroupCount());
+ expandGroup(groupIndex, groupIndex + headerCount);
+ return groupIndex;
+ }
+
+ // This method assumes that NO group is expanded when called
+ void testConvertionBetweenFlatAndPackedOnGroups() {
+ final int headerCount = mExpandableListView.getHeaderViewsCount();
+
+ for (int i=0; i<headerCount; i++) {
+ Assert.assertEquals("Non NULL position for header item",
+ ExpandableListView.PACKED_POSITION_VALUE_NULL,
+ mExpandableListView.getExpandableListPosition(i));
+ }
+
+ // Test all (non expanded) groups
+ final int groupCount = mAdapter.getGroupCount();
+ for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
+ int expectedFlatPosition = headerCount + groupIndex;
+ long packedPositionForGroup = ExpandableListView.getPackedPositionForGroup(groupIndex);
+ Assert.assertEquals("Group not found at flat position " + expectedFlatPosition,
+ packedPositionForGroup,
+ mExpandableListView.getExpandableListPosition(expectedFlatPosition));
+
+ Assert.assertEquals("Wrong flat position for group " + groupIndex,
+ expectedFlatPosition,
+ mExpandableListView.getFlatListPosition(packedPositionForGroup));
+ }
+
+ for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
+ Assert.assertEquals("Non NULL position for header item",
+ ExpandableListView.PACKED_POSITION_VALUE_NULL,
+ mExpandableListView.getExpandableListPosition(headerCount + groupCount + i));
+ }
+ }
+
+ // This method assumes that NO group is expanded when called
+ void testConvertionBetweenFlatAndPackedOnChildren() {
+ // Test with an expanded group
+ final int headerCount = mExpandableListView.getHeaderViewsCount();
+ final int groupIndex = expandAGroup();
+
+ final int childrenCount = mAdapter.getChildrenCount(groupIndex);
+ for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
+ int expectedFlatPosition = headerCount + groupIndex + 1 + childIndex;
+ long childPos = ExpandableListView.getPackedPositionForChild(groupIndex, childIndex);
+
+ Assert.assertEquals("Wrong flat position for child ",
+ childPos,
+ mExpandableListView.getExpandableListPosition(expectedFlatPosition));
+
+ Assert.assertEquals("Wrong flat position for child ",
+ expectedFlatPosition,
+ mExpandableListView.getFlatListPosition(childPos));
+ }
+ }
+
+ // This method assumes that NO group is expanded when called
+ void testSelectedPositionOnGroups() {
+ int index = 0;
+
+ // Scrolling on header elements should not give a valid selected position.
+ for (int i=0; i<mExpandableListView.getHeaderViewsCount(); i++) {
+ mListUtil.arrowScrollToSelectedPosition(index);
+ Assert.assertEquals("Header item is selected",
+ ExpandableListView.PACKED_POSITION_VALUE_NULL,
+ mExpandableListView.getSelectedPosition());
+ index++;
+ }
+
+ // Check selection on group items
+ final int groupCount = mAdapter.getGroupCount();
+ for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) {
+ mListUtil.arrowScrollToSelectedPosition(index);
+ Assert.assertEquals("Group item is not selected",
+ ExpandableListView.getPackedPositionForGroup(groupIndex),
+ mExpandableListView.getSelectedPosition());
+ index++;
+ }
+
+ // Scrolling on footer elements should not give a valid selected position.
+ for (int i=0; i<mExpandableListView.getFooterViewsCount(); i++) {
+ mListUtil.arrowScrollToSelectedPosition(index);
+ Assert.assertEquals("Footer item is selected",
+ ExpandableListView.PACKED_POSITION_VALUE_NULL,
+ mExpandableListView.getSelectedPosition());
+ index++;
+ }
+ }
+
+ // This method assumes that NO group is expanded when called
+ void testSelectedPositionOnChildren() {
+ // Test with an expanded group
+ final int headerCount = mExpandableListView.getHeaderViewsCount();
+ final int groupIndex = expandAGroup();
+
+ final int childrenCount = mAdapter.getChildrenCount(groupIndex);
+ for (int childIndex = 0; childIndex < childrenCount; childIndex++) {
+ int childFlatPosition = headerCount + groupIndex + 1 + childIndex;
+ mListUtil.arrowScrollToSelectedPosition(childFlatPosition);
+ Assert.assertEquals("Group item is not selected",
+ ExpandableListView.getPackedPositionForChild(groupIndex, childIndex),
+ mExpandableListView.getSelectedPosition());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
new file mode 100644
index 0000000..2251c1d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeaders.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import android.os.Bundle;
+import android.util.ExpandableListScenario;
+import android.widget.Button;
+import android.widget.ExpandableListView;
+
+public class ExpandableListWithHeaders extends ExpandableListScenario {
+ private static final int[] sNumChildren = {1, 4, 3, 2, 6};
+ private static final int sNumOfHeadersAndFooters = 12;
+
+ @Override
+ protected void init(ExpandableParams params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumChildren(sNumChildren)
+ .setItemScreenSizeFactor(0.14)
+ .setConnectAdapter(false);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final ExpandableListView expandableListView = getExpandableListView();
+ expandableListView.setItemsCanFocus(true);
+
+ for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
+ Button header = new Button(this);
+ header.setText("Header View " + i);
+ expandableListView.addHeaderView(header);
+ }
+
+ for (int i = 0; i < sNumOfHeadersAndFooters; i++) {
+ Button footer = new Button(this);
+ footer.setText("Footer View " + i);
+ expandableListView.addFooterView(footer);
+ }
+
+ // Set adapter here AFTER we set header and footer views
+ setAdapter(expandableListView);
+ }
+
+ /**
+ * @return The number of headers (and the same number of footers)
+ */
+ public int getNumOfHeadersAndFooters() {
+ return sNumOfHeadersAndFooters;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
new file mode 100644
index 0000000..64a0fff
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.ListUtil;
+import android.view.KeyEvent;
+import android.widget.ExpandableListView;
+
+public class ExpandableListWithHeadersTest extends
+ ActivityInstrumentationTestCase2<ExpandableListWithHeaders> {
+ private ExpandableListView mExpandableListView;
+ private ListUtil mListUtil;
+
+ public ExpandableListWithHeadersTest() {
+ super(ExpandableListWithHeaders.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mExpandableListView = getActivity().getExpandableListView();
+ mListUtil = new ListUtil(mExpandableListView, getInstrumentation());
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mExpandableListView);
+ }
+
+ @MediumTest
+ public void testExpandOnFirstPosition() {
+ // Should be a header, and hence the first group should NOT have expanded
+ mListUtil.arrowScrollToSelectedPosition(0);
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+ assertFalse(mExpandableListView.isGroupExpanded(0));
+ }
+
+ @LargeTest
+ public void testExpandOnFirstGroup() {
+ mListUtil.arrowScrollToSelectedPosition(getActivity().getNumOfHeadersAndFooters());
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+ assertTrue(mExpandableListView.isGroupExpanded(0));
+ }
+
+ @MediumTest
+ public void testContextMenus() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testContextMenus();
+ }
+
+ @MediumTest
+ public void testConvertionBetweenFlatAndPacked() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testConvertionBetweenFlatAndPackedOnGroups();
+ tester.testConvertionBetweenFlatAndPackedOnChildren();
+ }
+
+ @MediumTest
+ public void testSelectedPosition() {
+ ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this);
+ tester.testSelectedPositionOnGroups();
+ tester.testSelectedPositionOnChildren();
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java b/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
new file mode 100644
index 0000000..f4c9d56
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/InflatedExpandableListView.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.expandablelistview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.TextView;
+
+public class InflatedExpandableListView extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.inflated_expandablelistview);
+
+ ExpandableListView elv = (ExpandableListView) findViewById(R.id.elv);
+ elv.setAdapter(new MyExpandableListAdapter());
+ }
+
+ public class MyExpandableListAdapter extends BaseExpandableListAdapter {
+ // Sample data set. children[i] contains the children (String[]) for groups[i].
+ private String[] groups = { "People Names", "Dog Names", "Cat Names", "Fish Names" };
+ private String[][] children = {
+ { "Arnold", "Barry", "Chuck", "David" },
+ { "Ace", "Bandit", "Cha-Cha", "Deuce" },
+ { "Fluffy", "Snuggles" },
+ { "Goldy", "Bubbles" }
+ };
+
+ public Object getChild(int groupPosition, int childPosition) {
+ return children[groupPosition][childPosition];
+ }
+
+ public long getChildId(int groupPosition, int childPosition) {
+ return childPosition;
+ }
+
+ public int getChildrenCount(int groupPosition) {
+ return children[groupPosition].length;
+ }
+
+ public TextView getGenericView() {
+ // Layout parameters for the ExpandableListView
+ AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, 64);
+
+ TextView textView = new TextView(InflatedExpandableListView.this);
+ textView.setLayoutParams(lp);
+ // Center the text vertically
+ textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+ // Set the text starting position
+ textView.setPadding(36, 0, 0, 0);
+ return textView;
+ }
+
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ TextView textView = getGenericView();
+ textView.setText(getChild(groupPosition, childPosition).toString());
+ return textView;
+ }
+
+ public Object getGroup(int groupPosition) {
+ return groups[groupPosition];
+ }
+
+ public int getGroupCount() {
+ return groups.length;
+ }
+
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ TextView textView = getGenericView();
+ textView.setText(getGroup(groupPosition).toString());
+ return textView;
+ }
+
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java b/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
new file mode 100644
index 0000000..2dbdff8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/expandablelistview/PositionTesterContextMenuListener.java
@@ -0,0 +1,111 @@
+/*
+ * 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.widget.expandablelistview;
+
+import android.view.ContextMenu;
+import android.view.View;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnCreateContextMenuListener;
+import android.widget.ExpandableListView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+
+public class PositionTesterContextMenuListener implements OnCreateContextMenuListener {
+
+ private int groupPosition, childPosition;
+
+ // Fake constant to store in testType a test type specific to headers and footers
+ private static final int ADAPTER_TYPE = -1;
+ private int testType; // as returned by getPackedPositionType
+
+ // Will be set to null by each call to onCreateContextMenu, unless an error occurred.
+ private String errorMessage;
+
+ public void expectGroupContextMenu(int groupPosition) {
+ this.groupPosition = groupPosition;
+ testType = ExpandableListView.PACKED_POSITION_TYPE_GROUP;
+ }
+
+ public void expectChildContextMenu(int groupPosition, int childPosition) {
+ this.groupPosition = groupPosition;
+ this.childPosition = childPosition;
+ testType = ExpandableListView.PACKED_POSITION_TYPE_CHILD;
+ }
+
+ public void expectAdapterContextMenu(int flatPosition) {
+ this.groupPosition = flatPosition;
+ testType = ADAPTER_TYPE;
+ }
+
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ errorMessage = null;
+ if (testType == ADAPTER_TYPE) {
+ if (!isTrue("MenuInfo is not an AdapterContextMenuInfo",
+ menuInfo instanceof AdapterContextMenuInfo)) {
+ return;
+ }
+ AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;
+ if (!areEqual("Wrong flat position", groupPosition, adapterContextMenuInfo.position)) {
+ return;
+ }
+ } else {
+ if (!isTrue("MenuInfo is not an ExpandableListContextMenuInfo",
+ menuInfo instanceof ExpandableListView.ExpandableListContextMenuInfo)) {
+ return;
+ }
+ ExpandableListView.ExpandableListContextMenuInfo elvMenuInfo =
+ (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
+ long packedPosition = elvMenuInfo.packedPosition;
+
+ int packedPositionType = ExpandableListView.getPackedPositionType(packedPosition);
+ if (!areEqual("Wrong packed position type", testType, packedPositionType)) {
+ return;
+ }
+
+ int packedPositionGroup = ExpandableListView.getPackedPositionGroup(packedPosition);
+ if (!areEqual("Wrong group position", groupPosition, packedPositionGroup)) {
+ return;
+ }
+
+ if (testType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
+ int packedPositionChild = ExpandableListView.getPackedPositionChild(packedPosition);
+ if (!areEqual("Wrong child position", childPosition, packedPositionChild)) {
+ return;
+ }
+ }
+ }
+ }
+
+ private boolean areEqual(String message, int expected, int actual) {
+ if (expected != actual) {
+ errorMessage = String.format(message + " (%d vs %d", expected, actual);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isTrue(String message, boolean value) {
+ if (!value) {
+ errorMessage = message;
+ return false;
+ }
+ return true;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/AdjacentVerticalRectLists.java b/core/tests/coretests/src/android/widget/focus/AdjacentVerticalRectLists.java
new file mode 100644
index 0000000..75da6fe
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/AdjacentVerticalRectLists.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.util.InternalSelectionView;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.view.ViewGroup;
+
+/**
+ * {@link android.view.FocusFinder#findNextFocus(android.view.ViewGroup, android.view.View, int)}
+ * and
+ * {@link android.view.View#requestFocus(int, android.graphics.Rect)}
+ * work together to give a newly focused item a hint about the most interesting
+ * rectangle of the previously focused view. The view taking focus can use this
+ * to set an internal selection more appropriate using this rect.
+ *
+ * This Activity excercises that behavior using three adjacent {@link android.util.InternalSelectionView}
+ * that report interesting rects when giving up focus, and use interesting rects
+ * when taking focus to best select the internal row to show as selected.
+ */
+public class AdjacentVerticalRectLists extends Activity {
+
+ private LinearLayout mLayout;
+ private InternalSelectionView mLeftColumn;
+ private InternalSelectionView mMiddleColumn;
+ private InternalSelectionView mRightColumn;
+
+
+ public LinearLayout getLayout() {
+ return mLayout;
+ }
+
+ public InternalSelectionView getLeftColumn() {
+ return mLeftColumn;
+ }
+
+ public InternalSelectionView getMiddleColumn() {
+ return mMiddleColumn;
+ }
+
+ public InternalSelectionView getRightColumn() {
+ return mRightColumn;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mLayout = new LinearLayout(this);
+ mLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,
+ ViewGroup.LayoutParams.MATCH_PARENT, 1);
+
+ mLeftColumn = new InternalSelectionView(this, 5, "left column");
+ mLeftColumn.setLayoutParams(params);
+ mLeftColumn.setPadding(10, 10, 10, 10);
+ mLayout.addView(mLeftColumn);
+
+ mMiddleColumn = new InternalSelectionView(this, 5, "middle column");
+ mMiddleColumn.setLayoutParams(params);
+ mMiddleColumn.setPadding(10, 10, 10, 10);
+ mLayout.addView(mMiddleColumn);
+
+ mRightColumn = new InternalSelectionView(this, 5, "right column");
+ mRightColumn.setLayoutParams(params);
+ mRightColumn.setPadding(10, 10, 10, 10);
+ mLayout.addView(mRightColumn);
+
+ setContentView(mLayout);
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/DescendantFocusability.java b/core/tests/coretests/src/android/widget/focus/DescendantFocusability.java
new file mode 100644
index 0000000..f7d91aa
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/DescendantFocusability.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class DescendantFocusability extends Activity {
+
+ public ViewGroup beforeDescendants;
+ public Button beforeDescendantsChild;
+
+ public ViewGroup afterDescendants;
+ public Button afterDescendantsChild;
+
+ public ViewGroup blocksDescendants;
+ public Button blocksDescendantsChild;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.descendant_focusability);
+
+ beforeDescendants = (ViewGroup) findViewById(R.id.beforeDescendants);
+ beforeDescendantsChild = (Button) beforeDescendants.getChildAt(0);
+
+ afterDescendants = (ViewGroup) findViewById(R.id.afterDescendants);
+ afterDescendantsChild = (Button) afterDescendants.getChildAt(0);
+
+ blocksDescendants = (ViewGroup) findViewById(R.id.blocksDescendants);
+ blocksDescendantsChild = (Button) blocksDescendants.getChildAt(0);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/DescendantFocusabilityTest.java b/core/tests/coretests/src/android/widget/focus/DescendantFocusabilityTest.java
new file mode 100644
index 0000000..2af42ac
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/DescendantFocusabilityTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.DescendantFocusability;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.UiThreadTest;
+import android.test.TouchUtils;
+import android.view.ViewGroup;
+
+public class DescendantFocusabilityTest extends ActivityInstrumentationTestCase<DescendantFocusability> {
+
+ private DescendantFocusability a;
+
+ public DescendantFocusabilityTest() {
+ super("com.android.frameworks.coretests", DescendantFocusability.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ a = getActivity();
+
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals(ViewGroup.FOCUS_BEFORE_DESCENDANTS,
+ a.beforeDescendants.getDescendantFocusability());
+ assertEquals(ViewGroup.FOCUS_AFTER_DESCENDANTS,
+ a.afterDescendants.getDescendantFocusability());
+ assertEquals(ViewGroup.FOCUS_BLOCK_DESCENDANTS,
+ a.blocksDescendants.getDescendantFocusability());
+
+ assertTrue(a.beforeDescendantsChild.isFocusable());
+ assertTrue(a.afterDescendantsChild.isFocusable());
+ assertTrue(a.blocksDescendantsChild.isFocusable());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testBeforeDescendants() {
+ a.beforeDescendants.setFocusable(true);
+
+ assertTrue(a.beforeDescendants.requestFocus());
+ assertTrue(a.beforeDescendants.isFocused());
+
+ a.beforeDescendants.setFocusable(false);
+ a.beforeDescendants.requestFocus();
+ assertTrue(a.beforeDescendantsChild.isFocused());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testAfterDescendants() {
+ a.afterDescendants.setFocusable(true);
+
+ assertTrue(a.afterDescendants.requestFocus());
+ assertTrue(a.afterDescendantsChild.isFocused());
+
+ a.afterDescendants.setFocusable(false);
+ assertTrue(a.afterDescendants.requestFocus());
+ assertTrue(a.afterDescendantsChild.isFocused());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testBlocksDescendants() {
+ a.blocksDescendants.setFocusable(true);
+ assertTrue(a.blocksDescendants.requestFocus());
+ assertTrue(a.blocksDescendants.isFocused());
+ assertFalse(a.blocksDescendantsChild.isFocused());
+
+ a.blocksDescendants.setFocusable(false);
+ assertFalse(a.blocksDescendants.requestFocus());
+ assertFalse(a.blocksDescendants.isFocused());
+ assertFalse(a.blocksDescendantsChild.isFocused());
+ }
+
+ @UiThreadTest
+ @MediumTest
+ public void testChildOfDescendantBlockerRequestFocusFails() {
+ assertFalse(a.blocksDescendantsChild.requestFocus());
+ }
+
+ @LargeTest
+ public void testBeforeDescendantsEnterTouchMode() {
+ a.runOnUiThread(new Runnable() {
+ public void run() {
+ a.beforeDescendants.setFocusableInTouchMode(true);
+ a.beforeDescendantsChild.requestFocus();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ assertTrue(a.beforeDescendantsChild.isFocused());
+ assertFalse(a.beforeDescendantsChild.isInTouchMode());
+
+ TouchUtils.clickView(this, a.beforeDescendantsChild);
+ assertTrue(a.beforeDescendantsChild.isInTouchMode());
+ assertFalse(a.beforeDescendants.isFocused());
+ }
+
+ @LargeTest
+ public void testAfterDescendantsEnterTouchMode() {
+ a.runOnUiThread(new Runnable() {
+ public void run() {
+ a.afterDescendants.setFocusableInTouchMode(true);
+ a.afterDescendantsChild.requestFocus();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ assertTrue(a.afterDescendantsChild.isFocused());
+ assertFalse(a.afterDescendantsChild.isInTouchMode());
+
+ TouchUtils.clickView(this, a.afterDescendantsChild);
+ assertTrue(a.afterDescendantsChild.isInTouchMode());
+ assertTrue(a.afterDescendants.isFocused());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/FocusAfterRemoval.java b/core/tests/coretests/src/android/widget/focus/FocusAfterRemoval.java
new file mode 100644
index 0000000..93245e7
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/FocusAfterRemoval.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import android.view.View;
+
+/**
+ * Exercises cases where elements of the UI are removed (and
+ * focus should go somewhere).
+ */
+public class FocusAfterRemoval extends Activity {
+
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.focus_after_removal);
+
+ final LinearLayout left = (LinearLayout) findViewById(R.id.leftLayout);
+
+ // top left makes parent layout GONE
+ Button topLeftButton = (Button) findViewById(R.id.topLeftButton);
+ topLeftButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ left.setVisibility(View.GONE);
+ }
+ });
+
+ // bottom left makes parent layout INVISIBLE
+ // top left makes parent layout GONE
+ Button bottomLeftButton = (Button) findViewById(R.id.bottomLeftButton);
+ bottomLeftButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ left.setVisibility(View.INVISIBLE);
+ }
+ });
+
+ // top right button makes top right button GONE
+ final Button topRightButton = (Button) findViewById(R.id.topRightButton);
+ topRightButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ topRightButton.setVisibility(View.GONE);
+ }
+ });
+
+ // bottom right button makes bottom right button INVISIBLE
+ final Button bottomRightButton = (Button) findViewById(R.id.bottomRightButton);
+ bottomRightButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ bottomRightButton.setVisibility(View.INVISIBLE);
+ }
+ });
+
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/FocusAfterRemovalTest.java b/core/tests/coretests/src/android/widget/focus/FocusAfterRemovalTest.java
new file mode 100644
index 0000000..a1b7bcb
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/FocusAfterRemovalTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.FocusAfterRemoval;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * {@link FocusAfterRemoval} is set up to exercise cases where the views that
+ * have focus become invisible or GONE.
+ */
+public class FocusAfterRemovalTest extends ActivityInstrumentationTestCase<FocusAfterRemoval> {
+
+ private LinearLayout mLeftLayout;
+ private Button mTopLeftButton;
+ private Button mBottomLeftButton;
+ private Button mTopRightButton;
+ private Button mBottomRightButton;
+
+ public FocusAfterRemovalTest() {
+ super("com.android.frameworks.coretests", FocusAfterRemoval.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final FocusAfterRemoval a = getActivity();
+ mLeftLayout = (LinearLayout) a.findViewById(R.id.leftLayout);
+ mTopLeftButton = (Button) a.findViewById(R.id.topLeftButton);
+ mBottomLeftButton = (Button) a.findViewById(R.id.bottomLeftButton);
+ mTopRightButton = (Button) a.findViewById(R.id.topRightButton);
+ mBottomRightButton = (Button) a.findViewById(R.id.bottomRightButton);
+ }
+
+ // Test that setUp did what we expect it to do. These asserts
+ // can't go in SetUp, or the test will hang.
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mLeftLayout);
+ assertNotNull(mTopLeftButton);
+ assertNotNull(mTopRightButton);
+ assertNotNull(mBottomLeftButton);
+ assertNotNull(mBottomRightButton);
+
+ assertTrue(mTopLeftButton.hasFocus());
+ }
+
+ // if a parent layout becomes GONE when one of its children has focus,
+ // make sure the focus moves to something visible (bug 827087)
+ @MediumTest
+ public void testFocusLeavesWhenParentLayoutIsGone() throws Exception {
+
+ // clicking on this button makes its parent linear layout GONE
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ assertEquals(View.GONE, mLeftLayout.getVisibility());
+
+ assertTrue("focus should jump to visible button",
+ mTopRightButton.hasFocus());
+
+ }
+
+ @MediumTest
+ public void testFocusLeavesWhenParentLayoutInvisible() throws Exception {
+
+ // move down to bottom left button
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(mBottomLeftButton.hasFocus());
+
+ // clicking on this button makes its parent linear layout INVISIBLE
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ assertEquals(View.INVISIBLE,
+ getActivity().findViewById(R.id.leftLayout).getVisibility());
+
+ assertTrue("focus should jump to visible button",
+ mTopRightButton.hasFocus());
+ }
+
+ @MediumTest
+ public void testFocusLeavesWhenFocusedViewBecomesGone() throws Exception {
+
+ // move to top right
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertTrue(mTopRightButton.hasFocus());
+
+ // click making it GONE
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ assertEquals(View.GONE, mTopRightButton.getVisibility());
+
+ assertTrue("focus should jump to visible button",
+ mTopLeftButton.hasFocus());
+ }
+
+ @MediumTest
+ public void testFocusLeavesWhenFocusedViewBecomesInvisible() throws Exception {
+
+ // move to bottom right
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(mBottomRightButton.hasFocus());
+
+ // click making it INVISIBLE
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ assertEquals(View.INVISIBLE, mBottomRightButton.getVisibility());
+
+ assertTrue("focus should jump to visible button",
+ mTopLeftButton.hasFocus());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/FocusChangeWithInterestingRectHintTest.java b/core/tests/coretests/src/android/widget/focus/FocusChangeWithInterestingRectHintTest.java
new file mode 100644
index 0000000..8f8f184
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/FocusChangeWithInterestingRectHintTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.AdjacentVerticalRectLists;
+import android.util.InternalSelectionView;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+
+/**
+ * {@link android.view.FocusFinder#findNextFocus(android.view.ViewGroup, android.view.View, int)}
+ * and
+ * {@link android.view.View#requestFocus(int, android.graphics.Rect)}
+ * work together to give a newly focused item a hint about the most interesting
+ * rectangle of the previously focused view. The view taking focus can use this
+ * to set an internal selection more appropriate using this rect.
+ *
+ * This tests that behavior using three adjacent {@link android.util.InternalSelectionView}
+ * that report interesting rects when giving up focus, and use interesting rects
+ * when taking focus to best select the internal row to show as selected.
+ *
+ */
+public class FocusChangeWithInterestingRectHintTest extends ActivityInstrumentationTestCase<AdjacentVerticalRectLists> {
+
+ private InternalSelectionView mLeftColumn;
+ private InternalSelectionView mMiddleColumn;
+ private InternalSelectionView mRightColumn;
+
+ public FocusChangeWithInterestingRectHintTest() {
+ super("com.android.frameworks.coretests", AdjacentVerticalRectLists.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mLeftColumn = getActivity().getLeftColumn();
+ mMiddleColumn = getActivity().getMiddleColumn();
+ mRightColumn = getActivity().getRightColumn();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mLeftColumn);
+ assertNotNull(mMiddleColumn);
+ assertNotNull(mRightColumn);
+ assertTrue(mLeftColumn.hasFocus());
+ assertTrue("need at least 3 rows", mLeftColumn.getNumRows() > 2);
+ assertEquals(mLeftColumn.getNumRows(), mMiddleColumn.getNumRows());
+ assertEquals(mMiddleColumn.getNumRows(), mRightColumn.getNumRows());
+ }
+
+
+ @LargeTest
+ public void testSnakeBackAndForth() {
+ final int numRows = mLeftColumn.getNumRows();
+ for (int row = 0; row < numRows; row++) {
+
+ if ((row % 2) == 0) {
+ assertEquals("row " + row + ": should be at left column",
+ row, mLeftColumn.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertTrue("row " + row + ": should be at middle column",
+ mMiddleColumn.hasFocus());
+ assertEquals(row, mMiddleColumn.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertTrue("row " + row + ": should be at right column",
+ mRightColumn.hasFocus());
+ assertEquals(row, mRightColumn.getSelectedRow());
+
+ if (row < numRows - 1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(row + 1, mRightColumn.getSelectedRow());
+ }
+ } else {
+ assertTrue("row " + row + ": should be at right column",
+ mRightColumn.hasFocus());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ assertTrue("row " + row + ": should be at middle column",
+ mMiddleColumn.hasFocus());
+ assertEquals(row, mMiddleColumn.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ assertEquals("row " + row + ": should be at left column",
+ row, mLeftColumn.getSelectedRow());
+
+ if (row < numRows - 1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(row + 1, mLeftColumn.getSelectedRow());
+ }
+ }
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChild.java b/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChild.java
new file mode 100644
index 0000000..af90997
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChild.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * An activity that helps test the scenario where a parent is
+ * GONE and one of its children has focus; the activity should get
+ * the key event. see bug 945150.
+ */
+public class GoneParentFocusedChild extends Activity {
+ private LinearLayout mGoneGroup;
+ private Button mButton;
+
+ private boolean mUnhandledKeyEvent = false;
+ private LinearLayout mLayout;
+
+ public boolean isUnhandledKeyEvent() {
+ return mUnhandledKeyEvent;
+ }
+
+ public LinearLayout getLayout() {
+ return mLayout;
+ }
+
+ public LinearLayout getGoneGroup() {
+ return mGoneGroup;
+ }
+
+ public Button getButton() {
+ return mButton;
+ }
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mLayout = new LinearLayout(this);
+ mLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+
+ mGoneGroup = new LinearLayout(this);
+ mGoneGroup.setOrientation(LinearLayout.HORIZONTAL);
+ mGoneGroup.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ mButton = new Button(this);
+ mButton.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+
+ mGoneGroup.addView(mButton);
+ setContentView(mLayout);
+
+ mGoneGroup.setVisibility(View.GONE);
+ mButton.requestFocus();
+ }
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ mUnhandledKeyEvent = true;
+ return true;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChildTest.java b/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChildTest.java
new file mode 100644
index 0000000..dcbddef
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/GoneParentFocusedChildTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.focus.GoneParentFocusedChild;
+
+/**
+ * When a parent is GONE, key events shouldn't go to its children, even if they
+ * have focus. (part of investigation into issue 945150).
+ */
+public class GoneParentFocusedChildTest
+ extends ActivityInstrumentationTestCase<GoneParentFocusedChild> {
+
+
+ public GoneParentFocusedChildTest() {
+ super("com.android.frameworks.coretests", GoneParentFocusedChild.class);
+ }
+
+ @MediumTest
+ public void testPreconditinos() {
+ assertNotNull(getActivity().getLayout());
+ assertNotNull(getActivity().getGoneGroup());
+ assertNotNull(getActivity().getButton());
+ assertTrue("button should have focus",
+ getActivity().getButton().hasFocus());
+ assertEquals("gone group should be, well, gone!",
+ View.GONE,
+ getActivity().getGoneGroup().getVisibility());
+ assertFalse("the activity should have received no key events",
+ getActivity().isUnhandledKeyEvent());
+ }
+
+ @MediumTest
+ public void testKeyEventGoesToActivity() {
+ sendKeys(KeyEvent.KEYCODE_J);
+ assertTrue(getActivity().isUnhandledKeyEvent());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearch.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearch.java
new file mode 100644
index 0000000..11cac1e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearch.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import android.widget.TextView;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.content.Context;
+
+public class HorizontalFocusSearch extends Activity {
+
+ private LinearLayout mLayout;
+
+ private Button mLeftTall;
+ private Button mMidShort1Top;
+ private Button mMidShort2Bottom;
+ private Button mRightTall;
+
+
+ public LinearLayout getLayout() {
+ return mLayout;
+ }
+
+ public Button getLeftTall() {
+ return mLeftTall;
+ }
+
+ public Button getMidShort1Top() {
+ return mMidShort1Top;
+ }
+
+ public Button getMidShort2Bottom() {
+ return mMidShort2Bottom;
+ }
+
+ public Button getRightTall() {
+ return mRightTall;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mLayout = new LinearLayout(this);
+ mLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ mLeftTall = makeTall("left tall");
+ mLayout.addView(mLeftTall);
+
+ mMidShort1Top = addShort(mLayout, "mid(1) top", false);
+ mMidShort2Bottom = addShort(mLayout, "mid(2) bottom", true);
+
+ mRightTall = makeTall("right tall");
+ mLayout.addView(mRightTall);
+
+ setContentView(mLayout);
+ }
+
+ // just to get toString non-sucky
+ private static class MyButton extends Button {
+
+ public MyButton(Context context) {
+ super(context);
+ }
+
+
+ @Override
+ public String toString() {
+ return getText().toString();
+ }
+ }
+
+ private Button makeTall(String label) {
+ Button button = new MyButton(this);
+ button.setText(label);
+ button.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ return button;
+ }
+
+ private Button addShort(LinearLayout root, String label, boolean atBottom) {
+ Button button = new MyButton(this);
+ button.setText(label);
+ button.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 0, // height
+ 490));
+
+ TextView filler = new TextView(this);
+ filler.setText("filler");
+ filler.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 0, // height
+ 510));
+
+ LinearLayout ll = new LinearLayout(this);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ ll.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ if (atBottom) {
+ ll.addView(filler);
+ ll.addView(button);
+ root.addView(ll);
+ } else {
+ ll.addView(button);
+ ll.addView(filler);
+ root.addView(ll);
+ }
+ return button;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
new file mode 100644
index 0000000..b591e5f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.HorizontalFocusSearch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import android.view.View;
+
+import static android.widget.focus.VerticalFocusSearchTest.FocusSearchAlg;
+import static android.widget.focus.VerticalFocusSearchTest.NewFocusSearchAlg;
+
+/**
+ * Tests that focus searching works on a horizontal linear layout of buttons of
+ * various widths and vertical placements.
+ */
+// Suppress until bug http://b/issue?id=1416545 is fixed.
+@Suppress
+public class HorizontalFocusSearchTest extends ActivityInstrumentationTestCase<HorizontalFocusSearch> {
+
+ private FocusSearchAlg mFocusFinder;
+
+ private LinearLayout mLayout;
+ private Button mLeftTall;
+ private Button mMidShort1Top;
+ private Button mMidShort2Bottom;
+ private Button mRightTall;
+
+
+ public HorizontalFocusSearchTest() {
+ super("com.android.frameworks.coretests", HorizontalFocusSearch.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mFocusFinder = new NewFocusSearchAlg();
+
+ mLayout = getActivity().getLayout();
+ mLeftTall = getActivity().getLeftTall();
+ mMidShort1Top = getActivity().getMidShort1Top();
+ mMidShort2Bottom = getActivity().getMidShort2Bottom();
+ mRightTall = getActivity().getRightTall();
+ }
+
+ public void testPreconditions() {
+ assertNotNull(mLayout);
+ assertNotNull(mLeftTall);
+ assertNotNull(mMidShort1Top);
+ assertNotNull(mMidShort2Bottom);
+ assertNotNull(mRightTall);
+ }
+
+ public void testSearchFromLeftButton() {
+ assertNull("going up from mLeftTall",
+ mFocusFinder.findNextFocus(mLayout, mLeftTall, View.FOCUS_UP));
+ assertNull("going down from mLeftTall",
+ mFocusFinder.findNextFocus(mLayout, mLeftTall, View.FOCUS_DOWN));
+ assertNull("going left from mLeftTall",
+ mFocusFinder.findNextFocus(mLayout, mLeftTall, View.FOCUS_LEFT));
+
+ assertEquals("going right from mLeftTall",
+ mMidShort1Top,
+ mFocusFinder.findNextFocus(mLayout, mLeftTall, View.FOCUS_RIGHT));
+ }
+
+ public void TODO_testSearchFromMiddleLeftButton() {
+ assertNull("going up from mMidShort1Top",
+ mFocusFinder.findNextFocus(mLayout, mMidShort1Top, View.FOCUS_UP));
+ assertEquals("going down from mMidShort1Top",
+ mMidShort2Bottom,
+ mFocusFinder.findNextFocus(mLayout, mMidShort1Top, View.FOCUS_DOWN));
+ assertEquals("going left from mMidShort1Top",
+ mLeftTall,
+ mFocusFinder.findNextFocus(mLayout, mMidShort1Top, View.FOCUS_LEFT));
+ assertEquals("going right from mMidShort1Top",
+ mMidShort2Bottom,
+ mFocusFinder.findNextFocus(mLayout, mMidShort1Top, View.FOCUS_RIGHT));
+ }
+
+ public void TODO_testSearchFromMiddleRightButton() {
+ assertEquals("going up from mMidShort2Bottom",
+ mMidShort1Top,
+ mFocusFinder.findNextFocus(mLayout, mMidShort2Bottom, View.FOCUS_UP));
+ assertNull("going down from mMidShort2Bottom",
+ mFocusFinder.findNextFocus(mLayout, mMidShort2Bottom, View.FOCUS_DOWN));
+ assertEquals("going left from mMidShort2Bottom",
+ mMidShort1Top,
+ mFocusFinder.findNextFocus(mLayout, mMidShort2Bottom, View.FOCUS_LEFT));
+ assertEquals("goind right from mMidShort2Bottom",
+ mRightTall,
+ mFocusFinder.findNextFocus(mLayout, mMidShort2Bottom, View.FOCUS_RIGHT));
+ }
+
+ public void testSearchFromRightButton() {
+ assertNull("going up from mRightTall",
+ mFocusFinder.findNextFocus(mLayout, mRightTall, View.FOCUS_UP));
+ assertNull("going down from mRightTall",
+ mFocusFinder.findNextFocus(mLayout, mRightTall, View.FOCUS_DOWN));
+ assertEquals("going left from mRightTall",
+ mMidShort2Bottom,
+ mFocusFinder.findNextFocus(mLayout, mRightTall, View.FOCUS_LEFT));
+ assertNull("going right from mRightTall",
+ mFocusFinder.findNextFocus(mLayout, mRightTall, View.FOCUS_RIGHT));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/LinearLayoutGrid.java b/core/tests/coretests/src/android/widget/focus/LinearLayoutGrid.java
new file mode 100644
index 0000000..db082ec
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/LinearLayoutGrid.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import com.android.frameworks.coretests.R;
+
+public class LinearLayoutGrid extends Activity {
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.linear_layout_grid);
+ }
+
+ public ViewGroup getRootView() {
+ return (ViewGroup) findViewById(R.id.layout);
+ }
+
+ public Button getButtonAt(int column, int row) {
+ if (row < 0 || row > 2) {
+ throw new IllegalArgumentException("row out of range");
+ }
+ if (column < 0 || column > 2) {
+ throw new IllegalArgumentException("column out of range");
+ }
+ return (Button) getColumn(column).getChildAt(row);
+ }
+
+
+
+ private LinearLayout getColumn(int column) {
+ switch (column) {
+ case 0:
+ return (LinearLayout) findViewById(R.id.column1);
+ case 1:
+ return (LinearLayout) findViewById(R.id.column2);
+ case 2:
+ return (LinearLayout) findViewById(R.id.column3);
+ default:
+ throw new IllegalArgumentException("column out of range");
+ }
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/LinearLayoutGridTest.java b/core/tests/coretests/src/android/widget/focus/LinearLayoutGridTest.java
new file mode 100644
index 0000000..89cb8bb
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/LinearLayoutGridTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.test.SingleLaunchActivityTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.FocusFinder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.focus.LinearLayoutGrid;
+
+/**
+ * Tests focus searching between buttons within a grid that are touching, for example,
+ * two buttons next two each other would have the left button's right equal to the
+ * right button's left. Same goes for top and bottom edges.
+ *
+ * This exercises some edge cases of {@link android.view.FocusFinder}.
+ */
+public class LinearLayoutGridTest extends SingleLaunchActivityTestCase<LinearLayoutGrid> {
+ private ViewGroup mRootView;
+
+ public LinearLayoutGridTest() {
+ super("com.android.frameworks.coretests", LinearLayoutGrid.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mRootView = getActivity().getRootView();
+ }
+
+ @MediumTest
+ public void testGoDownFromMiddle() {
+ assertEquals(getActivity().getButtonAt(2, 1),
+ FocusFinder.getInstance().findNextFocus(
+ mRootView,
+ getActivity().getButtonAt(1, 1),
+ View.FOCUS_DOWN));
+ }
+
+ @MediumTest
+ public void testGoUpFromMiddle() {
+ assertEquals(getActivity().getButtonAt(0, 1),
+ FocusFinder.getInstance().findNextFocus(
+ mRootView,
+ getActivity().getButtonAt(1, 1),
+ View.FOCUS_UP));
+ }
+
+ @MediumTest
+ public void testGoRightFromMiddle() {
+ assertEquals(getActivity().getButtonAt(1, 2),
+ FocusFinder.getInstance().findNextFocus(
+ mRootView,
+ getActivity().getButtonAt(1, 1),
+ View.FOCUS_RIGHT));
+ }
+
+ @MediumTest
+ public void testGoLeftFromMiddle() {
+ assertEquals(getActivity().getButtonAt(1, 0),
+ FocusFinder.getInstance().findNextFocus(
+ mRootView,
+ getActivity().getButtonAt(1, 1),
+ View.FOCUS_LEFT));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfButtons.java b/core/tests/coretests/src/android/widget/focus/ListOfButtons.java
new file mode 100644
index 0000000..308861d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListOfButtons.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+
+/**
+ * A layout with a ListView containing buttons.
+ */
+public class ListOfButtons extends ListActivity {
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_with_button_above);
+ getListView().setItemsCanFocus(true);
+ setListAdapter(new MyAdapter(this, mLabels));
+ }
+
+ String[] mLabels = {
+ "Alabama", "Alaska", "Arizona", "apple sauce!",
+ "California", "Colorado", "Connecticut", "Delaware"
+ };
+
+
+ public String[] getLabels() {
+ return mLabels;
+ }
+
+ public static class MyAdapter extends ArrayAdapter<String> {
+
+
+ public MyAdapter(Context context, String[] labels) {
+ super(context, 0, labels);
+ }
+
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String label = getItem(position);
+
+ Button button = new Button(parent.getContext());
+ button.setText(label);
+ return button;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java b/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java
new file mode 100644
index 0000000..3dba4e5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.ListOfButtons;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * Tests that focus works as expected when navigating into and out of
+ * a {@link ListView} that has buttons in it.
+ */
+public class ListOfButtonsTest extends ActivityInstrumentationTestCase<ListOfButtons> {
+
+ private ListAdapter mListAdapter;
+ private Button mButtonAtTop;
+
+ private ListView mListView;
+
+ public ListOfButtonsTest() {
+ super("com.android.frameworks.coretests", ListOfButtons.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ ListOfButtons a = getActivity();
+ mListAdapter = a.getListAdapter();
+ mButtonAtTop = (Button) a.findViewById(R.id.button);
+ mListView = a.getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListAdapter);
+ assertNotNull(mButtonAtTop);
+ assertNotNull(mListView);
+
+ assertFalse(mButtonAtTop.hasFocus());
+ assertTrue(mListView.hasFocus());
+ assertEquals("expecting 0 index to be selected",
+ 0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testNavigateToButtonAbove() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ assertTrue(mButtonAtTop.hasFocus());
+ assertFalse(mListView.hasFocus());
+ }
+
+ @MediumTest
+ public void testNavigateToSecondItem() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue(mListView.hasFocus());
+
+ View childOne = mListView.getChildAt(1);
+ assertNotNull(childOne);
+ assertEquals(childOne, mListView.getFocusedChild());
+ assertTrue(childOne.hasFocus());
+ }
+
+ @MediumTest
+ public void testNavigateUpAboveAndBackOut() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertFalse("button at top should have focus back",
+ mButtonAtTop.hasFocus());
+ assertTrue(mListView.hasFocus());
+ }
+
+ // TODO: this reproduces bug 981791
+ public void TODO_testNavigateThroughAllButtonsAndBack() {
+
+ String[] labels = getActivity().getLabels();
+ for (int i = 0; i < labels.length; i++) {
+ String label = labels[i];
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ String indexInfo = "index: " + i + ", label: " + label;
+
+ assertTrue(indexInfo, mListView.hasFocus());
+
+ Button button = (Button) mListView.getSelectedView();
+ assertNotNull(indexInfo, button);
+ assertEquals(indexInfo, label, button.getText().toString());
+ assertTrue(indexInfo, button.hasFocus());
+ }
+
+ // pressing down again shouldn't matter; make sure last item keeps focus
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+
+ for (int i = labels.length - 1; i >= 0; i--) {
+ String label = labels[i];
+
+ String indexInfo = "index: " + i + ", label: " + label;
+
+ assertTrue(indexInfo, mListView.hasFocus());
+
+ Button button = (Button) mListView.getSelectedView();
+ assertNotNull(indexInfo, button);
+ assertEquals(indexInfo, label, button.getText().toString());
+ assertTrue(indexInfo, button.hasFocus());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ assertTrue("button at top should have focus back",
+ mButtonAtTop.hasFocus());
+ assertFalse(mListView.hasFocus());
+ }
+
+ @MediumTest
+ public void testGoInAndOutOfListWithItemsFocusable() {
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ assertTrue(mButtonAtTop.hasFocus());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ final String firstButtonLabel = getActivity().getLabels()[0];
+ final Button firstButton = (Button) mListView.getSelectedView();
+
+ assertTrue(firstButton.isFocused());
+ assertEquals(firstButtonLabel, firstButton.getText());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertTrue(mButtonAtTop.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(firstButton.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertTrue(mButtonAtTop.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(firstButton.isFocused());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java b/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java
new file mode 100644
index 0000000..c2e7a26
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListOfEditTexts.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.*;
+import com.google.android.collect.Lists;
+
+import java.util.List;
+
+public class ListOfEditTexts extends Activity {
+
+ private int mLinesPerEditText = 12;
+
+ private ListView mListView;
+ private LinearLayout mLinearLayout;
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ // create linear layout
+ mLinearLayout = new LinearLayout(this);
+ mLinearLayout.setOrientation(LinearLayout.VERTICAL);
+ mLinearLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // add a button above
+ Button buttonAbove = new Button(this);
+ buttonAbove.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ buttonAbove.setText("button above list");
+ mLinearLayout.addView(buttonAbove);
+
+ // add a list view to it
+ mListView = new ListView(this);
+ mListView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mListView.setDrawSelectorOnTop(false);
+ mListView.setItemsCanFocus(true);
+ mListView.setLayoutParams((new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 0,
+ 1f)));
+
+ List<String> bodies = Lists.newArrayList(
+ getBody("zero hello, my name is android"),
+ getBody("one i'm a paranoid android"),
+ getBody("two i robot. huh huh."),
+ getBody("three not the g-phone!"));
+
+ mListView.setAdapter(new MyAdapter(this, bodies));
+ mLinearLayout.addView(mListView);
+
+ // add button below
+ Button buttonBelow = new Button(this);
+ buttonBelow.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ buttonBelow.setText("button below list");
+ mLinearLayout.addView(buttonBelow);
+
+ setContentView(mLinearLayout);
+ }
+
+ String getBody(String line) {
+ StringBuilder sb = new StringBuilder((line.length() + 5) * mLinesPerEditText);
+ for (int i = 0; i < mLinesPerEditText; i++) {
+ sb.append(i + 1).append(' ').append(line);
+ if (i < mLinesPerEditText - 1) {
+ sb.append('\n'); // all but last line
+ }
+ }
+ return sb.toString();
+ }
+
+
+ private static class MyAdapter extends ArrayAdapter<String> {
+
+ public MyAdapter(Context context, List<String> bodies) {
+ super(context, 0, bodies);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String body = getItem(position);
+
+ if (convertView != null) {
+ ((EditText) convertView).setText(body);
+ return convertView;
+ }
+
+ EditText editText = new EditText(getContext());
+ editText.setText(body);
+ return editText;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java
new file mode 100644
index 0000000..6518341
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.util.InternalSelectionView;
+
+/**
+ * A list of {@link InternalSelectionView}s paramatarized by the number of items,
+ * how many rows in each item, and how tall each item is.
+ */
+public class ListOfInternalSelectionViews extends Activity {
+
+ private ListView mListView;
+
+
+ // keys for initializing via Intent params
+ public static final String BUNDLE_PARAM_NUM_ITEMS = "com.google.test.numItems";
+ public static final String BUNDLE_PARAM_NUM_ROWS_PER_ITEM = "com.google.test.numRowsPerItem";
+ public static final String BUNDLE_PARAM_ITEM_SCREEN_HEIGHT_FACTOR = "com.google.test.itemScreenHeightFactor";
+
+ private int mScreenHeight;
+
+ private int mNumItems = 5;
+ private int mNumRowsPerItem = 4;
+ private double mItemScreenSizeFactor = 5 / 4;
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ /**
+ * Each item is screen height * this factor tall.
+ */
+ public double getItemScreenSizeFactor() {
+ return mItemScreenSizeFactor;
+ }
+
+ /**
+ * @return The number of rows per item.
+ */
+ public int getNumRowsPerItem() {
+ return mNumRowsPerItem;
+ }
+
+ /**
+ * @return The number of items in the list.
+ */
+ public int getNumItems() {
+ return mNumItems;
+ }
+
+ /**
+ * @param position The position
+ * @return The label (closest thing to a value) for the item at position
+ */
+ public String getLabelForPosition(int position) {
+ return "position " + position;
+ }
+
+ /**
+ * Get the currently selected view.
+ */
+ public InternalSelectionView getSelectedView() {
+ return (InternalSelectionView) getListView().getSelectedView();
+ }
+
+ /**
+ * Get the screen height.
+ */
+ public int getScreenHeight() {
+ return mScreenHeight;
+ }
+
+ /**
+ * Initialize a bundle suitable for sending as the params of the intent that
+ * launches this activity.
+ * @param numItems The number of items in the list.
+ * @param numRowsPerItem The number of rows per item.
+ * @param itemScreenHeightFactor see {@link #getScreenHeight()}
+ * @return the intialized bundle.
+ */
+ public static Bundle getBundleFor(int numItems, int numRowsPerItem, double itemScreenHeightFactor) {
+ Bundle bundle = new Bundle();
+ bundle.putInt(BUNDLE_PARAM_NUM_ITEMS, numItems);
+ bundle.putInt(BUNDLE_PARAM_NUM_ROWS_PER_ITEM, numRowsPerItem);
+ bundle.putDouble(BUNDLE_PARAM_ITEM_SCREEN_HEIGHT_FACTOR, itemScreenHeightFactor);
+ return bundle;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ initFromBundle(extras);
+ }
+
+ mListView = new ListView(this);
+ mListView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ mListView.setDrawSelectorOnTop(false);
+ mListView.setAdapter(new MyAdapter());
+ mListView.setItemsCanFocus(true);
+ setContentView(mListView);
+ }
+
+ private void initFromBundle(Bundle icicle) {
+
+ int numItems = icicle.getInt(BUNDLE_PARAM_NUM_ITEMS, -1);
+ if (numItems != -1) {
+ mNumItems = numItems;
+ }
+ int numRowsPerItem = icicle.getInt(BUNDLE_PARAM_NUM_ROWS_PER_ITEM, -1);
+ if (numRowsPerItem != -1) {
+ mNumRowsPerItem = numRowsPerItem;
+ }
+ double screenHeightFactor = icicle.getDouble(BUNDLE_PARAM_ITEM_SCREEN_HEIGHT_FACTOR, -1.0);
+ if (screenHeightFactor > 0) {
+ mItemScreenSizeFactor = screenHeightFactor;
+ }
+ }
+
+ private class MyAdapter extends BaseAdapter {
+
+ public int getCount() {
+ return mNumItems;
+ }
+
+ public Object getItem(int position) {
+ return getLabelForPosition(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ InternalSelectionView item =
+ new InternalSelectionView(
+ parent.getContext(),
+ mNumRowsPerItem,
+ getLabelForPosition(position));
+ item.setDesiredHeight((int) (mScreenHeight * mItemScreenSizeFactor));
+ return item;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabels.java b/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabels.java
new file mode 100644
index 0000000..ceb0e95
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabels.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.google.android.collect.Lists;
+import com.android.frameworks.coretests.R;
+
+import java.util.List;
+
+public class ListWithFooterViewAndNewLabels extends ListActivity {
+
+ private MyAdapter mMyAdapter;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_with_button_above);
+
+ Button footerButton = new Button(this);
+ footerButton.setText("hi");
+ footerButton.setLayoutParams(
+ new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ getListView().addFooterView(footerButton);
+
+ mMyAdapter = new MyAdapter(this);
+ setListAdapter(mMyAdapter);
+
+ // not in list
+ Button topButton = (Button) findViewById(R.id.button);
+ topButton.setText("click to add new item");
+ topButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ mMyAdapter.addLabel("yo");
+ }
+ });
+
+ mMyAdapter.addLabel("first");
+ }
+
+ /**
+ * An adapter that can take new string labels.
+ */
+ static class MyAdapter extends BaseAdapter {
+
+ private final Context mContext;
+ private List<String> mLabels = Lists.newArrayList();
+
+ public MyAdapter(Context context) {
+ mContext = context;
+ }
+
+ public int getCount() {
+ return mLabels.size();
+ }
+
+ public Object getItem(int position) {
+ return mLabels.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String label = mLabels.get(position);
+
+ LayoutInflater inflater = (LayoutInflater)
+ mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ TextView tv = (TextView) inflater.inflate(
+ android.R.layout.simple_list_item_1,
+ null);
+ tv.setText(label);
+ return tv;
+ }
+
+ public void addLabel(String s) {
+ mLabels.add(s + mLabels.size());
+ notifyDataSetChanged();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabelsTest.java b/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabelsTest.java
new file mode 100644
index 0000000..57dbb78
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListWithFooterViewAndNewLabelsTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.ListWithFooterViewAndNewLabels;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+public class ListWithFooterViewAndNewLabelsTest
+ extends ActivityInstrumentationTestCase<ListWithFooterViewAndNewLabels> {
+
+ private Button mButton;
+
+ private ListAdapter mAdapter;
+
+ private ListView mListView;
+
+
+ public ListWithFooterViewAndNewLabelsTest() {
+ super("com.android.frameworks.coretests",
+ ListWithFooterViewAndNewLabels.class);
+ }
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ ListWithFooterViewAndNewLabels a = getActivity();
+ mButton = (Button) a.findViewById(R.id.button);
+ mAdapter = a.getListAdapter();
+ mListView = a.getListView();
+ }
+
+ // bug 900885
+ public void FAILING_testPreconditions() {
+ assertNotNull(mButton);
+ assertNotNull(mAdapter);
+ assertNotNull(mListView);
+
+ assertTrue(mButton.hasFocus());
+ assertEquals("expected list adapter to have 1 item",
+ 1, mAdapter.getCount());
+ assertEquals("expected list view to have 2 items (1 in adapter, plus "
+ + "the footer view).",
+ 2, mListView.getCount());
+
+ // fails here!!!
+ assertEquals("Expecting the selected index to be 0, the first non footer "
+ + "view item.",
+ 0, mListView.getSelectedItemPosition());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ListWithMailMessages.java b/core/tests/coretests/src/android/widget/focus/ListWithMailMessages.java
new file mode 100644
index 0000000..5de4ad5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ListWithMailMessages.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import com.android.frameworks.coretests.R;
+import com.google.android.collect.Lists;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.webkit.WebView;
+
+import java.util.List;
+
+public class ListWithMailMessages extends ListActivity {
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.list_with_button_above);
+
+ List<MailMessage> messages = Lists.newArrayList();
+ messages.add(new MailMessage("hello!", "<p>this is a test "
+ + "message, with a bunch of text and stuff.</p>", true));
+
+// String android = "android";
+ String android = "<a href=\"www.android.com\">android</a>";
+
+ String sentance = "all work and no play makes "
+ + android + " a dull... robot!";
+ StringBuffer longBody = new StringBuffer().append("<ol>\n");
+ for (int i = 0; i < 12; i++) {
+ longBody.append("<li>").append(sentance).append("</li>");
+ }
+ longBody.append("</ol>");
+
+ messages.add(new MailMessage("hello2!", longBody.toString(), true));
+ messages.add(new MailMessage("phone number?", "<p>hey man, what's ur "
+ + "contact info? i need to mail you this photo of my two"
+ + " cats, they've gotten soooo fat!</p>", true));
+
+ setListAdapter(new MyAdapter(this, R.layout.mail_message, messages));
+ getListView().setItemsCanFocus(true);
+ }
+
+
+ /**
+ * POJO mail message.
+ */
+ static class MailMessage {
+ private String mSubject;
+ private String mBody;
+ private boolean mFocusable;
+
+
+ public MailMessage(String subject, String body) {
+ this(subject, body, false);
+ }
+
+
+ public MailMessage(String subject, String body, boolean focusable) {
+ mSubject = subject;
+ mBody = body;
+ mFocusable = focusable;
+ }
+
+ public String getSubject() {
+ return mSubject;
+ }
+
+ public void setSubject(String subject) {
+ this.mSubject = subject;
+ }
+
+ public String getBody() {
+ return mBody;
+ }
+
+ public void setBody(String body) {
+ this.mBody = body;
+ }
+
+
+ public boolean isFocusable() {
+ return mFocusable;
+ }
+
+ public void setFocusable(boolean focusable) {
+ mFocusable = focusable;
+ }
+ }
+
+
+ public static class MyAdapter extends ArrayAdapter<MailMessage> {
+
+ public MyAdapter(Context context, int resource,
+ List<MailMessage> objects) {
+ super(context, resource, objects);
+ }
+
+ final String mimeType = "text/html";
+ final String encoding = "utf-8";
+
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ MailMessage message = getItem(position);
+
+ LayoutInflater inflater = (LayoutInflater)
+ getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ LinearLayout messageUi = (LinearLayout) inflater
+ .inflate(R.layout.mail_message, null);
+
+ TextView subject = (TextView) messageUi.findViewById(R.id.subject);
+ subject.setText(message.getSubject());
+
+ WebView body = (WebView) messageUi.findViewById(R.id.body);
+ body.loadData(message.getBody(), mimeType, encoding);
+// body.setText(message.getBody());
+ body.setFocusable(message.isFocusable());
+
+ return messageUi;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocus.java b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
new file mode 100644
index 0000000..af9ee17
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocus.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import android.view.View;
+
+/**
+ * Exercises cases where elements of the UI are requestFocus()ed.
+ */
+public class RequestFocus extends Activity {
+ protected final Handler mHandler = new Handler();
+
+ @Override protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.focus_after_removal);
+
+ // bottom right button starts with the focus.
+ final Button bottomRightButton = (Button) findViewById(R.id.bottomRightButton);
+ bottomRightButton.requestFocus();
+ bottomRightButton.setText("I should have focus");
+ }
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
new file mode 100644
index 0000000..477831e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.RequestFocus;
+import com.android.frameworks.coretests.R;
+
+import android.os.Handler;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.util.AndroidRuntimeException;
+
+/**
+ * {@link RequestFocusTest} is set up to exercise cases where the views that
+ * have focus become invisible or GONE.
+ */
+public class RequestFocusTest extends ActivityInstrumentationTestCase<RequestFocus> {
+
+ private Button mTopLeftButton;
+ private Button mBottomLeftButton;
+ private Button mTopRightButton;
+ private Button mBottomRightButton;
+ private Handler mHandler;
+
+ public RequestFocusTest() {
+ super("com.android.frameworks.coretests", RequestFocus.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final RequestFocus a = getActivity();
+ mHandler = a.getHandler();
+ mTopLeftButton = (Button) a.findViewById(R.id.topLeftButton);
+ mBottomLeftButton = (Button) a.findViewById(R.id.bottomLeftButton);
+ mTopRightButton = (Button) a.findViewById(R.id.topRightButton);
+ mBottomRightButton = (Button) a.findViewById(R.id.bottomRightButton);
+ }
+
+ // Test that setUp did what we expect it to do. These asserts
+ // can't go in SetUp, or the test will hang.
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mHandler);
+ assertNotNull(mTopLeftButton);
+ assertNotNull(mTopRightButton);
+ assertNotNull(mBottomLeftButton);
+ assertNotNull(mBottomRightButton);
+ assertTrue("requestFocus() should work from onCreate.", mBottomRightButton.hasFocus());
+ }
+
+ // Test that a posted requestFocus works.
+ @LargeTest
+ public void testPostedRequestFocus() throws Exception {
+ mHandler.post(new Runnable() { public void run() {
+ mBottomLeftButton.requestFocus();
+ }});
+ synchronized(this) {
+ try {
+ wait(500);
+ } catch (InterruptedException e) {
+ // Don't care.
+ }
+ }
+ assertTrue("Focus should move to bottom left", mBottomLeftButton.hasFocus());
+ }
+
+ // Test that a requestFocus from the wrong thread fails.
+ @MediumTest
+ public void testWrongThreadRequestFocusFails() throws Exception {
+ try {
+ mTopRightButton.requestFocus();
+ fail("requestFocus from wrong thread should raise exception.");
+ } catch (AndroidRuntimeException e) {
+ // Expected. The actual exception is not public, so we can't catch it.
+ assertEquals("android.view.ViewRoot$CalledFromWrongThreadException",
+ e.getClass().getName());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
new file mode 100644
index 0000000..eb9192a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.graphics.Rect;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.focus.ListOfInternalSelectionViews;
+import android.util.InternalSelectionView;
+
+
+/**
+ * TODO: extract base test case that launches {@link ListOfInternalSelectionViews} with
+ * bundle params.
+ */
+public class ScrollingThroughListOfFocusablesTest extends InstrumentationTestCase {
+
+ Rect mTempRect = new Rect();
+
+ private ListOfInternalSelectionViews mActivity;
+ private ListView mListView;
+
+ private int mNumItems = 4;
+ private int mNumRowsPerItem = 5;
+ private double mScreenHeightFactor = 5 /4;
+
+ @Override
+ protected void setUp() throws Exception {
+ mActivity = launchActivity(
+ "com.android.frameworks.coretests",
+ ListOfInternalSelectionViews.class,
+ ListOfInternalSelectionViews.getBundleFor(
+ mNumItems, // 4 items
+ mNumRowsPerItem, // 5 internally selectable rows per item
+ mScreenHeightFactor)); // each item is 5 / 4 screen height tall
+ mListView = mActivity.getListView();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ mActivity.finish();
+ super.tearDown();
+ }
+
+ @MediumTest
+ public void testPreconditions() throws Exception {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ assertEquals(mNumItems, mActivity.getNumItems());
+ assertEquals(mNumRowsPerItem, mActivity.getNumRowsPerItem());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @MediumTest
+ public void testScrollingDownInFirstItem() throws Exception {
+
+ for (int i = 0; i < mNumRowsPerItem; i++) {
+ assertEquals(0, mListView.getSelectedItemPosition());
+ InternalSelectionView view = mActivity.getSelectedView();
+
+ assertInternallySelectedRowOnScreen(view, i);
+
+ // move to next row
+ if (i < mNumRowsPerItem - 1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ }
+ }
+
+ {
+ assertEquals(0, mListView.getSelectedItemPosition());
+ InternalSelectionView view = (InternalSelectionView)
+ mListView.getSelectedView();
+
+ // 1 pixel tolerance in case height / 4 is not an even number
+ final int fadingEdge = mListView.getBottom() - mListView.getVerticalFadingEdgeLength();
+ assertTrue("bottom of view should be just above fading edge",
+ view.getBottom() >= fadingEdge - 1 &&
+ view.getBottom() <= fadingEdge);
+ }
+
+
+ // make sure fading edge is the expected view
+ {
+ assertEquals("should be a second view visible due to the fading edge",
+ 2, mListView.getChildCount());
+ InternalSelectionView peekingChild = (InternalSelectionView)
+ mListView.getChildAt(1);
+ assertNotNull(peekingChild);
+ assertEquals("wrong value for peeking list item",
+ mActivity.getLabelForPosition(1), peekingChild.getLabel());
+ }
+ }
+
+
+ @MediumTest
+ public void testScrollingToSecondItem() throws Exception {
+
+ for (int i = 0; i < mNumRowsPerItem; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ assertEquals("should have moved to second item",
+ 1, mListView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testNoFadingEdgeAtBottomOfLastItem() {
+
+ // move down to last item
+ for (int i = 0; i < mNumItems; i++) {
+ for (int j = 0; j < mNumRowsPerItem; j++) {
+ if (i < mNumItems - 1 || j < mNumRowsPerItem - 1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ }
+ }
+ }
+
+ assertEquals(mNumItems - 1, mListView.getSelectedItemPosition());
+ InternalSelectionView view = mActivity.getSelectedView();
+ assertEquals(mNumRowsPerItem - 1, view.getSelectedRow());
+
+ view.getRectForRow(mTempRect, mNumRowsPerItem - 1);
+ mListView.offsetDescendantRectToMyCoords(view, mTempRect);
+
+ assertTrue("bottom of last row of last item should be at " +
+ "the bottom of the list view (no fading edge)",
+ mListView.getBottom() - mListView.getVerticalFadingEdgeLength() < mTempRect.bottom);
+ }
+
+ @LargeTest
+ public void testNavigatingUpThroughInternalSelection() throws Exception {
+
+ // get to bottom of second item
+ for (int i = 0; i < 2; i++) {
+ for (int j = 0; j < mNumRowsPerItem; j++) {
+ if (i < 1 || j < mNumRowsPerItem - 1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ }
+ }
+ }
+
+
+ // (make sure we are at last row of second item)
+ {
+ assertEquals(1, mListView.getSelectedItemPosition());
+ InternalSelectionView view = mActivity.getSelectedView();
+ assertEquals(mNumRowsPerItem - 1, view.getSelectedRow());
+ }
+
+ // go back up to the top of the second item
+ for (int i = mNumRowsPerItem - 1; i >= 0; i--) {
+ assertEquals(1, mListView.getSelectedItemPosition());
+ InternalSelectionView view = mActivity.getSelectedView();
+
+ assertInternallySelectedRowOnScreen(view, i);
+
+ // move up to next row
+ if (i > 0) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+ }
+ }
+
+ // now we are at top row, should have caused scrolling, and fading edge...
+ {
+ assertEquals(1, mListView.getSelectedItemPosition());
+ InternalSelectionView view = mActivity.getSelectedView();
+ assertEquals(0, view.getSelectedRow());
+
+ view.getDrawingRect(mTempRect);
+ mListView.offsetDescendantRectToMyCoords(view, mTempRect);
+ assertEquals("top of selected row should be just below top vertical fading edge",
+ mListView.getVerticalFadingEdgeLength(),
+ view.getTop());
+ }
+
+ // make sure fading edge is the view we expect
+ {
+ final InternalSelectionView view =
+ (InternalSelectionView) mListView.getChildAt(0);
+ assertEquals(mActivity.getLabelForPosition(0), view.getLabel());
+ }
+
+
+ }
+
+ /**
+ * @param internalFocused The view to check
+ * @param row
+ */
+ private void assertInternallySelectedRowOnScreen(
+ InternalSelectionView internalFocused,
+ int row) {
+ assertEquals("expecting selected row",
+ row, internalFocused.getSelectedRow());
+
+ internalFocused.getRectForRow(mTempRect, row);
+ mListView.offsetDescendantRectToMyCoords(internalFocused, mTempRect);
+
+ assertTrue("top of row " + row + " should be on sreen",
+ mTempRect.top >= 0);
+ assertTrue("bottom of row " + row + " should be on sreen",
+ mTempRect.bottom < mActivity.getScreenHeight());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearch.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearch.java
new file mode 100644
index 0000000..deb9e67
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearch.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.Button;
+import android.widget.TextView;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.content.Context;
+
+/**
+ * Holds a few buttons of various sizes and horizontal placements in a
+ * vertical layout to excercise some core focus searching.
+ */
+public class VerticalFocusSearch extends Activity {
+
+ private LinearLayout mLayout;
+
+ private Button mTopWide;
+ private Button mMidSkinny1Left;
+ private Button mBottomWide;
+
+ private Button mMidSkinny2Right;
+
+
+ public LinearLayout getLayout() {
+ return mLayout;
+ }
+
+ public Button getTopWide() {
+ return mTopWide;
+ }
+
+ public Button getMidSkinny1Left() {
+ return mMidSkinny1Left;
+ }
+
+ public Button getMidSkinny2Right() {
+ return mMidSkinny2Right;
+ }
+
+ public Button getBottomWide() {
+ return mBottomWide;
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mLayout = new LinearLayout(this);
+ mLayout.setOrientation(LinearLayout.VERTICAL);
+ mLayout.setHorizontalGravity(Gravity.LEFT);
+ mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ mTopWide = makeWide("top wide");
+ mLayout.addView(mTopWide);
+
+ mMidSkinny1Left = addSkinny(mLayout, "mid skinny 1(L)", false);
+
+ mMidSkinny2Right = addSkinny(mLayout, "mid skinny 2(R)", true);
+
+ mBottomWide = makeWide("bottom wide");
+ mLayout.addView(mBottomWide);
+
+ setContentView(mLayout);
+ }
+
+ // just to get toString non-sucky
+ private static class MyButton extends Button {
+
+ public MyButton(Context context) {
+ super(context);
+ }
+
+
+ @Override
+ public String toString() {
+ return getText().toString();
+ }
+ }
+
+ private Button makeWide(String label) {
+ Button button = new MyButton(this);
+ button.setText(label);
+ button.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ return button;
+ }
+
+ /**
+ * Add a skinny button that takes up just less than half of the screen
+ * horizontally.
+ * @param root The layout to add the button to.
+ * @param label The label of the button.
+ * @param atRight Which side to put the button on.
+ * @return The newly created button.
+ */
+ private Button addSkinny(LinearLayout root, String label, boolean atRight) {
+ Button button = new MyButton(this);
+ button.setText(label);
+ button.setLayoutParams(new LinearLayout.LayoutParams(
+ 0, // width
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 480));
+
+ TextView filler = new TextView(this);
+ filler.setText("filler");
+ filler.setLayoutParams(new LinearLayout.LayoutParams(
+ 0, // width
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 520));
+
+ LinearLayout ll = new LinearLayout(this);
+ ll.setOrientation(LinearLayout.HORIZONTAL);
+ ll.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ if (atRight) {
+ ll.addView(filler);
+ ll.addView(button);
+ root.addView(ll);
+ } else {
+ ll.addView(button);
+ ll.addView(filler);
+ root.addView(ll);
+ }
+ return button;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
new file mode 100644
index 0000000..f05d83a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.focus;
+
+import android.widget.focus.VerticalFocusSearch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.FocusFinder;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * Tests that focus searching works on a vertical linear layout of buttons of
+ * various widths and horizontal placements.
+ */
+// Suppress until bug http://b/issue?id=1416545 is fixed
+@Suppress
+public class VerticalFocusSearchTest extends ActivityInstrumentationTestCase<VerticalFocusSearch> {
+
+ private LinearLayout mLayout;
+
+ private Button mTopWide;
+ private Button mMidSkinny1Left;
+ private Button mMidSkinny2Right;
+ private Button mBottomWide;
+
+ private FocusSearchAlg mFocusFinder;
+
+ // helps test old and new impls when figuring out why something that used
+ // to work doesn't anymore (or verifying that new works as well as old).
+ interface FocusSearchAlg {
+ View findNextFocus(ViewGroup root, View focused, int direction);
+ }
+
+ // calls new impl
+ static class NewFocusSearchAlg implements FocusSearchAlg {
+
+ public View findNextFocus(ViewGroup root, View focused, int direction) {
+ return FocusFinder.getInstance()
+ .findNextFocus(root, focused, direction);
+ }
+ }
+
+ public VerticalFocusSearchTest() {
+ super("com.android.frameworks.coretests", VerticalFocusSearch.class);
+ }
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLayout = getActivity().getLayout();
+ mTopWide = getActivity().getTopWide();
+ mMidSkinny1Left = getActivity().getMidSkinny1Left();
+ mMidSkinny2Right = getActivity().getMidSkinny2Right();
+ mBottomWide = getActivity().getBottomWide();
+
+ mFocusFinder = new NewFocusSearchAlg();
+ }
+
+ public void testPreconditions() {
+ assertNotNull(mLayout);
+ assertNotNull(mTopWide);
+ assertNotNull(mMidSkinny1Left);
+ assertNotNull(mMidSkinny2Right);
+ assertNotNull(mBottomWide);
+ }
+
+ public void testSearchFromTopButton() {
+ assertNull("going up from mTopWide.",
+ mFocusFinder.findNextFocus(mLayout, mTopWide, View.FOCUS_UP));
+
+ assertNull("going left from mTopWide.",
+ mFocusFinder.findNextFocus(mLayout, mTopWide, View.FOCUS_LEFT));
+
+ assertNull("going right from mTopWide.",
+ mFocusFinder.findNextFocus(mLayout, mTopWide, View.FOCUS_RIGHT));
+
+ assertEquals("going down from mTopWide.",
+ mMidSkinny1Left,
+ mFocusFinder
+ .findNextFocus(mLayout, mTopWide, View.FOCUS_DOWN));
+ }
+
+ public void testSearchFromMidLeft() {
+ assertNull("going left should have no next focus",
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny1Left, View.FOCUS_LEFT));
+
+ assertEquals("going right from mMidSkinny1Left should go to mMidSkinny2Right",
+ mMidSkinny2Right,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny1Left, View.FOCUS_RIGHT));
+
+ assertEquals("going up from mMidSkinny1Left should go to mTopWide",
+ mTopWide,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny1Left, View.FOCUS_UP));
+
+ assertEquals("going down from mMidSkinny1Left should go to mMidSkinny2Right",
+ mMidSkinny2Right,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny1Left, View.FOCUS_DOWN));
+ }
+
+ public void testSearchFromMidRight() {
+ assertEquals("going left from mMidSkinny2Right should go to mMidSkinny1Left",
+ mMidSkinny1Left,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny2Right, View.FOCUS_LEFT));
+
+ assertNull("going right should have no next focus",
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny2Right, View.FOCUS_RIGHT));
+
+ assertEquals("going up from mMidSkinny2Right should go to mMidSkinny1Left",
+ mMidSkinny1Left,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny2Right, View.FOCUS_UP));
+
+ assertEquals("going down from mMidSkinny2Right should go to mBottomWide",
+ mBottomWide,
+ mFocusFinder.findNextFocus(mLayout, mMidSkinny2Right, View.FOCUS_DOWN));
+
+ }
+
+ public void testSearchFromFromBottom() {
+ assertNull("going down from bottom button should have no next focus.",
+ mFocusFinder.findNextFocus(mLayout, mBottomWide, View.FOCUS_DOWN));
+
+ assertNull("going left from bottom button should have no next focus.",
+ mFocusFinder.findNextFocus(mLayout, mBottomWide, View.FOCUS_LEFT));
+
+ assertNull("going right from bottom button should have no next focus.",
+ mFocusFinder.findNextFocus(mLayout, mBottomWide, View.FOCUS_RIGHT));
+
+ assertEquals("going up from bottom button should go to mMidSkinny2Right.",
+ mMidSkinny2Right,
+ mFocusFinder.findNextFocus(mLayout, mBottomWide, View.FOCUS_UP));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridDelete.java b/core/tests/coretests/src/android/widget/gridview/GridDelete.java
new file mode 100644
index 0000000..57ae8f3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridDelete.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ListAdapter;
+
+import android.util.GridScenario;
+
+import java.util.ArrayList;
+
+/**
+ * A grid with vertical spacing between rows
+ */
+public class GridDelete extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(1001)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.20)
+ .setVerticalSpacing(20);
+ }
+
+
+
+ @Override
+ protected ListAdapter createAdapter() {
+ return new DeleteAdapter(getInitialNumItems());
+ }
+
+
+
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_DEL) {
+ GridView g = getGridView();
+ ((DeleteAdapter)g.getAdapter()).deletePosition(g.getSelectedItemPosition());
+ return true;
+ } else {
+ return super.onKeyDown(keyCode, event);
+ }
+ }
+
+
+
+
+ private class DeleteAdapter extends BaseAdapter {
+
+ private ArrayList<Integer> mData;
+
+ public DeleteAdapter(int initialNumItems) {
+ super();
+ mData = new ArrayList<Integer>(initialNumItems);
+
+ int i;
+ for (i=0; i<initialNumItems; ++i) {
+ mData.add(new Integer(10000 + i));
+ }
+
+ }
+
+ public void deletePosition(int selectedItemPosition) {
+ if (selectedItemPosition >=0 && selectedItemPosition < mData.size()) {
+ mData.remove(selectedItemPosition);
+ notifyDataSetChanged();
+ }
+
+ }
+
+ public int getCount() {
+ return mData.size();
+ }
+
+ public Object getItem(int position) {
+ return mData.get(position);
+ }
+
+ public long getItemId(int position) {
+ return mData.get(position);
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ int desiredHeight = getDesiredItemHeight();
+ return createView(mData.get(position), parent, desiredHeight);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridInHorizontal.java b/core/tests/coretests/src/android/widget/gridview/GridInHorizontal.java
new file mode 100644
index 0000000..493c2cd
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridInHorizontal.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a grid in a horizontal linear layout
+ */
+public class GridInHorizontal extends Activity {
+ Handler mHandler = new Handler();
+ TextView mText;
+ GridView mGridView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.grid_in_horizontal);
+
+ String values[] = new String[1000];
+ int i=0;
+ for(i=0; i<1000; i++) {
+ values[i] = ((Integer)i).toString();
+ }
+
+ mText = (TextView) findViewById(R.id.text);
+ mGridView = (GridView) findViewById(R.id.grid);
+ mGridView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ }
+
+ public GridView getGridView() {
+ return mGridView;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridInHorizontalTest.java b/core/tests/coretests/src/android/widget/gridview/GridInHorizontalTest.java
new file mode 100644
index 0000000..21ca655
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridInHorizontalTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+
+import android.widget.gridview.GridInHorizontal;
+
+public class GridInHorizontalTest extends ActivityInstrumentationTestCase<GridInHorizontal> {
+
+ private GridInHorizontal mActivity;
+ private GridView mGridView;
+
+ public GridInHorizontalTest() {
+ super("com.android.frameworks.coretests", GridInHorizontal.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+ assertTrue("Grid has 0 width", mGridView.getMeasuredWidth() > 0);
+ assertTrue("Grid has 0 height", mGridView.getMeasuredHeight() > 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridInVertical.java b/core/tests/coretests/src/android/widget/gridview/GridInVertical.java
new file mode 100644
index 0000000..aeceb23
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridInVertical.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a grid in a vertical linear layout
+ */
+public class GridInVertical extends Activity {
+ Handler mHandler = new Handler();
+ TextView mText;
+ GridView mGridView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.grid_in_vertical);
+
+ String values[] = new String[1000];
+ int i=0;
+ for(i=0; i<1000; i++) {
+ values[i] = ((Integer)i).toString();
+ }
+
+ mText = (TextView) findViewById(R.id.text);
+ mGridView = (GridView) findViewById(R.id.grid);
+ mGridView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ }
+
+ public GridView getGridView() {
+ return mGridView;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridInVerticalTest.java b/core/tests/coretests/src/android/widget/gridview/GridInVerticalTest.java
new file mode 100644
index 0000000..a674db2
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridInVerticalTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+
+import android.widget.gridview.GridInVertical;
+
+public class GridInVerticalTest extends ActivityInstrumentationTestCase<GridInVertical> {
+
+ private GridInVertical mActivity;
+ private GridView mGridView;
+
+ public GridInVerticalTest() {
+ super("com.android.frameworks.coretests", GridInVertical.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+ assertTrue("Grid has 0 width", mGridView.getMeasuredWidth() > 0);
+ assertTrue("Grid has 0 height", mGridView.getMeasuredHeight() > 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridPadding.java b/core/tests/coretests/src/android/widget/gridview/GridPadding.java
new file mode 100644
index 0000000..0b9e4c5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridPadding.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a grid with padding
+ */
+public class GridPadding extends Activity {
+ private GridView mGridView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.grid_padding);
+
+ String values[] = new String[1000];
+ for(int i = 0; i < 1000; i++) {
+ values[i] = String.valueOf(i);
+ }
+
+ mGridView = (GridView) findViewById(R.id.grid);
+ mGridView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+ }
+
+ public GridView getGridView() {
+ return mGridView;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridPaddingTest.java b/core/tests/coretests/src/android/widget/gridview/GridPaddingTest.java
new file mode 100644
index 0000000..ecd4b1c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridPaddingTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+
+public class GridPaddingTest extends ActivityInstrumentationTestCase2<GridPadding> {
+ private GridView mGridView;
+
+ public GridPaddingTest() {
+ super("com.android.frameworks.coretests", GridPadding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setActivityInitialTouchMode(true);
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mGridView);
+ assertTrue("Not in touch mode", mGridView.isInTouchMode());
+ }
+
+ @MediumTest
+ public void testResurrectSelection() {
+ sendKeys("DPAD_DOWN");
+ assertEquals("The first item should be selected", mGridView.getSelectedItemPosition(), 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridScrollListener.java b/core/tests/coretests/src/android/widget/gridview/GridScrollListener.java
new file mode 100644
index 0000000..4290941
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridScrollListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.AbsListView;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises change notification in a list
+ */
+public class GridScrollListener extends Activity implements AbsListView.OnScrollListener {
+ Handler mHandler = new Handler();
+ TextView mText;
+ GridView mGridView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.grid_scroll_listener);
+
+ String values[] = new String[1000];
+ int i=0;
+ for(i=0; i<1000; i++) {
+ values[i] = ((Integer)i).toString();
+ }
+
+ mText = (TextView) findViewById(R.id.text);
+ mGridView = (GridView) findViewById(R.id.grid);
+ mGridView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ mGridView.setOnScrollListener(this);
+ }
+
+ public GridView getGridView() {
+ return mGridView;
+ }
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ int lastItem = firstVisibleItem + visibleItemCount - 1;
+ mText.setText("Showing " + firstVisibleItem + "-" + lastItem + "/" + totalItemCount);
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
new file mode 100644
index 0000000..8f62b2c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.KeyEvent;
+import android.widget.AbsListView;
+import android.widget.GridView;
+
+import android.widget.gridview.GridScrollListener;
+
+public class GridScrollListenerTest extends ActivityInstrumentationTestCase<GridScrollListener> implements
+ AbsListView.OnScrollListener {
+ private GridScrollListener mActivity;
+ private GridView mGridView;
+ private int mFirstVisibleItem = -1;
+ private int mVisibleItemCount = -1;
+ private int mTotalItemCount = -1;
+
+ public GridScrollListenerTest() {
+ super("com.android.frameworks.coretests", GridScrollListener.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ mGridView.setOnScrollListener(this);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ assertEquals(0, mFirstVisibleItem);
+ }
+
+ @LargeTest
+ public void testKeyScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisibleItem = mFirstVisibleItem;
+ for (int i = 0; i < mVisibleItemCount * 2; i++) {
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ inst.waitForIdleSync();
+
+ assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+
+ firstVisibleItem = mFirstVisibleItem;
+ inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+ inst.waitForIdleSync();
+
+ assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+
+ firstVisibleItem = mFirstVisibleItem;
+ KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
+ KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
+ inst.sendKeySync(down);
+ inst.sendKeySync(up);
+ inst.waitForIdleSync();
+
+ assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+ assertEquals("Full scroll did not happen", mTotalItemCount,
+ mFirstVisibleItem + mVisibleItemCount);
+ }
+
+ @LargeTest
+ public void testTouchScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisibleItem = mFirstVisibleItem;
+ TouchUtils.dragQuarterScreenUp(this);
+ TouchUtils.dragQuarterScreenUp(this);
+ assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+ }
+
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ mFirstVisibleItem = firstVisibleItem;
+ mVisibleItemCount = visibleItemCount;
+ mTotalItemCount = totalItemCount;
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+}
diff --git a/core/java/android/content/TempProviderSyncResult.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelection.java
index 81f6f79..a3cda3b 100644
--- a/core/java/android/content/TempProviderSyncResult.java
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelection.java
@@ -14,23 +14,22 @@
* limitations under the License.
*/
-package android.content;
+package android.widget.gridview;
+
+import android.util.GridScenario;
/**
- * Used to hold data returned from a given phase of a TempProviderSync.
- * @hide
+ * Basic stacking from top scenario, nothing fancy. Items do not
+ * fill the screen.
*/
-public class TempProviderSyncResult {
- /**
- * An interface to a temporary content provider that contains
- * the result of updates that were sent to the server. This
- * provider must be merged into the permanent content provider.
- * This may be null, which indicates that there is nothing to
- * merge back into the content provider.
- */
- public SyncableContentProvider tempContentProvider;
-
- public TempProviderSyncResult() {
- tempContentProvider = null;
+public class GridSetSelection extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(15)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
}
}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionBaseTest.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionBaseTest.java
new file mode 100644
index 0000000..0e362b6
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionBaseTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.widget.GridView;
+
+public class GridSetSelectionBaseTest<T extends GridScenario> extends ActivityInstrumentationTestCase<T> {
+ private T mActivity;
+ private GridView mGridView;
+
+ protected GridSetSelectionBaseTest(Class<T> klass) {
+ super("com.android.frameworks.coretests", klass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // First item should be selected
+ if (mGridView.isStackFromBottom()) {
+ assertEquals(mGridView.getAdapter().getCount() - 1,
+ mGridView.getSelectedItemPosition());
+ } else {
+ assertEquals(0, mGridView.getSelectedItemPosition());
+ }
+ }
+
+ @MediumTest
+ public void testSetSelectionToTheEnd() {
+ final int target = mGridView.getAdapter().getCount() - 1;
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelection(target);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals(mGridView.getSelectedItemPosition(), target);
+ assertNotNull(mGridView.getSelectedView());
+
+ ViewAsserts.assertOnScreen(mGridView, mGridView.getSelectedView());
+ }
+
+ @MediumTest
+ public void testSetSelectionToMiddle() {
+ final int target = mGridView.getAdapter().getCount() / 2;
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelection(target);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals(mGridView.getSelectedItemPosition(), target);
+ assertNotNull(mGridView.getSelectedView());
+
+ ViewAsserts.assertOnScreen(mGridView, mGridView.getSelectedView());
+ }
+
+ @MediumTest
+ public void testSetSelectionToTheTop() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelection(0);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals(mGridView.getSelectedItemPosition(), 0);
+ assertNotNull(mGridView.getSelectedView());
+
+ ViewAsserts.assertOnScreen(mGridView, mGridView.getSelectedView());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionMany.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionMany.java
new file mode 100644
index 0000000..a6d481f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionMany.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * Basic stacking from top scenario, nothing fancy. Items do
+ * fill the screen.
+ */
+public class GridSetSelectionMany extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(150)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionManyTest.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionManyTest.java
new file mode 100644
index 0000000..6739645
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionManyTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridSetSelectionMany;
+
+public class GridSetSelectionManyTest extends GridSetSelectionBaseTest<GridSetSelectionMany> {
+ public GridSetSelectionManyTest() {
+ super(GridSetSelectionMany.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottom.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottom.java
new file mode 100644
index 0000000..dfcd5fc
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottom.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * Basic stacking from bottom scenario, nothing fancy. Items do not
+ * fill the screen.
+ */
+public class GridSetSelectionStackFromBottom extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(15)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomMany.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomMany.java
new file mode 100644
index 0000000..26a567e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomMany.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * Basic stacking from bottom scenario, nothing fancy. Items do
+ * fill the screen.
+ */
+public class GridSetSelectionStackFromBottomMany extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(150)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomManyTest.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomManyTest.java
new file mode 100644
index 0000000..46922b9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomManyTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridSetSelectionStackFromBottomMany;
+
+public class GridSetSelectionStackFromBottomManyTest extends GridSetSelectionBaseTest<GridSetSelectionStackFromBottomMany> {
+ public GridSetSelectionStackFromBottomManyTest() {
+ super(GridSetSelectionStackFromBottomMany.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomTest.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomTest.java
new file mode 100644
index 0000000..67dd6f1
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionStackFromBottomTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridSetSelectionStackFromBottom;
+
+public class GridSetSelectionStackFromBottomTest extends GridSetSelectionBaseTest<GridSetSelectionStackFromBottom> {
+ public GridSetSelectionStackFromBottomTest() {
+ super(GridSetSelectionStackFromBottom.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSetSelectionTest.java b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionTest.java
new file mode 100644
index 0000000..2127b3c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSetSelectionTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridSetSelection;
+
+public class GridSetSelectionTest extends GridSetSelectionBaseTest<GridSetSelection> {
+ public GridSetSelectionTest() {
+ super(GridSetSelection.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSimple.java b/core/tests/coretests/src/android/widget/gridview/GridSimple.java
new file mode 100644
index 0000000..7c2c696
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSimple.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.graphics.drawable.PaintDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import android.util.GridScenario;
+
+public class GridSimple extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(1000)
+ .setNumColumns(3)
+ .setItemScreenSizeFactor(0.14);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ getGridView().setSelector(new PaintDrawable(0xFFFF0000));
+ getGridView().setPadding(0, 0, 0, 0);
+ getGridView().setFadingEdgeLength(64);
+ getGridView().setVerticalFadingEdgeEnabled(true);
+ getGridView().setBackgroundColor(0xFFC0C0C0);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ View view = super.createView(position, parent, desiredHeight);
+ view.setBackgroundColor(0xFF000000);
+ ((TextView) view).setTextSize(16.0f);
+ return view;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSingleColumn.java b/core/tests/coretests/src/android/widget/gridview/GridSingleColumn.java
new file mode 100644
index 0000000..566e71b
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSingleColumn.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+import android.widget.GridView;
+
+/**
+ * A grid with vertical spacing between rows
+ */
+public class GridSingleColumn extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(101)
+ .setNumColumns(1)
+ .setColumnWidth(60)
+ .setItemScreenSizeFactor(0.20)
+ .setVerticalSpacing(20)
+ .setStretchMode(GridView.STRETCH_SPACING);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridSingleColumnTest.java b/core/tests/coretests/src/android/widget/gridview/GridSingleColumnTest.java
new file mode 100644
index 0000000..3b2504e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridSingleColumnTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+
+import android.widget.gridview.GridSingleColumn;
+
+public class GridSingleColumnTest extends ActivityInstrumentationTestCase<GridSingleColumn> {
+ private GridSingleColumn mActivity;
+ private GridView mGridView;
+
+ public GridSingleColumnTest() {
+ super("com.android.frameworks.coretests", GridSingleColumn.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+ assertEquals(0, mGridView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridStackFromBottom.java b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottom.java
new file mode 100644
index 0000000..2f0a88f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottom.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * Basic bottom stacking from bottom scenario, nothing fancy. Items do not
+ * fill the screen
+ */
+public class GridStackFromBottom extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(15)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomMany.java b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomMany.java
new file mode 100644
index 0000000..33a9592
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomMany.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * Basic bottom stacking from bottom scenario, nothing fancy. The grid items do not fit on the
+ * screen (to exercise scrolling.)
+ */
+public class GridStackFromBottomMany extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(54)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.12);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomManyTest.java b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomManyTest.java
new file mode 100644
index 0000000..640737e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomManyTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridStackFromBottomMany;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+import android.test.ActivityInstrumentationTestCase;
+
+public class GridStackFromBottomManyTest extends ActivityInstrumentationTestCase<GridStackFromBottomMany> {
+ private GridStackFromBottomMany mActivity;
+ private GridView mGridView;
+
+ public GridStackFromBottomManyTest() {
+ super("com.android.frameworks.coretests", GridStackFromBottomMany.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // Last item should be selected
+ assertEquals(mGridView.getAdapter().getCount() - 1, mGridView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomTest.java b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomTest.java
new file mode 100644
index 0000000..8fec241
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridStackFromBottomTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.widget.gridview.GridStackFromBottom;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.GridView;
+
+public class GridStackFromBottomTest extends ActivityInstrumentationTestCase<GridStackFromBottom> {
+ private GridStackFromBottom mActivity;
+ private GridView mGridView;
+
+ public GridStackFromBottomTest() {
+ super("com.android.frameworks.coretests", GridStackFromBottom.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // Last item should be selected
+ assertEquals(mGridView.getAdapter().getCount() - 1, mGridView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridThrasher.java b/core/tests/coretests/src/android/widget/gridview/GridThrasher.java
new file mode 100644
index 0000000..0ef5db9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridThrasher.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+
+import java.util.Random;
+
+/**
+ * Exercises change notification in a list
+ */
+public class GridThrasher extends Activity implements AdapterView.OnItemSelectedListener
+{
+ Handler mHandler = new Handler();
+ ThrashListAdapter mAdapter;
+ Random mRandomizer = new Random();
+ TextView mText;
+
+ Runnable mThrash = new Runnable() {
+ public void run() {
+ mAdapter.bumpVersion();
+ mHandler.postDelayed(mThrash, 500);
+ }
+ };
+
+ private class ThrashListAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+
+ /**
+ * Our data, part 1.
+ */
+ private String[] mTitles = new String[100];
+
+ /**
+ * Our data, part 2.
+ */
+ private int[] mVersion = new int[100];
+
+ public ThrashListAdapter(Context context) {
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTitles = new String[100];
+ mVersion = new int[100];
+
+ int i;
+ for (i=0; i<100; i++) {
+ mTitles[i] = "[" + i + "]";
+ mVersion[i] = 0;
+ }
+ }
+
+ public int getCount() {
+ return mTitles.length;
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+
+ if (convertView == null) {
+ view = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, null);
+ } else {
+ view = (TextView) convertView;
+ }
+ view.setText(mTitles[position] + " " + mVersion[position]);
+ return view;
+ }
+
+
+ public void bumpVersion() {
+ int position = mRandomizer.nextInt(getCount());
+ mVersion[position]++;
+ notifyDataSetChanged();
+ }
+
+
+ }
+
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.grid_thrasher);
+
+ mText = (TextView) findViewById(R.id.text);
+ mAdapter = new ThrashListAdapter(this);
+ GridView g = (GridView) findViewById(R.id.grid);
+ g.setAdapter(mAdapter);
+
+ mHandler.postDelayed(mThrash, 5000);
+
+ g.setOnItemSelectedListener(this);
+ }
+
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ mText.setText("Position " + position);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ mText.setText("Nothing");
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacing.java b/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacing.java
new file mode 100644
index 0000000..0d01d30
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacing.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * A grid with vertical spacing between rows
+ */
+public class GridVerticalSpacing extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(101)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.20)
+ .setVerticalSpacing(20);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacingStackFromBottom.java b/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacingStackFromBottom.java
new file mode 100644
index 0000000..bd68680
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/GridVerticalSpacingStackFromBottom.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview;
+
+import android.util.GridScenario;
+
+/**
+ * A grid with vertical spacing between rows that stacks from the bottom
+ */
+public class GridVerticalSpacingStackFromBottom extends GridScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(101)
+ .setNumColumns(4)
+ .setItemScreenSizeFactor(0.20)
+ .setVerticalSpacing(20);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchSetSelectionTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchSetSelectionTest.java
new file mode 100644
index 0000000..ca789af
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchSetSelectionTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.View;
+import android.widget.GridView;
+
+import android.widget.gridview.GridSimple;
+
+/**
+ * Tests setting the selection in touch mode
+ */
+public class GridTouchSetSelectionTest extends ActivityInstrumentationTestCase<GridSimple> {
+ private GridSimple mActivity;
+ private GridView mGridView;
+
+ public GridTouchSetSelectionTest() {
+ super("com.android.frameworks.coretests", GridSimple.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+ }
+
+ @LargeTest
+ public void testSetSelection() {
+ TouchUtils.dragQuarterScreenDown(this);
+ TouchUtils.dragQuarterScreenUp(this);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ final int targetPosition = mGridView.getAdapter().getCount() / 2;
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelection(targetPosition);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ boolean found = false;
+ int childCount = mGridView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ View child = mGridView.getChildAt(i);
+ if (child.getId() == targetPosition) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Selected item not visible in list", found);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java
new file mode 100644
index 0000000..f8e6ae7
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview.touch;
+
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.widget.gridview.GridStackFromBottomMany;
+
+import android.widget.GridView;
+import android.view.View;
+import android.test.ActivityInstrumentationTestCase;
+
+public class GridTouchStackFromBottomManyTest extends ActivityInstrumentationTestCase<GridStackFromBottomMany> {
+ private GridStackFromBottomMany mActivity;
+ private GridView mGridView;
+
+ public GridTouchStackFromBottomManyTest() {
+ super("com.android.frameworks.coretests", GridStackFromBottomMany.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // Last item should be selected
+ assertEquals(mGridView.getAdapter().getCount() - 1, mGridView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testScrollToTop() {
+ View firstChild;
+ TouchUtils.scrollToTop(this, mGridView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ firstChild = mGridView.getChildAt(0);
+
+ assertEquals("Item zero not the first child in the grid", 0, firstChild.getId());
+
+ assertEquals("Item zero not at the top of the grid",
+ mGridView.getListPaddingTop(), firstChild.getTop());
+ }
+
+ @MediumTest
+ public void testScrollToBottom() {
+ TouchUtils.scrollToBottom(this, mGridView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("Grid is not scrolled to the bottom", mGridView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ assertEquals("Last item is not touching the bottom edge",
+ mGridView.getHeight() - mGridView.getListPaddingBottom(), lastChild.getBottom());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomTest.java
new file mode 100644
index 0000000..d8d4e43
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview.touch;
+
+import android.widget.gridview.GridStackFromBottom;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.widget.GridView;
+import android.view.View;
+
+public class GridTouchStackFromBottomTest extends ActivityInstrumentationTestCase<GridStackFromBottom> {
+ private GridStackFromBottom mActivity;
+ private GridView mGridView;
+
+ public GridTouchStackFromBottomTest() {
+ super("com.android.frameworks.coretests", GridStackFromBottom.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // First item should be selected
+ assertEquals(mGridView.getAdapter().getCount() - 1, mGridView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testPushUp() {
+ TouchUtils.scrollToBottom(this, mGridView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("Last item not the last child in the grid",
+ mGridView.getAdapter().getCount() - 1, lastChild.getId());
+
+ assertEquals("Last item not at the bottom of the grid",
+ mGridView.getHeight() - mGridView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+ @MediumTest
+ public void testPullDown() {
+ TouchUtils.scrollToTop(this, mGridView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("Last item not the last child in the grid",
+ mGridView.getAdapter().getCount() - 1, lastChild.getId());
+
+ assertEquals("Last item not at the bottom of the grid",
+ mGridView.getHeight() - mGridView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+ @MediumTest
+ public void testPushUpFast() {
+ TouchUtils.dragViewToTop(this, mGridView.getChildAt(mGridView.getChildCount() - 1), 2);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("Last item not the last child in the grid",
+ mGridView.getAdapter().getCount() - 1, lastChild.getId());
+
+ assertEquals("Last item not at the bottom of the grid",
+ mGridView.getHeight() - mGridView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+ @MediumTest
+ public void testPullDownFast() {
+ TouchUtils.dragViewToBottom(this, mGridView.getChildAt(0), 2);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mGridView.getSelectedItemPosition());
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("Last item not the last child in the grid",
+ mGridView.getAdapter().getCount() - 1, lastChild.getId());
+
+ assertEquals("Last item not at the bottom of the grid",
+ mGridView.getHeight() - mGridView.getListPaddingBottom(), lastChild.getBottom());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingStackFromBottomTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingStackFromBottomTest.java
new file mode 100644
index 0000000..55a66d9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingStackFromBottomTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview.touch;
+
+import android.content.Context;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.GridView;
+
+import android.widget.gridview.GridVerticalSpacingStackFromBottom;
+
+public class GridTouchVerticalSpacingStackFromBottomTest extends ActivityInstrumentationTestCase<GridVerticalSpacingStackFromBottom> {
+ private GridVerticalSpacingStackFromBottom mActivity;
+ private GridView mGridView;
+ private ViewConfiguration mViewConfig;
+
+ public GridTouchVerticalSpacingStackFromBottomTest() {
+ super("com.android.frameworks.coretests", GridVerticalSpacingStackFromBottom.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ final Context context = mActivity.getApplicationContext();
+ mViewConfig = ViewConfiguration.get(context);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ // Last item should be selected
+ assertEquals(mGridView.getAdapter().getCount() - 1, mGridView.getSelectedItemPosition());
+
+ }
+
+ @MediumTest
+ public void testNoScroll() {
+ View firstChild = mGridView.getChildAt(0);
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ TouchUtils.dragViewBy(this, firstChild, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0,
+ ViewConfiguration.getTouchSlop());
+
+ View newLastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("View scrolled too early", lastTop, newLastChild.getTop());
+ assertEquals("Wrong view in last position", mGridView.getAdapter().getCount() - 1,
+ newLastChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testShortScroll() {
+ View firstChild = mGridView.getChildAt(0);
+ if (firstChild.getTop() < this.mGridView.getListPaddingTop()) {
+ firstChild = mGridView.getChildAt(1);
+ }
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ TouchUtils.dragViewBy(this, firstChild, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0,
+ mViewConfig.getScaledTouchSlop() + 1 + 10);
+
+ View newLastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ assertEquals("View scrolled to wrong position", lastTop, newLastChild.getTop() - 10);
+ assertEquals("Wrong view in last position", mGridView.getAdapter().getCount() - 1,
+ newLastChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testLongScroll() {
+ View firstChild = mGridView.getChildAt(0);
+ if (firstChild.getTop() < mGridView.getListPaddingTop()) {
+ firstChild = mGridView.getChildAt(1);
+ }
+
+ int firstTop = firstChild.getTop();
+
+ int distance = TouchUtils.dragViewBy(this, firstChild,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0,
+ (int) (mActivity.getWindowManager().getDefaultDisplay().getHeight() * 0.75f));
+
+ assertEquals("View scrolled to wrong position", firstTop
+ + (distance - mViewConfig.getScaledTouchSlop() - 1), firstChild.getTop());
+ }
+
+ @LargeTest
+ public void testManyScrolls() {
+ int originalCount = mGridView.getChildCount();
+
+ View firstChild;
+ int firstId = Integer.MIN_VALUE;
+ int firstTop = Integer.MIN_VALUE;
+ int prevId;
+ int prevTop;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenDown(this);
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mGridView.getChildCount(), originalCount + 4),
+ mGridView.getChildCount() <= originalCount + 4);
+ firstChild = mGridView.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+
+
+ firstChild = mGridView.getChildAt(0);
+ assertEquals("View scrolled to wrong position", 0, firstChild.getId());
+
+ firstId = Integer.MIN_VALUE;
+ firstTop = Integer.MIN_VALUE;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenUp(this);
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mGridView.getChildCount(), originalCount + 4),
+ mGridView.getChildCount() <= originalCount + 4);
+ firstChild = mGridView.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+ assertEquals("Grid is not scrolled to the bottom", mGridView.getAdapter().getCount() - 1,
+ lastChild.getId());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingTest.java
new file mode 100644
index 0000000..bae4ee7
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchVerticalSpacingTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.gridview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.GridView;
+
+import android.widget.gridview.GridVerticalSpacing;
+
+public class GridTouchVerticalSpacingTest extends ActivityInstrumentationTestCase<GridVerticalSpacing> {
+ private GridVerticalSpacing mActivity;
+ private GridView mGridView;
+
+ public GridTouchVerticalSpacingTest() {
+ super("com.android.frameworks.coretests", GridVerticalSpacing.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mGridView = getActivity().getGridView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mGridView);
+
+ assertEquals(0, mGridView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testNoScroll() {
+ View firstChild = mGridView.getChildAt(0);
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ int firstTop = firstChild.getTop();
+
+ TouchUtils.dragViewBy(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ 0, -(ViewConfiguration.getTouchSlop()));
+
+ View newFirstChild = mGridView.getChildAt(0);
+
+ assertEquals("View scrolled too early", firstTop, newFirstChild.getTop());
+ assertEquals("Wrong view in first position", 0, newFirstChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testShortScroll() {
+ View firstChild = mGridView.getChildAt(0);
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ int firstTop = firstChild.getTop();
+
+ TouchUtils.dragViewBy(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ 0, -(ViewConfiguration.getTouchSlop() + 1 + 10));
+
+ View newFirstChild = mGridView.getChildAt(0);
+
+ assertEquals("View scrolled to wrong position", firstTop, newFirstChild.getTop() + 10);
+ assertEquals("Wrong view in first position", 0, newFirstChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testLongScroll() {
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ int distance = TouchUtils.dragViewToY(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ mGridView.getTop());
+
+ assertEquals("View scrolled to wrong position",
+ lastTop - (distance - ViewConfiguration.getTouchSlop() - 1), lastChild.getTop());
+ }
+
+ @LargeTest
+ public void testManyScrolls() {
+ int originalCount = mGridView.getChildCount();
+
+ View firstChild;
+ int firstId = Integer.MIN_VALUE;
+ int firstTop = Integer.MIN_VALUE;
+ int prevId;
+ int prevTop;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenUp(this);
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mGridView.getChildCount(), originalCount + 4),
+ mGridView.getChildCount() <= originalCount + 4);
+ firstChild = mGridView.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+
+
+ View lastChild = mGridView.getChildAt(mGridView.getChildCount() - 1);
+ assertEquals("Grid is not scrolled to the bottom", mGridView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ firstId = Integer.MIN_VALUE;
+ firstTop = Integer.MIN_VALUE;
+ do {
+ prevId = firstId;
+ prevTop = firstTop;
+ TouchUtils.dragQuarterScreenDown(this);
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mGridView.getChildCount(), originalCount + 4),
+ mGridView.getChildCount() <= originalCount + 4);
+ firstChild = mGridView.getChildAt(0);
+ firstId = firstChild.getId();
+ firstTop = firstChild.getTop();
+ } while ((prevId != firstId) || (prevTop != firstTop));
+
+ firstChild = mGridView.getChildAt(0);
+ assertEquals("Grid is not scrolled to the top", 0, firstChild.getId());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravity.java b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravity.java
new file mode 100644
index 0000000..9791e36
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.frame;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class FrameLayoutGravity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.framelayout_gravity);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravityTest.java b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravityTest.java
new file mode 100644
index 0000000..fe4e932
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutGravityTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.frame;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.app.Activity;
+import android.view.View;
+import android.widget.layout.frame.FrameLayoutGravity;
+import com.android.frameworks.coretests.R;
+
+public class FrameLayoutGravityTest extends ActivityInstrumentationTestCase<FrameLayoutGravity> {
+ private View mLeftView;
+ private View mRightView;
+ private View mCenterHorizontalView;
+ private View mLeftCenterVerticalView;
+ private View mRighCenterVerticalView;
+ private View mCenterView;
+ private View mLeftBottomView;
+ private View mRightBottomView;
+ private View mCenterHorizontalBottomView;
+ private View mParent;
+
+ public FrameLayoutGravityTest() {
+ super("com.android.frameworks.coretests", FrameLayoutGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+
+ mParent = activity.findViewById(R.id.parent);
+
+ mLeftView = activity.findViewById(R.id.left);
+ mRightView = activity.findViewById(R.id.right);
+ mCenterHorizontalView = activity.findViewById(R.id.center_horizontal);
+
+ mLeftCenterVerticalView = activity.findViewById(R.id.left_center_vertical);
+ mRighCenterVerticalView = activity.findViewById(R.id.right_center_vertical);
+ mCenterView = activity.findViewById(R.id.center);
+
+ mLeftBottomView = activity.findViewById(R.id.left_bottom);
+ mRightBottomView = activity.findViewById(R.id.right_bottom);
+ mCenterHorizontalBottomView = activity.findViewById(R.id.center_horizontal_bottom);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mParent);
+ assertNotNull(mLeftView);
+ assertNotNull(mRightView);
+ assertNotNull(mCenterHorizontalView);
+ assertNotNull(mLeftCenterVerticalView);
+ assertNotNull(mRighCenterVerticalView);
+ assertNotNull(mCenterView);
+ assertNotNull(mLeftBottomView);
+ assertNotNull(mRightBottomView);
+ assertNotNull(mCenterHorizontalBottomView);
+ }
+
+ @MediumTest
+ public void testLeftTopAligned() throws Exception {
+ ViewAsserts.assertLeftAligned(mParent, mLeftView);
+ ViewAsserts.assertTopAligned(mParent, mLeftView);
+ }
+
+ @MediumTest
+ public void testRightTopAligned() throws Exception {
+ ViewAsserts.assertRightAligned(mParent, mRightView);
+ ViewAsserts.assertTopAligned(mParent, mRightView);
+ }
+
+ @MediumTest
+ public void testCenterHorizontalTopAligned() throws Exception {
+ ViewAsserts.assertHorizontalCenterAligned(mParent, mCenterHorizontalView);
+ ViewAsserts.assertTopAligned(mParent, mCenterHorizontalView);
+ }
+
+ @MediumTest
+ public void testLeftCenterVerticalAligned() throws Exception {
+ ViewAsserts.assertLeftAligned(mParent, mLeftCenterVerticalView);
+ ViewAsserts.assertVerticalCenterAligned(mParent, mLeftCenterVerticalView);
+ }
+
+ @MediumTest
+ public void testRightCenterVerticalAligned() throws Exception {
+ ViewAsserts.assertRightAligned(mParent, mRighCenterVerticalView);
+ ViewAsserts.assertVerticalCenterAligned(mParent, mRighCenterVerticalView);
+ }
+
+ @MediumTest
+ public void testCenterAligned() throws Exception {
+ ViewAsserts.assertHorizontalCenterAligned(mParent, mCenterView);
+ ViewAsserts.assertVerticalCenterAligned(mParent, mCenterView);
+ }
+
+ @MediumTest
+ public void testLeftBottomAligned() throws Exception {
+ ViewAsserts.assertLeftAligned(mParent, mLeftBottomView);
+ ViewAsserts.assertBottomAligned(mParent, mLeftBottomView);
+ }
+
+ @MediumTest
+ public void testRightBottomAligned() throws Exception {
+ ViewAsserts.assertRightAligned(mParent, mRightBottomView);
+ ViewAsserts.assertBottomAligned(mParent, mRightBottomView);
+ }
+
+ @MediumTest
+ public void testCenterHorizontalBottomAligned() throws Exception {
+ ViewAsserts.assertHorizontalCenterAligned(mParent, mCenterHorizontalBottomView);
+ ViewAsserts.assertBottomAligned(mParent, mCenterHorizontalBottomView);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMargin.java b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMargin.java
new file mode 100644
index 0000000..81b3ea1
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMargin.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.frame;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class FrameLayoutMargin extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.framelayout_margin);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMarginTest.java b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMarginTest.java
new file mode 100644
index 0000000..c052d65
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/frame/FrameLayoutMarginTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.frame;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.layout.frame.FrameLayoutMargin;
+import com.android.frameworks.coretests.R;
+
+public class FrameLayoutMarginTest extends ActivityInstrumentationTestCase<FrameLayoutMargin> {
+ private View mLeftView;
+ private View mRightView;
+ private View mTopView;
+ private View mBottomView;
+ private View mParent;
+
+ public FrameLayoutMarginTest() {
+ super("com.android.frameworks.coretests", FrameLayoutMargin.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+
+ mParent = activity.findViewById(R.id.parent);
+
+ mLeftView = activity.findViewById(R.id.left);
+ mRightView = activity.findViewById(R.id.right);
+ mTopView = activity.findViewById(R.id.top);
+ mBottomView = activity.findViewById(R.id.bottom);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mParent);
+ assertNotNull(mLeftView);
+ assertNotNull(mRightView);
+ assertNotNull(mTopView);
+ assertNotNull(mBottomView);
+ }
+
+ @MediumTest
+ public void testLeftMarginAligned() throws Exception {
+ ViewAsserts.assertLeftAligned(mParent, mLeftView,
+ ((ViewGroup.MarginLayoutParams) mLeftView.getLayoutParams()).leftMargin);
+ }
+
+ @MediumTest
+ public void testRightMarginAligned() throws Exception {
+ ViewAsserts.assertRightAligned(mParent, mRightView,
+ ((ViewGroup.MarginLayoutParams) mRightView.getLayoutParams()).rightMargin);
+ }
+
+ @MediumTest
+ public void testTopMarginAligned() throws Exception {
+ ViewAsserts.assertTopAligned(mParent, mTopView,
+ ((ViewGroup.MarginLayoutParams) mTopView.getLayoutParams()).topMargin);
+ }
+
+ @MediumTest
+ public void testBottomMarginAligned() throws Exception {
+ ViewAsserts.assertBottomAligned(mParent, mBottomView,
+ ((ViewGroup.MarginLayoutParams) mBottomView.getLayoutParams()).bottomMargin);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravity.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravity.java
new file mode 100644
index 0000000..766dd0a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class BaselineAlignmentCenterGravity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.baseline_center_gravity);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravityTest.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravityTest.java
new file mode 100644
index 0000000..079e9d0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentCenterGravityTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.view.View;
+import android.widget.Button;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.BaselineAlignmentCenterGravity;
+
+public class BaselineAlignmentCenterGravityTest extends ActivityInstrumentationTestCase<BaselineAlignmentCenterGravity> {
+ private Button mButton1;
+ private Button mButton2;
+ private Button mButton3;
+
+ public BaselineAlignmentCenterGravityTest() {
+ super("com.android.frameworks.coretests", BaselineAlignmentCenterGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mButton1 = (Button) activity.findViewById(R.id.button1);
+ mButton2 = (Button) activity.findViewById(R.id.button2);
+ mButton3 = (Button) activity.findViewById(R.id.button3);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mButton1);
+ assertNotNull(mButton2);
+ assertNotNull(mButton3);
+ }
+
+ @MediumTest
+ public void testChildrenAligned() throws Exception {
+ final View parent = (View) mButton1.getParent();
+ ViewAsserts.assertTopAligned(mButton1, parent);
+ ViewAsserts.assertTopAligned(mButton2, parent);
+ ViewAsserts.assertTopAligned(mButton3, parent);
+ ViewAsserts.assertBottomAligned(mButton1, parent);
+ ViewAsserts.assertBottomAligned(mButton2, parent);
+ ViewAsserts.assertBottomAligned(mButton3, parent);
+ ViewAsserts.assertTopAligned(mButton1, mButton2);
+ ViewAsserts.assertTopAligned(mButton2, mButton3);
+ ViewAsserts.assertBottomAligned(mButton1, mButton2);
+ ViewAsserts.assertBottomAligned(mButton2, mButton3);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentSpinnerButton.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentSpinnerButton.java
new file mode 100644
index 0000000..c3bfe3a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentSpinnerButton.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.HorizontalOrientationVerticalAlignment;
+
+public class BaselineAlignmentSpinnerButton extends ActivityInstrumentationTestCase<HorizontalOrientationVerticalAlignment> {
+ private View mSpinner;
+ private View mButton;
+
+ public BaselineAlignmentSpinnerButton() {
+ super("com.android.frameworks.coretests", HorizontalOrientationVerticalAlignment.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mSpinner = activity.findViewById(R.id.reminder_value);
+ mButton = activity.findViewById(R.id.reminder_remove);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mSpinner);
+ assertNotNull(mButton);
+ }
+
+ @MediumTest
+ public void testChildrenAligned() throws Exception {
+ ViewAsserts.assertBaselineAligned(mSpinner, mButton);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeight.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeight.java
new file mode 100644
index 0000000..5ed5e71
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeight.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class BaselineAlignmentZeroWidthAndWeight extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.baseline_0width_and_weight);
+
+ findViewById(R.id.show).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ findViewById(R.id.layout).setVisibility(View.VISIBLE);
+ }
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeightTest.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeightTest.java
new file mode 100644
index 0000000..2dd2bb8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineAlignmentZeroWidthAndWeightTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.BaselineAlignmentZeroWidthAndWeight;
+import android.widget.layout.linear.ExceptionTextView;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+
+public class BaselineAlignmentZeroWidthAndWeightTest extends ActivityInstrumentationTestCase<BaselineAlignmentZeroWidthAndWeight> {
+ private Button mShowButton;
+
+ public BaselineAlignmentZeroWidthAndWeightTest() {
+ super("com.android.frameworks.coretests", BaselineAlignmentZeroWidthAndWeight.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mShowButton = (Button) activity.findViewById(R.id.show);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mShowButton);
+ }
+
+ @MediumTest
+ public void testComputeTexViewWithoutIllegalArgumentException() throws Exception {
+ assertTrue(mShowButton.hasFocus());
+
+ // Pressing the button will show an ExceptionTextView that might set a failed bit if
+ // the test fails.
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+
+ final ExceptionTextView etv = (ExceptionTextView) getActivity()
+ .findViewById(R.id.routeToField);
+ assertFalse("exception test view should not fail", etv.isFailed());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineButtons.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineButtons.java
new file mode 100644
index 0000000..c9ad831
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineButtons.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class BaselineButtons extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.baseline_buttons);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/BaselineButtonsTest.java b/core/tests/coretests/src/android/widget/layout/linear/BaselineButtonsTest.java
new file mode 100644
index 0000000..6f1fc90
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/BaselineButtonsTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.BaselineButtons;
+
+public class BaselineButtonsTest extends ActivityInstrumentationTestCase<BaselineButtons> {
+ private View mCurrentTime;
+ private View mTotalTime;
+ private ImageButton mPrev;
+ private ImageButton mNext;
+ private ImageButton mPause;
+ private View mLayout;
+
+ public BaselineButtonsTest() {
+ super("com.android.frameworks.coretests", BaselineButtons.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mCurrentTime = activity.findViewById(R.id.currenttime);
+ mTotalTime = activity.findViewById(R.id.totaltime);
+ mPrev = (ImageButton) activity.findViewById(R.id.prev);
+ mNext = (ImageButton) activity.findViewById(R.id.next);
+ mPause = (ImageButton) activity.findViewById(R.id.pause);
+ mLayout = activity.findViewById(R.id.layout);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mCurrentTime);
+ assertNotNull(mTotalTime);
+ assertNotNull(mPrev);
+ assertNotNull(mNext);
+ assertNotNull(mPause);
+ assertNotNull(mLayout);
+ }
+
+ @MediumTest
+ public void testLayout() {
+ int pauseHeight = Math.max(mPause.getDrawable().getIntrinsicHeight()
+ + mPause.getPaddingTop() + mPause.getPaddingBottom(),
+ mPause.getBackground().getMinimumHeight());
+ int prevHeight = Math.max(mPrev.getDrawable().getIntrinsicHeight() + mPrev.getPaddingTop()
+ + mPrev.getPaddingBottom(),
+ mPrev.getBackground().getMinimumHeight());
+ int nextHeight = Math.max(mNext.getDrawable().getIntrinsicHeight() + mNext.getPaddingTop()
+ + mNext.getPaddingBottom(),
+ mNext.getBackground().getMinimumHeight());
+ assertEquals("Layout incorrect height", pauseHeight, mLayout.getHeight());
+ assertEquals("Pause incorrect height", pauseHeight, mPause.getHeight());
+ assertEquals("Prev incorrect height", prevHeight, mPrev.getHeight());
+ assertEquals("Next incorrect height", nextHeight, mNext.getHeight());
+ assertEquals("Pause wrong top", 0, mPause.getTop());
+ assertEquals("Prev wrong top", (pauseHeight - prevHeight) / 2, mPrev.getTop());
+ assertEquals("Next wrong top", (pauseHeight - nextHeight) / 2, mNext.getTop());
+ assertEquals("CurrentTime wrong bottom", pauseHeight, mCurrentTime.getBottom());
+ assertEquals("TotalTime wrong bottom", pauseHeight, mTotalTime.getBottom());
+ assertTrue("CurrentTime too tall", mCurrentTime.getTop() > 0);
+ assertTrue("TotalTime too tall", mTotalTime.getTop() > 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/ExceptionTextView.java b/core/tests/coretests/src/android/widget/layout/linear/ExceptionTextView.java
new file mode 100644
index 0000000..9fa9be9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/ExceptionTextView.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import junit.framework.Assert;
+
+import android.widget.EditText;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.text.BoringLayout;
+
+
+/**
+ * A special EditText that sets {@link #isFailed()} to true as its internal makeNewLayout() method is called
+ * with a width lower than 0. This is used to fail the unit test in
+ * BaselineAlignmentZeroWidthAndWeightTest.
+ */
+public class ExceptionTextView extends EditText {
+
+ private boolean mFailed = false;
+
+ public ExceptionTextView(Context context) {
+ super(context);
+ }
+
+ public ExceptionTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ExceptionTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public boolean isFailed() {
+ return mFailed;
+ }
+
+ @Override
+ protected void makeNewLayout(int w, int hintWidth,
+ BoringLayout.Metrics boring,
+ BoringLayout.Metrics hintMetrics,
+ int ellipsizedWidth, boolean bringIntoView) {
+ if (w < 0) {
+ mFailed = true;
+ w = 100;
+ }
+
+ super.makeNewLayout(w, hintWidth, boring, hintMetrics, ellipsizedWidth,
+ bringIntoView);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/FillInWrap.java b/core/tests/coretests/src/android/widget/layout/linear/FillInWrap.java
new file mode 100644
index 0000000..50aa5b7
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/FillInWrap.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class FillInWrap extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.fill_in_wrap);
+ ((TextView) findViewById(R.id.data)).setText("1\n2\n3\n4\n5");
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/FillInWrapTest.java b/core/tests/coretests/src/android/widget/layout/linear/FillInWrapTest.java
new file mode 100644
index 0000000..f161802
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/FillInWrapTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+
+public class FillInWrapTest extends ActivityInstrumentationTestCase<FillInWrap> {
+ private View mChild;
+ private View mContainer;
+
+ public FillInWrapTest() {
+ super("com.android.frameworks.coretests", FillInWrap.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mChild = activity.findViewById(R.id.data);
+ mContainer = activity.findViewById(R.id.layout);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mChild);
+ assertNotNull(mContainer);
+ }
+
+ @MediumTest
+ public void testLayout() {
+ assertTrue("the child's height should be less than the parent's",
+ mChild.getMeasuredHeight() < mContainer.getMeasuredHeight());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/HorizontalOrientationVerticalAlignment.java b/core/tests/coretests/src/android/widget/layout/linear/HorizontalOrientationVerticalAlignment.java
new file mode 100644
index 0000000..9f937a9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/HorizontalOrientationVerticalAlignment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class HorizontalOrientationVerticalAlignment extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.linear_layout_spinner_then_button);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLEditTextThenButton.java b/core/tests/coretests/src/android/widget/layout/linear/LLEditTextThenButton.java
new file mode 100644
index 0000000..fe2525f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLEditTextThenButton.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+public class LLEditTextThenButton extends Activity {
+ private EditText mEditText;
+ private Button mButton;
+
+ private LinearLayout mLayout;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.linear_layout_edittext_then_button);
+
+ mLayout = (LinearLayout) findViewById(R.id.layout);
+ mEditText = (EditText) findViewById(R.id.editText);
+ mButton = (Button) findViewById(R.id.button);
+ }
+
+ public LinearLayout getLayout() {
+ return mLayout;
+ }
+
+ public EditText getEditText() {
+ return mEditText;
+ }
+
+ public Button getButton() {
+ return mButton;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons1.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons1.java
new file mode 100644
index 0000000..3d0144f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons1.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import com.android.frameworks.coretests.R;
+
+/**
+ * One of two simple vertical linear layouts of buttons used to test out
+ * the transistion between touch and focus mode.
+ */
+public class LLOfButtons1 extends Activity {
+
+ private boolean mButtonPressed = false;
+ private Button mFirstButton;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.linear_layout_buttons);
+ mFirstButton = (Button) findViewById(R.id.button1);
+
+ mFirstButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mButtonPressed = true;
+ }
+ });
+
+ }
+
+ public LinearLayout getLayout() {
+ return (LinearLayout) findViewById(R.id.layout);
+ }
+
+ public Button getFirstButton() {
+ return mFirstButton;
+ }
+
+ public boolean buttonClickListenerFired() {
+ return mButtonPressed;
+ }
+
+ public boolean isInTouchMode() {
+ return mFirstButton.isInTouchMode();
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons2.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons2.java
new file mode 100644
index 0000000..77f564d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfButtons2.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+/**
+ * One of two simple vertical linear layouts of buttons used to test out
+ * the transistion between touch and focus mode.
+ */
+public class LLOfButtons2 extends LLOfButtons1 {
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
new file mode 100644
index 0000000..1ba56ba
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class LLOfTwoFocusableInTouchMode extends Activity {
+
+ private View mButton1;
+ private View mButton2;
+ private View mButton3;
+
+ private boolean mB1Fired = false;
+ private boolean mB2Fired = false;
+ private boolean mB3Fired = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.linear_layout_buttons);
+
+ mButton1 = findViewById(R.id.button1);
+ mButton2 = findViewById(R.id.button2);
+ mButton3 = findViewById(R.id.button3);
+
+ mButton1.setFocusableInTouchMode(true);
+ mButton2.setFocusableInTouchMode(true);
+ mButton3.setFocusableInTouchMode(true);
+
+ mButton1.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mB1Fired = true;
+ }
+ });
+
+ mButton2.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mB2Fired = true;
+ }
+ });
+
+ mButton3.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mB3Fired = true;
+ }
+ });
+ }
+
+ public View getButton1() {
+ return mButton1;
+ }
+
+ public View getButton2() {
+ return mButton2;
+ }
+
+ public View getButton3() {
+ return mButton3;
+ }
+
+ public boolean isB1Fired() {
+ return mB1Fired;
+ }
+
+ public boolean isB2Fired() {
+ return mB2Fired;
+ }
+
+ public boolean isB3Fired() {
+ return mB3Fired;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTexts.java b/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTexts.java
new file mode 100644
index 0000000..90db788
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTexts.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class LinearLayoutEditTexts extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.linear_layout_textviews);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTextsTest.java b/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTextsTest.java
new file mode 100644
index 0000000..d5998b7
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/LinearLayoutEditTextsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.widget.layout.linear.LinearLayoutEditTexts;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.app.Activity;
+
+public class LinearLayoutEditTextsTest extends ActivityInstrumentationTestCase<LinearLayoutEditTexts> {
+ private View mChild;
+ private View mContainer;
+
+ public LinearLayoutEditTextsTest() {
+ super("com.android.frameworks.coretests", LinearLayoutEditTexts.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mChild = activity.findViewById(R.id.editText1);
+ mContainer = activity.findViewById(R.id.layout);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mChild);
+ assertNotNull(mContainer);
+ }
+
+ @MediumTest
+ public void testLayout() {
+ final int childHeight = mChild.getHeight();
+ final int containerHeight = mContainer.getHeight();
+
+ assertEquals(containerHeight, childHeight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/Weight.java b/core/tests/coretests/src/android/widget/layout/linear/Weight.java
new file mode 100644
index 0000000..20edd7c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/Weight.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Weight extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.linear_layout_weight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/WeightSum.java b/core/tests/coretests/src/android/widget/layout/linear/WeightSum.java
new file mode 100644
index 0000000..2e421da
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/WeightSum.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+public class WeightSum extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.weight_sum);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/WeightSumTest.java b/core/tests/coretests/src/android/widget/layout/linear/WeightSumTest.java
new file mode 100644
index 0000000..f9a94ce
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/WeightSumTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.WeightSum;
+
+public class WeightSumTest extends ActivityInstrumentationTestCase<WeightSum> {
+ private View mChild;
+ private View mContainer;
+
+ public WeightSumTest() {
+ super("com.android.frameworks.coretests", WeightSum.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mChild = activity.findViewById(R.id.child);
+ mContainer = activity.findViewById(R.id.container);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mChild);
+ assertNotNull(mContainer);
+ }
+
+ @MediumTest
+ public void testLayout() {
+ final int childWidth = mChild.getWidth();
+ final int containerWidth = mContainer.getWidth();
+
+ assertEquals(containerWidth / 2, childWidth);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/linear/WeightTest.java b/core/tests/coretests/src/android/widget/layout/linear/WeightTest.java
new file mode 100644
index 0000000..0349d7f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/linear/WeightTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.linear;
+
+import android.app.Activity;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import android.widget.layout.linear.Weight;
+
+public class WeightTest extends ActivityInstrumentationTestCase<Weight> {
+ private View mChild;
+ private View mContainer;
+
+ public WeightTest() {
+ super("com.android.frameworks.coretests", Weight.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Activity activity = getActivity();
+ mChild = activity.findViewById(R.id.child4);
+ mContainer = activity.findViewById(R.id.layout);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mChild);
+ assertNotNull(mContainer);
+ }
+
+ @MediumTest
+ public void testLayout() {
+ ViewAsserts.assertRightAligned(mChild, mContainer);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/AddColumn.java b/core/tests/coretests/src/android/widget/layout/table/AddColumn.java
new file mode 100644
index 0000000..400c32c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/AddColumn.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+/**
+ * This test adds an extra row with an extra column in the table.
+ */
+public class AddColumn extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.add_column_in_table);
+
+ final Button addRowButton = (Button) findViewById(R.id.add_row_button);
+ addRowButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ final TableLayout table = (TableLayout) findViewById(R.id.table);
+ final TableRow newRow = new TableRow(AddColumn.this);
+ for (int i = 0; i < 4; i++) {
+ final TextView view = new TextView(AddColumn.this);
+ view.setText("Column " + (i + 1));
+ view.setPadding(3, 3, 3, 3);
+ newRow.addView(view, new TableRow.LayoutParams());
+ }
+ table.addView(newRow, new TableLayout.LayoutParams());
+ newRow.requestLayout();
+ }
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/AddColumnTest.java b/core/tests/coretests/src/android/widget/layout/table/AddColumnTest.java
new file mode 100644
index 0000000..bfb4d17
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/AddColumnTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.AddColumn;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+/**
+ * {@link android.widget.layout.table.AddColumn} is
+ * setup to exercise the case of adding row programmatically in a table.
+ */
+public class AddColumnTest extends ActivityInstrumentationTestCase<AddColumn> {
+ private Button mAddRow;
+ private TableLayout mTable;
+
+ public AddColumnTest() {
+ super("com.android.frameworks.coretests", AddColumn.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final AddColumn activity = getActivity();
+ mAddRow = (Button) activity.findViewById(R.id.add_row_button);
+ mTable = (TableLayout) activity.findViewById(R.id.table);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mAddRow);
+ assertNotNull(mTable);
+ assertTrue(mAddRow.hasFocus());
+ }
+
+ @MediumTest
+ public void testWidths() throws Exception {
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync();
+
+ TableRow row1 = (TableRow) mTable.getChildAt(0);
+ TableRow row2 = (TableRow) mTable.getChildAt(1);
+
+ assertTrue(row1.getChildCount() < row2.getChildCount());
+
+ for (int i = 0; i < row1.getChildCount(); i++) {
+ assertEquals(row2.getChildAt(i).getWidth(), row1.getChildAt(i).getWidth());
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/CellSpan.java b/core/tests/coretests/src/android/widget/layout/table/CellSpan.java
new file mode 100644
index 0000000..d91cf56
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/CellSpan.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Exercise table layout with cells spanning.
+ */
+public class CellSpan extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.table_layout_cell_span);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/CellSpanTest.java b/core/tests/coretests/src/android/widget/layout/table/CellSpanTest.java
new file mode 100644
index 0000000..331ec45
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/CellSpanTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.CellSpan;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+/**
+ * {@link android.widget.layout.table.CellSpan} is
+ * setup to exercise tables in which cells use spanning.
+ */
+public class CellSpanTest extends ActivityInstrumentationTestCase<CellSpan> {
+ private View mA;
+ private View mB;
+ private View mC;
+ private View mSpanThenCell;
+ private View mCellThenSpan;
+ private View mSpan;
+
+ public CellSpanTest() {
+ super("com.android.frameworks.coretests", CellSpan.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final CellSpan activity = getActivity();
+ mA = activity.findViewById(R.id.a);
+ mB = activity.findViewById(R.id.b);
+ mC = activity.findViewById(R.id.c);
+ mSpanThenCell = activity.findViewById(R.id.spanThenCell);
+ mCellThenSpan = activity.findViewById(R.id.cellThenSpan);
+ mSpan = activity.findViewById(R.id.span);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mA);
+ assertNotNull(mB);
+ assertNotNull(mC);
+ assertNotNull(mSpanThenCell);
+ assertNotNull(mCellThenSpan);
+ assertNotNull(mSpan);
+ }
+
+ @MediumTest
+ public void testSpanThenCell() throws Exception {
+ int spanWidth = mA.getMeasuredWidth() + mB.getMeasuredWidth();
+ assertEquals("span followed by cell is broken", spanWidth,
+ mSpanThenCell.getMeasuredWidth());
+ }
+
+ @MediumTest
+ public void testCellThenSpan() throws Exception {
+ int spanWidth = mB.getMeasuredWidth() + mC.getMeasuredWidth();
+ assertEquals("cell followed by span is broken", spanWidth,
+ mCellThenSpan.getMeasuredWidth());
+ }
+
+ @MediumTest
+ public void testSpan() throws Exception {
+ int spanWidth = mA.getMeasuredWidth() + mB.getMeasuredWidth() +
+ mC.getMeasuredWidth();
+ assertEquals("span is broken", spanWidth, mSpan.getMeasuredWidth());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/FixedWidth.java b/core/tests/coretests/src/android/widget/layout/table/FixedWidth.java
new file mode 100644
index 0000000..435815a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/FixedWidth.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Exercise table layout with cells having a fixed width and height.
+ */
+public class FixedWidth extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.table_layout_fixed_width);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/FixedWidthTest.java b/core/tests/coretests/src/android/widget/layout/table/FixedWidthTest.java
new file mode 100644
index 0000000..b20ec84
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/FixedWidthTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.FixedWidth;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+/**
+ * {@link android.widget.layout.table.FixedWidth} is
+ * setup to exercise tables in which cells use fixed width and height.
+ */
+public class FixedWidthTest extends ActivityInstrumentationTestCase<FixedWidth> {
+ private View mFixedWidth;
+ private View mFixedHeight;
+ private View mNonFixedWidth;
+
+ public FixedWidthTest() {
+ super("com.android.frameworks.coretests", FixedWidth.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final FixedWidth activity = getActivity();
+ mFixedWidth = activity.findViewById(R.id.fixed_width);
+ mNonFixedWidth = activity.findViewById(R.id.non_fixed_width);
+ mFixedHeight = activity.findViewById(R.id.fixed_height);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mFixedWidth);
+ assertNotNull(mFixedHeight);
+ assertNotNull(mNonFixedWidth);
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @MediumTest
+ public void testFixedWidth() throws Exception {
+ assertEquals(150, mFixedWidth.getWidth());
+ assertEquals(mFixedWidth.getWidth(), mNonFixedWidth.getWidth());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @MediumTest
+ public void testFixedHeight() throws Exception {
+ assertEquals(48, mFixedHeight.getHeight());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/HorizontalGravity.java b/core/tests/coretests/src/android/widget/layout/table/HorizontalGravity.java
new file mode 100644
index 0000000..1444f60
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/HorizontalGravity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Exercise table layout with cells using a horizontal gravity.
+ */
+public class HorizontalGravity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.table_layout_horizontal_gravity);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/HorizontalGravityTest.java b/core/tests/coretests/src/android/widget/layout/table/HorizontalGravityTest.java
new file mode 100644
index 0000000..964df82
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/HorizontalGravityTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.HorizontalGravity;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.view.View;
+
+/**
+ * {@link android.widget.layout.table.HorizontalGravity} is
+ * setup to exercise tables in which cells use horizontal gravity.
+ */
+public class HorizontalGravityTest extends ActivityInstrumentationTestCase<HorizontalGravity> {
+ private View mReference;
+ private View mCenter;
+ private View mBottomRight;
+ private View mLeft;
+
+ public HorizontalGravityTest() {
+ super("com.android.frameworks.coretests", HorizontalGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final HorizontalGravity activity = getActivity();
+ mReference = activity.findViewById(R.id.reference);
+ mCenter = activity.findViewById(R.id.center);
+ mBottomRight = activity.findViewById(R.id.bottomRight);
+ mLeft = activity.findViewById(R.id.left);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mReference);
+ assertNotNull(mCenter);
+ assertNotNull(mBottomRight);
+ assertNotNull(mLeft);
+ }
+
+ @MediumTest
+ public void testCenterGravity() throws Exception {
+ ViewAsserts.assertHorizontalCenterAligned(mReference, mCenter);
+ }
+
+ @MediumTest
+ public void testLeftGravity() throws Exception {
+ ViewAsserts.assertLeftAligned(mReference, mLeft);
+ }
+
+ @MediumTest
+ public void testRightGravity() throws Exception {
+ ViewAsserts.assertRightAligned(mReference, mBottomRight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/VerticalGravity.java b/core/tests/coretests/src/android/widget/layout/table/VerticalGravity.java
new file mode 100644
index 0000000..4fdb378
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/VerticalGravity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Exercise table layout with cells using a vertical gravity.
+ */
+public class VerticalGravity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.table_layout_vertical_gravity);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/VerticalGravityTest.java b/core/tests/coretests/src/android/widget/layout/table/VerticalGravityTest.java
new file mode 100644
index 0000000..1d6be3f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/VerticalGravityTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.VerticalGravity;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ViewAsserts;
+import android.view.View;
+
+/**
+ * {@link android.widget.layout.table.VerticalGravity} is
+ * setup to exercise tables in which cells use vertical gravity.
+ */
+public class VerticalGravityTest extends ActivityInstrumentationTestCase<VerticalGravity> {
+ private View mReference1;
+ private View mReference2;
+ private View mReference3;
+ private View mTop;
+ private View mCenter;
+ private View mBottom;
+
+ public VerticalGravityTest() {
+ super("com.android.frameworks.coretests", VerticalGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final VerticalGravity activity = getActivity();
+ mReference1 = activity.findViewById(R.id.reference1);
+ mReference2 = activity.findViewById(R.id.reference2);
+ mReference3 = activity.findViewById(R.id.reference3);
+ mTop = activity.findViewById(R.id.cell_top);
+ mCenter = activity.findViewById(R.id.cell_center);
+ mBottom = activity.findViewById(R.id.cell_bottom);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mReference1);
+ assertNotNull(mReference2);
+ assertNotNull(mReference3);
+ assertNotNull(mTop);
+ assertNotNull(mCenter);
+ assertNotNull(mBottom);
+ }
+
+ @MediumTest
+ public void testTopGravity() throws Exception {
+ ViewAsserts.assertTopAligned(mReference1, mTop);
+ }
+
+ @MediumTest
+ public void testCenterGravity() throws Exception {
+ ViewAsserts.assertVerticalCenterAligned(mReference2, mCenter);
+ }
+
+ @Suppress
+ @MediumTest
+ public void testBottomGravity() throws Exception {
+ ViewAsserts.assertBottomAligned(mReference3, mBottom);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/Weight.java b/core/tests/coretests/src/android/widget/layout/table/Weight.java
new file mode 100644
index 0000000..6d4d51d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/Weight.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Exercise table layout with cells having a weight.
+ */
+public class Weight extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.table_layout_weight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/layout/table/WeightTest.java b/core/tests/coretests/src/android/widget/layout/table/WeightTest.java
new file mode 100644
index 0000000..b665573
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/layout/table/WeightTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.layout.table;
+
+import android.widget.layout.table.Weight;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+/**
+ * {@link android.widget.layout.table.Weight} is
+ * setup to exercise tables in which cells use a weight.
+ */
+public class WeightTest extends ActivityInstrumentationTestCase<Weight> {
+ private View mCell1;
+ private View mCell2;
+ private View mCell3;
+ private View mRow;
+
+ public WeightTest() {
+ super("com.android.frameworks.coretests", Weight.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final Weight activity = getActivity();
+ mCell1 = activity.findViewById(R.id.cell1);
+ mCell3 = activity.findViewById(R.id.cell2);
+ mCell2 = activity.findViewById(R.id.cell3);
+ mRow = activity.findViewById(R.id.row);
+ }
+
+ @MediumTest
+ public void testSetUpConditions() throws Exception {
+ assertNotNull(mCell1);
+ assertNotNull(mCell2);
+ assertNotNull(mCell3);
+ assertNotNull(mRow);
+ }
+
+ @MediumTest
+ public void testAllCellsFillParent() throws Exception {
+ assertEquals(mCell1.getWidth() + mCell2.getWidth() + mCell3.getWidth(), mRow.getWidth());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
new file mode 100644
index 0000000..98fbed3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.InternalSelectionView;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+
+/**
+ * Most bodacious scenario yet!
+ */
+public class AdjacentListsWithAdjacentISVsInside extends Activity {
+
+ private ListView mLeftListView;
+ private ListView mRightListView;
+
+ public ListView getLeftListView() {
+ return mLeftListView;
+ }
+
+ public ListView getRightListView() {
+ return mRightListView;
+ }
+
+ public InternalSelectionView getLeftIsv() {
+ return (InternalSelectionView)
+ ((ViewGroup) mLeftListView.getChildAt(0)).getChildAt(0);
+ }
+
+ public InternalSelectionView getLeftMiddleIsv() {
+ return (InternalSelectionView)
+ ((ViewGroup) mLeftListView.getChildAt(0)).getChildAt(1);
+ }
+
+ public InternalSelectionView getRightMiddleIsv() {
+ return (InternalSelectionView)
+ ((ViewGroup) mRightListView.getChildAt(0)).getChildAt(0);
+ }
+
+ public InternalSelectionView getRightIsv() {
+ return (InternalSelectionView)
+ ((ViewGroup) mRightListView.getChildAt(0)).getChildAt(1);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final int desiredHeight = (int) (0.8 * getWindowManager().getDefaultDisplay().getHeight());
+
+ mLeftListView = new ListView(this);
+ mLeftListView.setAdapter(new AdjacentISVAdapter(desiredHeight));
+ mLeftListView.setItemsCanFocus(true);
+
+
+ mRightListView = new ListView(this);
+ mRightListView.setAdapter(new AdjacentISVAdapter(desiredHeight));
+ mRightListView.setItemsCanFocus(true);
+
+
+
+ setContentView(combineAdjacent(mLeftListView, mRightListView));
+ }
+
+ private static View combineAdjacent(View... views) {
+ if (views.length < 2) {
+ throw new IllegalArgumentException("you should pass at least 2 views in");
+ }
+
+ final LinearLayout ll = new LinearLayout(views[0].getContext());
+ ll.setOrientation(LinearLayout.HORIZONTAL);
+ final LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f);
+
+ for (View view : views) {
+ ll.addView(view, lp);
+ }
+ return ll;
+ }
+
+ static class AdjacentISVAdapter extends BaseAdapter {
+
+ private final int mItemHeight;
+
+ AdjacentISVAdapter(int itemHeight) {
+ mItemHeight = itemHeight;
+ }
+
+ public int getCount() {
+ return 1;
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final InternalSelectionView isvLeft = new InternalSelectionView(
+ parent.getContext(), 5, "isv left");
+ isvLeft.setDesiredHeight(mItemHeight);
+ final InternalSelectionView isvRight = new InternalSelectionView(
+ parent.getContext(), 5, "isv right");
+ isvRight.setDesiredHeight(mItemHeight);
+ return combineAdjacent(isvLeft, isvRight);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListBottomGravity.java b/core/tests/coretests/src/android/widget/listview/ListBottomGravity.java
new file mode 100644
index 0000000..a386ebd
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListBottomGravity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.Gravity;
+
+import android.util.ListScenario;
+
+/**
+ * Basic bottom gravity scenario, nothing fancy. Items do not
+ * fill the screen
+ */
+public class ListBottomGravity extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(2)
+ .setItemScreenSizeFactor(0.22);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListBottomGravityMany.java b/core/tests/coretests/src/android/widget/listview/ListBottomGravityMany.java
new file mode 100644
index 0000000..519816c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListBottomGravityMany.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.Gravity;
+
+import android.util.ListScenario;
+
+/**
+ * Basic bottom gravity scenario, nothing fancy. There are
+ * more items than fit on the screen
+ */
+public class ListBottomGravityMany extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(true)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(10)
+ .setItemScreenSizeFactor(0.22);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListBottomGravityManyTest.java b/core/tests/coretests/src/android/widget/listview/ListBottomGravityManyTest.java
new file mode 100644
index 0000000..e1171eb
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListBottomGravityManyTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+
+import android.widget.listview.ListBottomGravityMany;
+
+public class ListBottomGravityManyTest extends ActivityInstrumentationTestCase<ListBottomGravityMany> {
+ private ListBottomGravityMany mActivity;
+ private ListView mListView;
+
+ public ListBottomGravityManyTest() {
+ super("com.android.frameworks.coretests", ListBottomGravityMany.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // Last item should be selected
+ assertEquals(mListView.getAdapter().getCount() - 1, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListBottomGravityTest.java b/core/tests/coretests/src/android/widget/listview/ListBottomGravityTest.java
new file mode 100644
index 0000000..c595f62
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListBottomGravityTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+
+import android.widget.listview.ListBottomGravity;
+
+public class ListBottomGravityTest extends ActivityInstrumentationTestCase<ListBottomGravity> {
+ private ListBottomGravity mActivity;
+ private ListView mListView;
+
+ public ListBottomGravityTest() {
+ super("com.android.frameworks.coretests", ListBottomGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // Last item should be selected
+ assertEquals(mListView.getAdapter().getCount() - 1, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListButtonsDiagonalAcrossItems.java b/core/tests/coretests/src/android/widget/listview/ListButtonsDiagonalAcrossItems.java
new file mode 100644
index 0000000..bbed73c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListButtonsDiagonalAcrossItems.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListItemFactory;
+import static android.util.ListItemFactory.Slot;
+import android.util.ListScenario;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class ListButtonsDiagonalAcrossItems extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setItemsFocusable(true)
+ .setNumItems(3)
+ .setItemScreenSizeFactor(0.2)
+ .setMustFillScreen(false);
+ }
+
+ public Button getLeftButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(0)).getChildAt(0);
+ }
+
+ public Button getCenterButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(1)).getChildAt(1);
+ }
+
+ public Button getRightButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(2)).getChildAt(2);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent,
+ int desiredHeight) {
+ final Slot slot = position == 0 ? Slot.Left :
+ (position == 1 ? Slot.Middle : Slot.Right);
+ return ListItemFactory.horizontalButtonSlots(
+ parent.getContext(), desiredHeight, slot);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListDividers.java b/core/tests/coretests/src/android/widget/listview/ListDividers.java
new file mode 100644
index 0000000..3928c03
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListDividers.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a list width dividers and padding.
+ */
+public class ListDividers extends Activity {
+ private ListView mListView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_dividers);
+
+ String values[] = new String[1000];
+ for (int i = 0; i < 1000; i++) {
+ values[i] = ((Integer) i).toString();
+ }
+
+ mListView = (ListView) findViewById(android.R.id.list);
+ mListView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListEmptyViewTest.java b/core/tests/coretests/src/android/widget/listview/ListEmptyViewTest.java
new file mode 100644
index 0000000..258d3ef
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListEmptyViewTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ListView;
+
+public class ListEmptyViewTest extends ActivityInstrumentationTestCase<ListWithEmptyView> {
+ private ListWithEmptyView mActivity;
+ private ListView mListView;
+
+
+ public ListEmptyViewTest() {
+ super("com.android.frameworks.coretests", ListWithEmptyView.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ assertTrue("Empty view not shown", mListView.getVisibility() == View.GONE);
+ }
+
+ @MediumTest
+ public void testZeroToOne() {
+ Instrumentation inst = getInstrumentation();
+
+ inst.invokeMenuActionSync(mActivity, mActivity.MENU_ADD, 0);
+ inst.waitForIdleSync();
+ assertTrue("Empty view still shown", mActivity.getEmptyView().getVisibility() == View.GONE);
+ assertTrue("List not shown", mActivity.getListView().getVisibility() == View.VISIBLE);
+ }
+
+ @MediumTest
+ public void testZeroToOneForwardBack() {
+ Instrumentation inst = getInstrumentation();
+
+ inst.invokeMenuActionSync(mActivity, mActivity.MENU_ADD, 0);
+ inst.waitForIdleSync();
+ assertTrue("Empty view still shown", mActivity.getEmptyView().getVisibility() == View.GONE);
+ assertTrue("List not shown", mActivity.getListView().getVisibility() == View.VISIBLE);
+
+ // Navigate forward
+ Intent intent = new Intent();
+ intent.setClass(mActivity, ListWithEmptyView.class);
+ mActivity.startActivity(intent);
+
+ // Navigate backward
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+ assertTrue("Empty view still shown", mActivity.getEmptyView().getVisibility() == View.GONE);
+ assertTrue("List not shown", mActivity.getListView().getVisibility() == View.VISIBLE);
+
+ }
+
+ @LargeTest
+ public void testZeroToManyToZero() {
+ Instrumentation inst = getInstrumentation();
+
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ inst.invokeMenuActionSync(mActivity, mActivity.MENU_ADD, 0);
+ inst.waitForIdleSync();
+ assertTrue("Empty view still shown",
+ mActivity.getEmptyView().getVisibility() == View.GONE);
+ assertTrue("List not shown", mActivity.getListView().getVisibility() == View.VISIBLE);
+ }
+
+ for (i = 0; i < 10; i++) {
+ inst.invokeMenuActionSync(mActivity, mActivity.MENU_REMOVE, 0);
+ inst.waitForIdleSync();
+ if (i < 9) {
+ assertTrue("Empty view still shown",
+ mActivity.getEmptyView().getVisibility() == View.GONE);
+ assertTrue("List not shown",
+ mActivity.getListView().getVisibility() == View.VISIBLE);
+ } else {
+ assertTrue("Empty view not shown",
+ mActivity.getEmptyView().getVisibility() == View.VISIBLE);
+ assertTrue("List still shown",
+ mActivity.getListView().getVisibility() == View.GONE);
+ }
+ }
+
+ // Navigate forward
+ Intent intent = new Intent();
+ intent.setClass(mActivity, ListWithEmptyView.class);
+ mActivity.startActivity(intent);
+
+ // Navigate backward
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+ assertTrue("Empty view not shown", mActivity.getEmptyView().getVisibility() == View.VISIBLE);
+ assertTrue("List still shown", mActivity.getListView().getVisibility() == View.GONE);
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListEndingWithMultipleSeparators.java b/core/tests/coretests/src/android/widget/listview/ListEndingWithMultipleSeparators.java
new file mode 100644
index 0000000..85f9924
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListEndingWithMultipleSeparators.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListEndingWithMultipleSeparators extends ListScenario {
+
+ protected void init(Params params) {
+ params.setItemsFocusable(false)
+ .setNumItems(5)
+ .setItemScreenSizeFactor(0.22)
+ .setPositionUnselectable(3)
+ .setPositionUnselectable(4);
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListFilter.java b/core/tests/coretests/src/android/widget/listview/ListFilter.java
new file mode 100644
index 0000000..9e9d1b0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListFilter.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+
+import com.android.frameworks.coretests.R;
+
+
+/**
+ * Tests hiding and showing the list filter by hiding and showing an ancestor of the
+ * ListView
+ */
+public class ListFilter extends ListActivity implements OnClickListener {
+
+ private View mFrame;
+ private Button mHide;
+ private Button mShow;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.list_filter);
+ setListAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, mStrings));
+ getListView().setTextFilterEnabled(true);
+ mFrame = findViewById(R.id.frame);
+
+ mHide = (Button) findViewById(R.id.hide);
+ mHide.setOnClickListener(this);
+
+ mShow = (Button) findViewById(R.id.show);
+ mShow.setOnClickListener(this);
+ }
+
+
+ public void onClick(View v) {
+ mFrame.setVisibility(v == mHide ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ private String[] mStrings = {
+ "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+ "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+ "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+ "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+ "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+ "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+ "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+ "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+ "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+ "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+ "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+ "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+ "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+ "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+ "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+ "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+ "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+ "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+ "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+ "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+ "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+ "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+ "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+ "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+ "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+ "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+ "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+ "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+ "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+ "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+ "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+ "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+ "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+ "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+ "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+ "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+ "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+ "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+ "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+ "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+ "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+ "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+ "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+ "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+ "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+ "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+ "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+ "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+ "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+ "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+ "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+ "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+ "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+ "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+ "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+ "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+ "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+ "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+ "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+ "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+ "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+ "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+ "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+ "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+ "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+ "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+ "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+ "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+ "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+ "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+ "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+ "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+ "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+ "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+ "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+ "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+ "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+ "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+ "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+ "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+ "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+ "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+ "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+ "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+ "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+ "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+ "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+ "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+ "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+ "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+ "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+ "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+ "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+ "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+ "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+ "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+ "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+ "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+ "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+ "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+ "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+ "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+ "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+ "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+ "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+ "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+ "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+ "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+ "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+ "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+ "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+ "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+ "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+ "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+ "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+ "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+ "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+ "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+ "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+ "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+ "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+ "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+ "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+ "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+ "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+ "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+ "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+ "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+ "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+ "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"};
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListFocusableTest.java b/core/tests/coretests/src/android/widget/listview/ListFocusableTest.java
new file mode 100644
index 0000000..bf18a13
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListFocusableTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.widget.ListAdapter;
+import android.widget.ArrayAdapter;
+
+public class ListFocusableTest extends ActivityInstrumentationTestCase<ListTopGravity> {
+ private ListTopGravity mActivity;
+ private ListView mListView;
+
+ public ListFocusableTest() {
+ super("com.android.frameworks.coretests", ListTopGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // First item should be selected
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testAdapterFull() {
+ setFullAdapter();
+ assertTrue(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterEmpty() {
+ setEmptyAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterNull() {
+ setNullAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterFullSetFocusable() {
+ assertTrue(mListView.isFocusable());
+
+ setFocusable();
+ assertTrue(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterFullSetNonFocusable() {
+ assertTrue(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterEmptySetFocusable() {
+ setEmptyAdapter();
+ assertFalse(mListView.isFocusable());
+
+ setFocusable();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterEmptySetNonFocusable() {
+ setEmptyAdapter();
+ assertFalse(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterNullSetFocusable() {
+ setNullAdapter();
+ assertFalse(mListView.isFocusable());
+
+ setFocusable();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testAdapterNullSetNonFocusable() {
+ setNullAdapter();
+ assertFalse(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testFocusableSetAdapterFull() {
+ assertTrue(mListView.isFocusable());
+
+ setFullAdapter();
+ assertTrue(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testNonFocusableSetAdapterFull() {
+ assertTrue(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+
+ setFullAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testFocusableSetAdapterEmpty() {
+ assertTrue(mListView.isFocusable());
+
+ setEmptyAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testNonFocusableSetAdapterEmpty() {
+ assertTrue(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+
+ setEmptyAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testFocusableSetAdapterNull() {
+ assertTrue(mListView.isFocusable());
+
+ setNullAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ @MediumTest
+ public void testNonFocusableSetAdapterNull() {
+ assertTrue(mListView.isFocusable());
+
+ setNonFocusable();
+ assertFalse(mListView.isFocusable());
+
+ setNullAdapter();
+ assertFalse(mListView.isFocusable());
+ }
+
+ private ListAdapter createFullAdapter() {
+ return new ArrayAdapter<String>(mActivity, android.R.layout.simple_list_item_1,
+ new String[] { "Android", "Robot" });
+ }
+
+ private ListAdapter createEmptyAdapter() {
+ return new ArrayAdapter<String>(mActivity, android.R.layout.simple_list_item_1,
+ new String[] { });
+ }
+
+
+ private void setFullAdapter() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setAdapter(createFullAdapter());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ private void setEmptyAdapter() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setAdapter(createEmptyAdapter());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ private void setNullAdapter() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setAdapter(null);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ private void setFocusable() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setFocusable(true);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+
+ private void setNonFocusable() {
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setFocusable(false);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListGetCheckItemIdsTest.java b/core/tests/coretests/src/android/widget/listview/ListGetCheckItemIdsTest.java
new file mode 100644
index 0000000..33d61a0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListGetCheckItemIdsTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+
+import java.util.Arrays;
+
+/**
+ * Testing the ListView getCheckItemIds() method in different situations.
+ */
+public class ListGetCheckItemIdsTest extends ActivityInstrumentationTestCase2<ListSimple> {
+ private ListView mListView;
+
+ public ListGetCheckItemIdsTest() {
+ super(ListSimple.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ }
+
+ private void assertChecked(String message, long... expectedIds) {
+ // Sort the two arrays since we are actually doing a set equality.
+ long[] checkItemIds = mListView.getCheckItemIds();
+ long[] sortedCheckItemsIds = new long[checkItemIds.length];
+ System.arraycopy(checkItemIds, 0, sortedCheckItemsIds, 0, checkItemIds.length);
+ Arrays.sort(sortedCheckItemsIds);
+
+ long[] sortedExpectedIds = new long[expectedIds.length];
+ System.arraycopy(expectedIds, 0, sortedExpectedIds, 0, expectedIds.length);
+ Arrays.sort(sortedExpectedIds);
+
+ assertTrue(message, Arrays.equals(sortedExpectedIds, sortedCheckItemsIds));
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testNoneCheck() {
+ mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
+
+ mListView.setItemChecked(0, true);
+ assertChecked("None check choice has item checked");
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testSimpleCheck() {
+ mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ assertChecked("Item checked when setting Single mode");
+
+ // Test a check at each position
+ int childCount = mListView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ mListView.setItemChecked(i, true);
+ assertChecked("Only element " + i + " should be checked", i);
+ }
+
+ // Check an element and uncheck some others
+ for (int i = 0; i < childCount; i++) {
+ mListView.setItemChecked(i, true);
+ mListView.setItemChecked((i - 3 + childCount) % childCount, false);
+ mListView.setItemChecked((i + 1) % childCount, false);
+ assertChecked("Only element " + i + " should be checked", i);
+ }
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testMultipleCheck() {
+ mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ assertChecked("Item checked when setting Multiple mode");
+
+ int childCount = mListView.getChildCount();
+ assertTrue("Tests requires at least 4 items", childCount >= 4);
+
+ mListView.setItemChecked(1, true);
+ assertChecked("First element non checked", 1);
+
+ mListView.setItemChecked(3, true);
+ assertChecked("Second element not checked", 1, 3);
+
+ mListView.setItemChecked(0, true);
+ assertChecked("Third element not checked", 0, 1, 3);
+
+ mListView.setItemChecked(2, false);
+ assertChecked("Unchecked element appears checked", 0, 1, 3);
+
+ mListView.setItemChecked(1, false);
+ assertChecked("Unchecked element remains", 0, 3);
+
+ mListView.setItemChecked(2, false);
+ assertChecked("Already unchecked element appears", 0, 3);
+
+ mListView.setItemChecked(3, false);
+ assertChecked("Unchecked 3 remains", 0);
+
+ mListView.setItemChecked(3, false);
+ assertChecked("Twice unchecked 3 remains", 0);
+
+ mListView.setItemChecked(0, false);
+ assertChecked("Checked items after last element unchecked");
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testClearChoices() {
+ mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ mListView.setItemChecked(0, true);
+ mListView.clearChoices();
+ assertChecked("Item checked after SINGLE clear choice");
+
+ mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ mListView.setItemChecked(0, i % 3 == 0);
+ }
+ mListView.clearChoices();
+ assertChecked("Item checked after MULTIPLE clear choice");
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListGetSelectedView.java b/core/tests/coretests/src/android/widget/listview/ListGetSelectedView.java
new file mode 100644
index 0000000..5639195
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListGetSelectedView.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Basic top gravity scenario. This test is made to check that getSelectedView() will return
+ * null in touch mode.
+ */
+public class ListGetSelectedView extends ListScenario {
+ @Override
+ protected void init(Params params) {
+ params.setMustFillScreen(false).setNumItems(2).setItemScreenSizeFactor(0.22);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListHeterogeneous.java b/core/tests/coretests/src/android/widget/listview/ListHeterogeneous.java
new file mode 100644
index 0000000..1f59c30
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListHeterogeneous.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.util.ListItemFactory;
+import android.util.ListScenario;
+
+/**
+ * List that has different view types
+ */
+public class ListHeterogeneous extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setNumItems(50)
+ .setItemScreenSizeFactor(1.0 / 8)
+ .setItemsFocusable(true)
+ .setHeaderViewCount(3)
+ .setFooterViewCount(2);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ switch (position % 3) {
+ case 0:
+ return ListItemFactory.text(
+ position, parent.getContext(), getValueAtPosition(position), desiredHeight);
+ case 1:
+ return ListItemFactory.button(
+ position, parent.getContext(), getValueAtPosition(position), desiredHeight);
+ case 2:
+ return ListItemFactory.doubleText(
+ position, parent.getContext(), getValueAtPosition(position), desiredHeight);
+ }
+
+ return null;
+ }
+
+ @Override
+ public View convertView(int position, View convertView, ViewGroup parent) {
+ switch (position % 3) {
+ case 0:
+ return ListItemFactory.convertText(convertView, getValueAtPosition(position), position);
+ case 1:
+ return ListItemFactory.convertButton(convertView, getValueAtPosition(position),
+ position);
+ case 2:
+ return ListItemFactory.convertDoubleText(convertView, getValueAtPosition(position),
+ position);
+ }
+
+ return null;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return position % 3;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3;
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListHeterogeneousTest.java b/core/tests/coretests/src/android/widget/listview/ListHeterogeneousTest.java
new file mode 100644
index 0000000..01b39db
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListHeterogeneousTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ListView;
+
+import android.widget.listview.ListHeterogeneous;
+
+public class ListHeterogeneousTest extends ActivityInstrumentationTestCase<ListHeterogeneous> {
+ private ListHeterogeneous mActivity;
+ private ListView mListView;
+
+
+ public ListHeterogeneousTest() {
+ super("com.android.frameworks.coretests", ListHeterogeneous.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ }
+
+ @LargeTest
+ public void testKeyScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int count = mListView.getAdapter().getCount();
+
+
+ for (int i = 0; i < count - 1; i++) {
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ inst.waitForIdleSync();
+ int convertMissesBefore = mActivity.getConvertMisses();
+
+ assertEquals("Unexpected convert misses", 0, convertMissesBefore);
+
+ for (int i = 0; i < count - 1; i++) {
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ inst.waitForIdleSync();
+ int convertMissesAfter = mActivity.getConvertMisses();
+
+ assertEquals("Unexpected convert misses", 0, convertMissesAfter);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListHorizontalFocusWithinItemWins.java b/core/tests/coretests/src/android/widget/listview/ListHorizontalFocusWithinItemWins.java
new file mode 100644
index 0000000..2ff65de
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListHorizontalFocusWithinItemWins.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListItemFactory;
+import static android.util.ListItemFactory.Slot;
+import android.util.ListScenario;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class ListHorizontalFocusWithinItemWins extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setItemsFocusable(true)
+ .setNumItems(2)
+ .setItemScreenSizeFactor(0.2)
+ .setMustFillScreen(false);
+ }
+
+ public Button getTopLeftButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(0)).getChildAt(0);
+ }
+
+ public Button getTopRightButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(0)).getChildAt(2);
+ }
+
+ public Button getBottomMiddleButton() {
+ return (Button) ((ViewGroup) getListView().getChildAt(1)).getChildAt(1);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent,
+ int desiredHeight) {
+ final Context context = parent.getContext();
+ if (position == 0) {
+ return ListItemFactory.horizontalButtonSlots(
+ context, desiredHeight, Slot.Left, Slot.Right);
+ } else if (position == 1) {
+ return ListItemFactory.horizontalButtonSlots(
+ context, desiredHeight, Slot.Middle);
+ } else {
+ throw new IllegalArgumentException("expecting position 0 or 1");
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListInHorizontal.java b/core/tests/coretests/src/android/widget/listview/ListInHorizontal.java
new file mode 100644
index 0000000..5f09ff6
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListInHorizontal.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.ListView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a list in a horizontal linear layout
+ */
+public class ListInHorizontal extends Activity {
+ private ListView mListView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_in_horizontal);
+
+ String values[] = new String[1000];
+ for (int i = 0; i < 1000; i++) {
+ values[i] = ((Integer) i).toString();
+ }
+
+ mListView = (ListView) findViewById(R.id.list);
+ mListView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListInHorizontalTest.java b/core/tests/coretests/src/android/widget/listview/ListInHorizontalTest.java
new file mode 100644
index 0000000..3643f79
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListInHorizontalTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.widget.listview.ListInHorizontal;
+
+public class ListInHorizontalTest extends ListUnspecifiedMeasure<ListInHorizontal> {
+ public ListInHorizontalTest() {
+ super(ListInHorizontal.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListInVertical.java b/core/tests/coretests/src/android/widget/listview/ListInVertical.java
new file mode 100644
index 0000000..3b4885a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListInVertical.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.widget.ArrayAdapter;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.widget.ListView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises a list in a vertical linear layout
+ */
+public class ListInVertical extends Activity {
+ private ListView mListView;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_in_vertical);
+
+ String values[] = new String[1000];
+ for (int i = 0; i < 1000; i++) {
+ values[i] = ((Integer) i).toString();
+ }
+
+ mListView = (ListView) findViewById(R.id.list);
+ mListView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListInVerticalTest.java b/core/tests/coretests/src/android/widget/listview/ListInVerticalTest.java
new file mode 100644
index 0000000..8586429
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListInVerticalTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.widget.listview.ListInVertical;
+
+public class ListInVerticalTest extends ListUnspecifiedMeasure<ListInVertical> {
+ public ListInVerticalTest() {
+ super(ListInVertical.class);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListInterleaveFocusables.java b/core/tests/coretests/src/android/widget/listview/ListInterleaveFocusables.java
new file mode 100644
index 0000000..d5da28e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListInterleaveFocusables.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.View;
+ import android.view.ViewGroup;
+ import com.google.android.collect.Sets;
+ import android.util.ListScenario;
+ import android.util.ListItemFactory;
+
+ import java.util.Set;
+
+/**
+ * List that interleaves focusable items.
+ */
+public class ListInterleaveFocusables extends ListScenario {
+
+ private Set<Integer> mFocusablePositions = Sets.newHashSet(1, 3, 6);
+
+ @Override
+ protected void init(Params params) {
+ params.setNumItems(7)
+ .setItemScreenSizeFactor(1.0 / 8)
+ .setItemsFocusable(true)
+ .setMustFillScreen(false);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ if (mFocusablePositions.contains(position)) {
+ return ListItemFactory.button(
+ position, parent.getContext(), getValueAtPosition(position), desiredHeight);
+ } else {
+ return super.createView(position, parent, desiredHeight);
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return mFocusablePositions.contains(position) ? 0 : 1;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemFocusableAboveUnfocusable.java b/core/tests/coretests/src/android/widget/listview/ListItemFocusableAboveUnfocusable.java
new file mode 100644
index 0000000..f7c01b1
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemFocusableAboveUnfocusable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.util.ListItemFactory;
+import android.util.ListScenario;
+
+/**
+ * A list where the items may befocusable, but the second item isn't actually focusabe.
+ */
+public class ListItemFocusableAboveUnfocusable extends ListScenario {
+
+
+ protected void init(Params params) {
+ params.setNumItems(2)
+ .setItemsFocusable(true)
+ .setItemScreenSizeFactor(0.2)
+ .setMustFillScreen(false);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ if (position == 0) {
+ return ListItemFactory.button(
+ position, parent.getContext(), getValueAtPosition(position), desiredHeight);
+ } else {
+ return super.createView(position, parent, desiredHeight);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemFocusablesClose.java b/core/tests/coretests/src/android/widget/listview/ListItemFocusablesClose.java
new file mode 100644
index 0000000..b529b2e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemFocusablesClose.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+import android.util.ListItemFactory;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Each list item has two focusables that are close enough together that
+ * it shouldn't require panning to move focus.
+ */
+public class ListItemFocusablesClose extends ListScenario {
+
+
+ /**
+ * Get the child of a list item.
+ * @param listIndex The index of the currently visible items
+ * @param index The index of the child.
+ */
+ public View getChildOfItem(int listIndex, int index) {
+ return ((ViewGroup) getListView().getChildAt(listIndex)).getChildAt(index);
+
+ }
+
+ @Override
+ protected void init(Params params) {
+ params.setItemsFocusable(true)
+ .setNumItems(2)
+ .setItemScreenSizeFactor(0.55);
+ }
+
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ return ListItemFactory.twoButtonsSeparatedByFiller(
+ position, parent.getContext(), desiredHeight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemFocusablesFarApart.java b/core/tests/coretests/src/android/widget/listview/ListItemFocusablesFarApart.java
new file mode 100644
index 0000000..59987ec
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemFocusablesFarApart.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.util.ListItemFactory;
+import android.util.ListScenario;
+
+/**
+ * A list where each item is tall with buttons that are farther apart than the screen
+ * size. We don't want to jump over content off screen to the next button, we need to
+ * pan across the intermediate part.
+ */
+public class ListItemFocusablesFarApart extends ListScenario {
+
+
+ @Override
+ protected void init(Params params) {
+ params.setItemsFocusable(true)
+ .setNumItems(2)
+ .setItemScreenSizeFactor(2);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ return ListItemFactory.twoButtonsSeparatedByFiller(
+ position, parent.getContext(), desiredHeight);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemISVAndButton.java b/core/tests/coretests/src/android/widget/listview/ListItemISVAndButton.java
new file mode 100644
index 0000000..ea2c5f2
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemISVAndButton.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.util.InternalSelectionView;
+import android.util.ListScenario;
+
+/**
+ * Each item is an internal selection view, a button, and some filler
+ */
+public class ListItemISVAndButton extends ListScenario {
+
+
+ @Override
+ protected void init(Params params) {
+ params.setItemScreenSizeFactor(2.0)
+ .setNumItems(3)
+ .setItemsFocusable(true);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ Context context = parent.getContext();
+
+ final LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.VERTICAL);
+
+ final InternalSelectionView isv = new InternalSelectionView(context, 8, "ISV postion " + position);
+ isv.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ desiredHeight - 240));
+ ll.addView(isv);
+
+ final LinearLayout.LayoutParams buttonLp =
+ new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 40);
+ final Button topButton = new Button(context);
+ topButton.setLayoutParams(
+ buttonLp);
+ topButton.setText("button " + position + ")");
+ ll.addView(topButton);
+
+ final TextView filler = new TextView(context);
+ filler.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ 200));
+ filler.setText("filler");
+ ll.addView(filler);
+
+
+ return ll;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemRequestRectAboveThinFirstItemTest.java b/core/tests/coretests/src/android/widget/listview/ListItemRequestRectAboveThinFirstItemTest.java
new file mode 100644
index 0000000..072ac6c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemRequestRectAboveThinFirstItemTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.listview.ListOfThinItems;
+
+public class ListItemRequestRectAboveThinFirstItemTest
+ extends ActivityInstrumentationTestCase<ListOfThinItems> {
+ private ListView mListView;
+
+ public ListItemRequestRectAboveThinFirstItemTest() {
+ super("com.android.frameworks.coretests", ListOfThinItems.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+
+ assertTrue("first child needs to be within fading edge height",
+ mListView.getChildAt(0).getBottom() < mListView.getVerticalFadingEdgeLength());
+ assertTrue("should be at least two visible children",
+ mListView.getChildCount() >= 2);
+ }
+
+ // reproduce bug 998501: when first item fits within fading edge,
+ // having the second item call requestRectangleOnScreen with a rect above
+ // the bounds of the list, it was scrolling too far
+ @MediumTest
+ public void testSecondItemRequestRectAboveTop() throws Exception {
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("selected position", 1, mListView.getSelectedItemPosition());
+
+ final View second = mListView.getChildAt(1);
+ final Rect rect = new Rect();
+ second.getDrawingRect(rect);
+ rect.offset(0, -2 * second.getBottom());
+
+ getActivity().requestRectangleOnScreen(1, rect);
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals("top of first item",
+ mListView.getListPaddingTop(), mListView.getChildAt(0).getTop());
+
+ }
+
+ // same thing, but at bottom
+ @LargeTest
+ public void testSecondToLastItemRequestRectBelowBottom() throws Exception {
+
+ final int secondToLastPos = mListView.getCount() - 2;
+
+ while (mListView.getSelectedItemPosition() < secondToLastPos) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("selected position", secondToLastPos,
+ mListView.getSelectedItemPosition());
+
+ final View secondToLast = mListView.getSelectedView();
+ final Rect rect = new Rect();
+ secondToLast.getDrawingRect(rect);
+ rect.offset(0,
+ 2 * (mListView.getBottom() - secondToLast.getTop()));
+
+ final int secondToLastIndex = mListView.getChildCount() - 2;
+ getActivity().requestRectangleOnScreen(secondToLastIndex, rect);
+ getInstrumentation().waitForIdleSync();
+
+ int listBottom = mListView.getHeight() - mListView.getPaddingBottom();
+ assertEquals("bottom of last item should be at bottom of list",
+ listBottom,
+ mListView.getChildAt(mListView.getChildCount() - 1).getBottom());
+ }
+
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListItemsExpandOnSelection.java b/core/tests/coretests/src/android/widget/listview/ListItemsExpandOnSelection.java
new file mode 100644
index 0000000..a5fe17a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListItemsExpandOnSelection.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.TextView;
+
+import android.util.ListScenario;
+
+/**
+ * A list where each item expands by 1.5 when selected.
+ */
+public class ListItemsExpandOnSelection extends ListScenario {
+
+
+ @Override
+ protected void init(Params params) {
+ params.setNumItems(10)
+ .setItemScreenSizeFactor(1.0/5);
+ }
+
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ TextView result = new ExpandWhenSelectedView(parent.getContext(), desiredHeight);
+ result.setHeight(desiredHeight);
+ result.setFocusable(mItemsFocusable);
+ result.setText(getValueAtPosition(position));
+ final AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ result.setLayoutParams(lp);
+ return result;
+ }
+
+
+ @Override
+ public View convertView(int position, View convertView, ViewGroup parent) {
+ ((ExpandWhenSelectedView)convertView).setText(getValueAtPosition(position));
+ return convertView;
+ }
+
+
+ static private class ExpandWhenSelectedView extends TextView {
+
+ private final int mDesiredHeight;
+
+ public ExpandWhenSelectedView(Context context, int desiredHeight) {
+ super(context);
+ mDesiredHeight = desiredHeight;
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ if (selected) {
+ setHeight((int) (mDesiredHeight * 1.5));
+ } else {
+ setHeight(mDesiredHeight);
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListLastItemPartiallyVisible.java b/core/tests/coretests/src/android/widget/listview/ListLastItemPartiallyVisible.java
new file mode 100644
index 0000000..d733749
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListLastItemPartiallyVisible.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * A list where the very last item is partially visible, but still requires scrolling
+ * to bring it into view.
+ */
+public class ListLastItemPartiallyVisible extends ListScenario {
+
+ protected void init(Params params) {
+ params.setNumItems(5)
+ .setItemScreenSizeFactor(0.22);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListManagedCursor.java b/core/tests/coretests/src/android/widget/listview/ListManagedCursor.java
new file mode 100644
index 0000000..12b5ef4
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListManagedCursor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Contacts.People;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.SimpleCursorAdapter;
+import android.widget.AdapterView.OnItemClickListener;
+
+
+public class ListManagedCursor extends ListActivity implements OnItemClickListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Get a cursor with all people
+ Cursor c = getContentResolver().query(Settings.System.CONTENT_URI, null, null, null, null);
+ startManagingCursor(c);
+
+ ListAdapter adapter = new SimpleCursorAdapter(this,
+ // Use a template that displays a text view
+ android.R.layout.simple_list_item_1,
+ // Give the cursor to the list adatper
+ c,
+ // Map the NAME column in the people database to...
+ new String[] {People.NAME} ,
+ // The "text1" view defined in the XML template
+ new int[] {android.R.id.text1});
+ setListAdapter(adapter);
+ getListView().setOnItemClickListener(this);
+ }
+
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ Intent dummyIntent = new Intent(this, ListSimple.class);
+ startActivity(dummyIntent);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/widget/listview/ListManagedCursorTest.java b/core/tests/coretests/src/android/widget/listview/ListManagedCursorTest.java
new file mode 100644
index 0000000..7938cba
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListManagedCursorTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.FlakyTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ListView;
+import android.test.TouchUtils;
+
+/**
+ * Tests restoring the scroll position in a list with a managed cursor.
+ */
+public class ListManagedCursorTest extends ActivityInstrumentationTestCase<ListManagedCursor> {
+ private ListManagedCursor mActivity;
+ private ListView mListView;
+
+ public ListManagedCursorTest() {
+ super("com.android.frameworks.coretests", ListManagedCursor.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ assertEquals(0, mListView.getFirstVisiblePosition());
+ }
+
+ /**
+ * Scroll the list using arrows, launch new activity, hit back, make sure we're still scrolled.
+ */
+ @LargeTest
+ public void testKeyScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisiblePosition = arrowScroll(inst);
+
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+
+ assertTrue("List changed to touch mode", !mListView.isInTouchMode());
+ assertTrue("List did not preserve scroll position",
+ firstVisiblePosition == mListView.getFirstVisiblePosition());
+ }
+
+ /**
+ * Scroll the list using touch, launch new activity, hit back, make sure we're still scrolled.
+ */
+ @LargeTest
+ public void testTouchScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisiblePosition = touchScroll(inst);
+
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+
+ assertTrue("List not in touch mode", mListView.isInTouchMode());
+ assertTrue("List did not preserve scroll position",
+ firstVisiblePosition == mListView.getFirstVisiblePosition());
+ }
+
+ /**
+ * Scroll the list using arrows, launch new activity, change to touch mode, hit back, make sure
+ * we're still scrolled.
+ */
+ @LargeTest
+ public void testKeyScrollingToTouchMode() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisiblePosition = arrowScroll(inst);
+
+ TouchUtils.dragQuarterScreenUp(this);
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+
+ assertTrue("List did not change to touch mode", mListView.isInTouchMode());
+ assertTrue("List did not preserve scroll position",
+ firstVisiblePosition == mListView.getFirstVisiblePosition());
+ }
+
+
+ /**
+ * Scroll the list using touch, launch new activity, change to trackball mode, hit back, make
+ * sure we're still scrolled.
+ */
+ @FlakyTest(tolerance=3)
+ @LargeTest
+ public void testTouchScrollingToTrackballMode() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisiblePosition = touchScroll(inst);
+
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ inst.waitForIdleSync();
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ inst.waitForIdleSync();
+ inst.sendCharacterSync(KeyEvent.KEYCODE_BACK);
+ inst.waitForIdleSync();
+ assertTrue("List not in trackball mode", !mListView.isInTouchMode());
+ assertTrue("List did not preserve scroll position", firstVisiblePosition == mListView
+ .getFirstVisiblePosition());
+ }
+
+ public int arrowScroll(Instrumentation inst) {
+ int count = mListView.getChildCount();
+
+ for (int i = 0; i < count * 2; i++) {
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ inst.waitForIdleSync();
+
+ int firstVisiblePosition = mListView.getFirstVisiblePosition();
+ assertTrue("Arrow scroll did not happen", firstVisiblePosition > 0);
+ assertTrue("List still in touch mode", !mListView.isInTouchMode());
+
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_CENTER);
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ return firstVisiblePosition;
+ }
+
+ public int touchScroll(Instrumentation inst) {
+ TouchUtils.dragQuarterScreenUp(this);
+ inst.waitForIdleSync();
+ TouchUtils.dragQuarterScreenUp(this);
+ inst.waitForIdleSync();
+ TouchUtils.dragQuarterScreenUp(this);
+ inst.waitForIdleSync();
+ TouchUtils.dragQuarterScreenUp(this);
+ inst.waitForIdleSync();
+
+ int firstVisiblePosition = mListView.getFirstVisiblePosition();
+ assertTrue("Touch scroll did not happen", firstVisiblePosition > 0);
+ assertTrue("List not in touch mode", mListView.isInTouchMode());
+
+ TouchUtils.clickView(this, mListView.getChildAt(mListView.getChildCount() - 1));
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ return firstVisiblePosition;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfItemsShorterThanScreen.java b/core/tests/coretests/src/android/widget/listview/ListOfItemsShorterThanScreen.java
new file mode 100644
index 0000000..46decfa
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfItemsShorterThanScreen.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListOfItemsShorterThanScreen extends ListScenario {
+
+ protected void init(Params params) {
+ params.setNumItems(5)
+ .setItemScreenSizeFactor(1.1 / 4)
+ .setItemsFocusable(false);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfItemsTallerThanScreen.java b/core/tests/coretests/src/android/widget/listview/ListOfItemsTallerThanScreen.java
new file mode 100644
index 0000000..0d88993
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfItemsTallerThanScreen.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListOfItemsTallerThanScreen extends ListScenario {
+
+
+ protected void init(Params params) {
+ params.setNumItems(3)
+ .setItemScreenSizeFactor(4.0 / 3)
+ .setItemsFocusable(false);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfShortShortTallShortShort.java b/core/tests/coretests/src/android/widget/listview/ListOfShortShortTallShortShort.java
new file mode 100644
index 0000000..1639aa4
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfShortShortTallShortShort.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Exposes fading in and out multiple items.
+ */
+public class ListOfShortShortTallShortShort extends ListScenario {
+
+
+ protected void init(Params params) {
+ params.setNumItems(5)
+ .setItemScreenSizeFactor(0.1)
+ .setFadingEdgeScreenSizeFactor(0.22)
+ .setPositionScreenSizeFactorOverride(2, 1.1);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfShortTallShort.java b/core/tests/coretests/src/android/widget/listview/ListOfShortTallShort.java
new file mode 100644
index 0000000..960e129
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfShortTallShort.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Two short items separated by one that is taller than the screen.
+ */
+public class ListOfShortTallShort extends ListScenario {
+
+
+ protected void init(Params params) {
+ params.setItemScreenSizeFactor(1.0 / 8)
+ .setNumItems(3)
+ .setPositionScreenSizeFactorOverride(1, 1.2);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfThinItems.java b/core/tests/coretests/src/android/widget/listview/ListOfThinItems.java
new file mode 100644
index 0000000..007479f
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfThinItems.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListOfThinItems extends ListScenario {
+
+ protected void init(Params params) {
+
+ final int numItemsOnScreen = getScreenHeight() / 18;
+
+ params.setNumItems(numItemsOnScreen + 5)
+ .setItemScreenSizeFactor(18.0 / getScreenHeight())
+ .setItemsFocusable(false);
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListOfTouchables.java b/core/tests/coretests/src/android/widget/listview/ListOfTouchables.java
new file mode 100644
index 0000000..919ef69
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListOfTouchables.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import android.util.ListScenario;
+
+/**
+ * Each list item has two focusables that are close enough together that
+ * it shouldn't require panning to move focus.
+ */
+public class ListOfTouchables extends ListScenario {
+
+
+ @Override
+ protected void init(Params params) {
+ params.setItemsFocusable(true)
+ .setItemScreenSizeFactor(0.2)
+ .setNumItems(100);
+ }
+
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ Button b = new Button(this);
+ b.setText("Position " + position);
+ b.setId(position);
+ return b;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListRecyclerProfiling.java b/core/tests/coretests/src/android/widget/listview/ListRecyclerProfiling.java
new file mode 100644
index 0000000..d5d7261
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListRecyclerProfiling.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.ImageButton;
+import android.view.ViewDebug;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+
+public class ListRecyclerProfiling extends Activity {
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_recycler_profiling);
+
+ String values[] = new String[1000];
+ for (int i = 0; i < 1000; i++) {
+ values[i] = ((Integer) i).toString();
+ }
+
+ ListView listView = (ListView) findViewById(R.id.list);
+
+ ViewDebug.startRecyclerTracing("SimpleList", listView);
+
+ listView.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+ ImageButton stopProfiling = (ImageButton) findViewById(R.id.pause);
+ stopProfiling.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ ViewDebug.stopRecyclerTracing();
+ }
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListRetainsFocusAcrossLayoutsTest.java b/core/tests/coretests/src/android/widget/listview/ListRetainsFocusAcrossLayoutsTest.java
new file mode 100644
index 0000000..896bd19
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListRetainsFocusAcrossLayoutsTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.widget.listview.ListItemFocusablesClose;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+
+public class ListRetainsFocusAcrossLayoutsTest extends ActivityInstrumentationTestCase<ListItemFocusablesClose> {
+
+ public ListRetainsFocusAcrossLayoutsTest() {
+ super("com.android.frameworks.coretests", ListItemFocusablesClose.class);
+ }
+
+ private void requestLayoutOnList() {
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ getActivity().getListView().requestLayout();
+ }
+ });
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("top button at position 0 should be focused",
+ getActivity().getChildOfItem(0, 0).isFocused());
+ }
+
+ @MediumTest
+ public void testBottomButtonRetainsFocusAfterLayout() throws Exception {
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue("bottom botton at position 0 should be focused",
+ getActivity().getChildOfItem(0, 2).isFocused());
+
+ requestLayoutOnList();
+ getInstrumentation().waitForIdleSync();
+
+ assertTrue("bottom botton at position 0 should be focused after layout",
+ getActivity().getChildOfItem(0, 2).isFocused());
+ }
+
+ @MediumTest
+ public void testTopButtonOfSecondPositionRetainsFocusAfterLayout() {
+ sendRepeatedKeys(2, KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue("top botton at position 1 should be focused",
+ getActivity().getChildOfItem(1, 0).isFocused());
+
+ requestLayoutOnList();
+ getInstrumentation().waitForIdleSync();
+
+ assertTrue("top botton at position 1 should be focused after layout",
+ getActivity().getChildOfItem(1, 0).isFocused());
+
+ }
+
+ @MediumTest
+ public void testBottomButtonOfSecondPositionRetainsFocusAfterLayout() {
+ sendRepeatedKeys(3, KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue("bottom botton at position 1 should be focused",
+ getActivity().getChildOfItem(1, 2).isFocused());
+
+ requestLayoutOnList();
+ getInstrumentation().waitForIdleSync();
+
+ assertTrue("bottom botton at position 1 should be focused after layout",
+ getActivity().getChildOfItem(1, 2).isFocused());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListScrollListener.java b/core/tests/coretests/src/android/widget/listview/ListScrollListener.java
new file mode 100644
index 0000000..58a31dc
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListScrollListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * Exercises change notification in a list
+ */
+public class ListScrollListener extends ListActivity implements AbsListView.OnScrollListener {
+ Handler mHandler = new Handler();
+ TextView mText;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_scroll_listener);
+
+ String values[] = new String[1000];
+ int i=0;
+ for(i=0; i<1000; i++) {
+ values[i] = ((Integer)i).toString();
+ }
+
+ mText = (TextView) findViewById(R.id.text);
+ setListAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, values));
+
+
+ getListView().setOnScrollListener(this);
+ }
+
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ mText.setText("Position " + position);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ mText.setText("Nothing");
+ }
+
+ public void onScroll(AbsListView view, int firstCell, int cellCount, int itemCount) {
+ int last = firstCell + cellCount - 1;
+ mText.setText("Showing " + firstCell + "-" + last + "/" + itemCount);
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
new file mode 100644
index 0000000..2d6e75e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+import android.test.TouchUtils;
+
+public class ListScrollListenerTest extends ActivityInstrumentationTestCase<ListScrollListener> implements
+ AbsListView.OnScrollListener {
+ private ListScrollListener mActivity;
+ private ListView mListView;
+ private int mFirstVisibleItem = -1;
+ private int mVisibleItemCount = -1;
+ private int mTotalItemCount = -1;
+
+ public ListScrollListenerTest() {
+ super("com.android.frameworks.coretests", ListScrollListener.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ mListView.setOnScrollListener(this);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ assertEquals(0, mFirstVisibleItem);
+ }
+
+ @LargeTest
+ public void testKeyScrolling() {
+ Instrumentation inst = getInstrumentation();
+
+ int firstVisibleItem = mFirstVisibleItem;
+ for (int i = 0; i < mVisibleItemCount * 2; i++) {
+ inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ inst.waitForIdleSync();
+ assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+
+ firstVisibleItem = mFirstVisibleItem;
+ inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+ inst.waitForIdleSync();
+ assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+
+ firstVisibleItem = mFirstVisibleItem;
+ KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
+ KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
+ inst.sendKeySync(down);
+ inst.sendKeySync(up);
+ inst.waitForIdleSync();
+
+ assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+ assertEquals("Full scroll did not happen", mTotalItemCount,
+ mFirstVisibleItem + mVisibleItemCount);
+ }
+
+ @LargeTest
+ public void testTouchScrolling() {
+ int firstVisibleItem = mFirstVisibleItem;
+ TouchUtils.dragQuarterScreenUp(this);
+ TouchUtils.dragQuarterScreenUp(this);
+ assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
+ }
+
+
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ mFirstVisibleItem = firstVisibleItem;
+ mVisibleItemCount = visibleItemCount;
+ mTotalItemCount = totalItemCount;
+ }
+
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListSetSelection.java b/core/tests/coretests/src/android/widget/listview/ListSetSelection.java
new file mode 100644
index 0000000..6c2e264
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListSetSelection.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+import android.view.KeyEvent;
+import android.view.View;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.Button;
+
+/**
+ * List of 1,000 items used to test calls to setSelection() in touch mode.
+ * Pressing the S key will call setSelection(0) on the list.
+ */
+public class ListSetSelection extends ListScenario {
+ private Button mButton;
+
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(1000)
+ .setItemScreenSizeFactor(0.22);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mButton = new Button(this);
+ mButton.setText("setSelection(0)");
+ mButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ getListView().setSelection(0);
+ }
+ });
+
+ getListViewContainer().addView(mButton, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ ));
+ }
+
+ public Button getButton() {
+ return mButton;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_S) {
+ getListView().setSelection(0);
+ return true;
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListSetSelectionTest.java b/core/tests/coretests/src/android/widget/listview/ListSetSelectionTest.java
new file mode 100644
index 0000000..4cef164
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListSetSelectionTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+
+/**
+ * Basic tests of setting & clearing the selection
+ */
+public class ListSetSelectionTest extends ActivityInstrumentationTestCase2<ListSimple> {
+ private ListSimple mActivity;
+ private ListView mListView;
+
+ public ListSetSelectionTest() {
+ super("com.android.frameworks.coretests", ListSimple.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ }
+
+ /** Confirm that we can set the selection to each specific position */
+ @MediumTest
+ @UiThreadTest
+ public void testSetSelection() {
+ // Set the selection to each position
+ int childCount = mListView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ mListView.setSelection(i);
+ assertEquals("Set selection", i, mListView.getSelectedItemPosition());
+ }
+ }
+
+ /** Confirm that you cannot unset the selection using the same API */
+ @MediumTest
+ @UiThreadTest
+ public void testClearSelection() {
+ // Set the selection to first position
+ mListView.setSelection(0);
+ assertEquals("Set selection", 0, mListView.getSelectedItemPosition());
+
+ // Clear the selection
+ mListView.setSelection(ListView.INVALID_POSITION);
+ assertEquals("Set selection", 0, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListSimple.java b/core/tests/coretests/src/android/widget/listview/ListSimple.java
new file mode 100644
index 0000000..6accae1
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListSimple.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ListSimple extends ListScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(1000)
+ .setItemScreenSizeFactor(0.14);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ getListView().setVerticalScrollBarEnabled(true);
+ getListView().setFadingEdgeLength(12);
+ getListView().setVerticalFadingEdgeEnabled(true);
+ }
+
+ @Override
+ protected View createView(int position, ViewGroup parent, int desiredHeight) {
+ View view = super.createView(position, parent, desiredHeight);
+ view.setBackgroundColor(0xFF191919);
+ ((TextView) view).setTextSize(16.0f);
+ return view;
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListTakeFocusFromSide.java b/core/tests/coretests/src/android/widget/listview/ListTakeFocusFromSide.java
new file mode 100644
index 0000000..95f09f6
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListTakeFocusFromSide.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+/**
+ * Exercises moving focus into the list from the side
+ */
+public class ListTakeFocusFromSide extends ListActivity {
+
+
+ private class ThrashListAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+
+ private String[] mTitles = new String[100];
+
+ public ThrashListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTitles = new String[100];
+
+ int i;
+ for (i = 0; i < 100; i++) {
+ mTitles[i] = "[" + i + "]";
+ }
+ }
+
+ public int getCount() {
+ return mTitles.length;
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+
+ if (convertView == null) {
+ view = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, null);
+ } else {
+ view = (TextView) convertView;
+ }
+ view.setText(mTitles[position]);
+ return view;
+ }
+
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_take_focus_from_side);
+ setListAdapter(new ThrashListAdapter(this));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListThrasher.java b/core/tests/coretests/src/android/widget/listview/ListThrasher.java
new file mode 100644
index 0000000..ba3d590
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListThrasher.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.Random;
+
+/**
+ * Exercises change notification in a list
+ */
+public class ListThrasher extends ListActivity implements AdapterView.OnItemSelectedListener {
+ Handler mHandler = new Handler();
+ ThrashListAdapter mAdapter;
+ Random mRandomizer = new Random();
+ TextView mText;
+
+ Runnable mThrash = new Runnable() {
+ public void run() {
+ mAdapter.bumpVersion();
+ mHandler.postDelayed(mThrash, 500);
+ }
+ };
+
+ private class ThrashListAdapter extends BaseAdapter {
+ private LayoutInflater mInflater;
+
+ /**
+ * Our data, part 1.
+ */
+ private String[] mTitles = new String[100];
+
+ /**
+ * Our data, part 2.
+ */
+ private int[] mVersion = new int[100];
+
+ public ThrashListAdapter(Context context) {
+ mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mTitles = new String[100];
+ mVersion = new int[100];
+
+ int i;
+ for (i = 0; i < 100; i++) {
+ mTitles[i] = "[" + i + "]";
+ mVersion[i] = 0;
+ }
+ }
+
+ public int getCount() {
+ return mTitles.length;
+ }
+
+ public Object getItem(int position) {
+ return position;
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView view;
+
+ if (convertView == null) {
+ view = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, null);
+ } else {
+ view = (TextView) convertView;
+ }
+ view.setText(mTitles[position] + " " + mVersion[position]);
+ return view;
+ }
+
+
+ public void bumpVersion() {
+ int position = mRandomizer.nextInt(getCount());
+ mVersion[position]++;
+ notifyDataSetChanged();
+ }
+
+
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.list_thrasher);
+
+ mText = (TextView) findViewById(R.id.text);
+ mAdapter = new ThrashListAdapter(this);
+ setListAdapter(mAdapter);
+
+ mHandler.postDelayed(mThrash, 5000);
+
+ getListView().setOnItemSelectedListener(this);
+ }
+
+ public void onItemSelected(AdapterView parent, View v, int position, long id) {
+ mText.setText("Position " + position);
+ }
+
+ public void onNothingSelected(AdapterView parent) {
+ mText.setText("Nothing");
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListTopGravity.java b/core/tests/coretests/src/android/widget/listview/ListTopGravity.java
new file mode 100644
index 0000000..986cc57
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListTopGravity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.view.Gravity;
+
+import android.util.ListScenario;
+
+/**
+ * Basic top gravity scenario, nothing fancy. Items do not
+ * fill the screen
+ */
+public class ListTopGravity extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setMustFillScreen(false)
+ .setNumItems(2)
+ .setItemScreenSizeFactor(0.22);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListTopGravityMany.java b/core/tests/coretests/src/android/widget/listview/ListTopGravityMany.java
new file mode 100644
index 0000000..5592ad9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListTopGravityMany.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Basic top gravity scenario, nothing fancy. There are
+ * more items than fit on the screen
+ */
+public class ListTopGravityMany extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(10)
+ .setItemScreenSizeFactor(0.22);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListUnspecifiedMeasure.java b/core/tests/coretests/src/android/widget/listview/ListUnspecifiedMeasure.java
new file mode 100644
index 0000000..199d069
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListUnspecifiedMeasure.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.app.Activity;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+
+public class ListUnspecifiedMeasure<T extends Activity> extends ActivityInstrumentationTestCase<T> {
+ private T mActivity;
+ private ListView mListView;
+
+ protected ListUnspecifiedMeasure(Class<T> klass) {
+ super("com.android.frameworks.coretests", klass);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = (ListView) mActivity.findViewById(R.id.list);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ }
+
+ @MediumTest
+ public void testWasMeasured() {
+ assertTrue(mListView.getMeasuredWidth() > 0);
+ assertTrue(mListView.getWidth() > 0);
+ assertTrue(mListView.getMeasuredHeight() > 0);
+ assertTrue(mListView.getHeight() > 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListViewHeight.java b/core/tests/coretests/src/android/widget/listview/ListViewHeight.java
new file mode 100644
index 0000000..64f280a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListViewHeight.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.frameworks.coretests.R;
+
+public class ListViewHeight extends Activity {
+
+ private View mButton1;
+ private View mButton2;
+ private View mButton3;
+
+ private View mOuterLayout;
+ private ListView mInnerList;
+
+ ArrayAdapter<String> mAdapter;
+ private String[] mStrings = {
+ "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi" };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.linear_layout_listview_height);
+
+ mButton1 = findViewById(R.id.button1);
+ mButton2 = findViewById(R.id.button2);
+ mButton3 = findViewById(R.id.button3);
+
+ mOuterLayout = findViewById(R.id.layout);
+ mInnerList = (ListView)findViewById(R.id.inner_list);
+
+ mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line,
+ mStrings);
+
+ // Clicking this button will show the list view and set it to a fixed height
+ // If you then hide the views, there is no problem.
+ mButton1.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // set listview to fixed height
+ ViewGroup.MarginLayoutParams lp;
+ lp = (ViewGroup.MarginLayoutParams) mInnerList.getLayoutParams();
+ lp.height = 200;
+ mInnerList.setLayoutParams(lp);
+ // enable list adapter
+ mInnerList.setAdapter(mAdapter);
+ // and show it
+ mOuterLayout.setVisibility(View.VISIBLE);
+ }
+ });
+
+ // Clicking this button will show the list view and set it match_parent height
+ // If you then hide the views, there is an NPE when calculating the ListView height.
+ mButton2.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ // set listview to fill screen
+ ViewGroup.MarginLayoutParams lp;
+ lp = (ViewGroup.MarginLayoutParams) mInnerList.getLayoutParams();
+ lp.height = lp.MATCH_PARENT;
+ mInnerList.setLayoutParams(lp);
+ // enable list adapter
+ mInnerList.setAdapter(mAdapter);
+ // and show it
+ mOuterLayout.setVisibility(View.VISIBLE);
+ }
+ });
+
+ // Clicking this button will remove the list adapter and hide the outer enclosing view.
+ // We have to climb all the way to the top because the bug (not checking visibility)
+ // only occurs at the very outer loop of ViewRoot.performTraversals and in the case of
+ // an Activity, this means you have to crawl all the way out to the root view.
+ // In the search manager, it's sufficient to simply show/hide the outer search manager
+ // view to trigger the same bug.
+ mButton3.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ mInnerList.setAdapter(null);
+ // hide listview's owner
+ // as it turns out, the owner doesn't take us high enough
+ // because our activity includes a title bar, thus another layer
+ View parent = (View) mOuterLayout.getParent(); // FrameLayout (app container)
+ View grandpa = (View) parent.getParent(); // LinearLayout (title+app)
+ View great = (View) grandpa.getParent(); // PhoneWindow.DecorView
+ great.setVisibility(View.GONE);
+ }
+ });
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListViewHeightTest.java b/core/tests/coretests/src/android/widget/listview/ListViewHeightTest.java
new file mode 100644
index 0000000..5ab2757
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListViewHeightTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.app.Instrumentation;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.widget.ListView;
+
+import com.android.frameworks.coretests.R;
+import android.widget.listview.ListViewHeight;
+
+public class ListViewHeightTest extends ActivityInstrumentationTestCase<ListViewHeight> {
+ private ListViewHeight mActivity;
+
+
+ public ListViewHeightTest() {
+ super("com.android.frameworks.coretests", ListViewHeight.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ }
+
+ @MediumTest
+ public void testButtons() {
+ Instrumentation inst = getInstrumentation();
+ final Button button1 = (Button) mActivity.findViewById(R.id.button1);
+ final Button button2 = (Button) mActivity.findViewById(R.id.button2);
+ final Button button3 = (Button) mActivity.findViewById(R.id.button3);
+
+
+ ListView list = (ListView) mActivity.findViewById(R.id.inner_list);
+ assertEquals("Unexpected items in adapter", 0, list.getCount());
+ assertEquals("Unexpected children in list view", 0, list.getChildCount());
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ button1.performClick();
+ }
+ });
+ inst.waitForIdleSync();
+ assertTrue("List not be visible after clicking button1", list.isShown());
+ assertTrue("List incorrect height", list.getHeight() == 200);
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ button2.performClick();
+ }
+ });
+ inst.waitForIdleSync();
+ assertTrue("List not be visible after clicking button2", list.isShown());
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ button3.performClick();
+ }
+ });
+ inst.waitForIdleSync();
+ assertFalse("List should not be visible clicking button3", list.isShown());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithDisappearingItemBug.java b/core/tests/coretests/src/android/widget/listview/ListWithDisappearingItemBug.java
new file mode 100644
index 0000000..348ea1b
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithDisappearingItemBug.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.Contacts.People;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.LayoutAnimationController;
+import android.view.animation.TranslateAnimation;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.Toast;
+
+/**
+ * See 1080989. You need some contacts for this adapter.
+ */
+public class ListWithDisappearingItemBug extends ListActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Toast.makeText(this, "Make sure you rotate screen to see bug", Toast.LENGTH_LONG).show();
+
+ // Get a cursor with all people
+ Cursor c = getContentResolver().query(People.CONTENT_URI, null, null, null, null);
+ startManagingCursor(c);
+
+ ListAdapter adapter = new SimpleCursorAdapter(this,
+ // Use a template that displays a text view
+ R.layout.list_with_disappearing_item_bug_item,
+ // Give the cursor to the list adatper
+ c,
+ // Map the NAME column in the people database to...
+ new String[] {People.NAME} ,
+ // The "text1" view defined in the XML template
+ new int[] {R.id.text1});
+ setListAdapter(adapter);
+
+ AnimationSet set = new AnimationSet(true);
+
+ Animation animation = new AlphaAnimation(0.0f, 1.0f);
+ animation.setDuration(50);
+ set.addAnimation(animation);
+
+ animation = new TranslateAnimation(
+ Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f,
+ Animation.RELATIVE_TO_SELF, -1.0f,Animation.RELATIVE_TO_SELF, 0.0f
+ );
+ animation.setDuration(100);
+ set.addAnimation(animation);
+
+ LayoutAnimationController controller =
+ new LayoutAnimationController(set, 0.5f);
+ ListView listView = getListView();
+ listView.setLayoutAnimation(controller);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithEditTextHeader.java b/core/tests/coretests/src/android/widget/listview/ListWithEditTextHeader.java
new file mode 100644
index 0000000..5303faf
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithEditTextHeader.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * A list view with a single edit text in a header.
+ */
+public class ListWithEditTextHeader extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setHeaderViewCount(1)
+ .setHeaderFocusable(true)
+ .setItemsFocusable(true)
+ .setNumItems(6)
+ .setItemScreenSizeFactor(0.2);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithEmptyView.java b/core/tests/coretests/src/android/widget/listview/ListWithEmptyView.java
new file mode 100644
index 0000000..74dd06c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithEmptyView.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+
+
+/**
+ * Tests using an empty view with a list */
+public class ListWithEmptyView extends ListActivity {
+
+ private class CarefulAdapter<T> extends ArrayAdapter<T> {
+
+ public CarefulAdapter(Context context, int textViewResourceId) {
+ super(context, textViewResourceId);
+ // TODO Auto-generated constructor stub
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (position < 0 || position >= this.getCount()) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ return super.getItemId(position);
+ }
+
+
+ }
+
+ public static final int MENU_ADD = Menu.FIRST + 1;
+ public static final int MENU_REMOVE = Menu.FIRST + 2;
+
+ private CarefulAdapter<String> mAdapter;
+
+ private int mNextItem = 0;
+
+ private View mEmptyView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mAdapter = new CarefulAdapter<String>(this,
+ android.R.layout.simple_list_item_1);
+ setContentView(R.layout.list_with_empty_view);
+ setListAdapter(mAdapter);
+
+ mEmptyView = findViewById(R.id.empty);
+ getListView().setEmptyView(mEmptyView);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ menu.add(0, MENU_ADD, 0, R.string.menu_add)
+ .setIcon(android.R.drawable.ic_menu_add);
+ menu.add(0, MENU_REMOVE, 0, R.string.menu_remove)
+ .setIcon(android.R.drawable.ic_menu_delete);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case MENU_ADD:
+ String str = "Item + " + mNextItem++;
+ mAdapter.add(str);
+ return true;
+ case MENU_REMOVE:
+ if (mAdapter.getCount() > 0) {
+ mAdapter.remove(mAdapter.getItem(0));
+ }
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public View getEmptyView() {
+ return mEmptyView;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithFirstScreenUnSelectable.java b/core/tests/coretests/src/android/widget/listview/ListWithFirstScreenUnSelectable.java
new file mode 100644
index 0000000..5261283
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithFirstScreenUnSelectable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.listview;
+
+import android.util.ListScenario;
+
+/**
+ * The first item is unselectable, and takes up the whole screen.
+ */
+public class ListWithFirstScreenUnSelectable extends ListScenario {
+
+ @Override
+ protected void init(Params params) {
+ params.setItemScreenSizeFactor(1.2)
+ .setNumItems(2)
+ .setPositionsUnselectable(0);
+
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithHeaders.java b/core/tests/coretests/src/android/widget/listview/ListWithHeaders.java
new file mode 100644
index 0000000..aea091a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithHeaders.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+public class ListWithHeaders extends ListScenario {
+ @Override
+ protected void init(Params params) {
+ params.setStackFromBottom(false)
+ .setStartingSelectionPosition(-1)
+ .setNumItems(24)
+ .setItemScreenSizeFactor(0.14);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ final ListView listView = getListView();
+ listView.setItemsCanFocus(true);
+
+ for (int i = 0; i < 12; i++) {
+ Button header = new Button(this);
+ header.setText("Header View");
+ listView.addHeaderView(header);
+ }
+
+ for (int i = 0; i < 12; i++) {
+ Button footer = new Button(this);
+ footer.setText("Footer View");
+ listView.addFooterView(footer);
+ }
+
+ final ListAdapter adapter = listView.getAdapter();
+ listView.setAdapter(adapter);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithNoFadingEdge.java b/core/tests/coretests/src/android/widget/listview/ListWithNoFadingEdge.java
new file mode 100644
index 0000000..b870fc8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithNoFadingEdge.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListWithNoFadingEdge extends ListScenario {
+
+ protected void init(Params params) {
+ params.setFadingEdgeScreenSizeFactor(0.0)
+ .setNumItems(10)
+ .setItemScreenSizeFactor(0.2);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithOffScreenNextSelectable.java b/core/tests/coretests/src/android/widget/listview/ListWithOffScreenNextSelectable.java
new file mode 100644
index 0000000..2e65bd0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithOffScreenNextSelectable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Pressing down from position 0 requires looking past positions 1, 2 and 3 to
+ * an offscreen item to know that it is the next selectable.
+ */
+public class ListWithOffScreenNextSelectable extends ListScenario {
+
+
+ protected void init(Params params) {
+ params.setItemsFocusable(false)
+ .setNumItems(5)
+ .setItemScreenSizeFactor(0.25)
+ .setPositionUnselectable(1)
+ .setPositionUnselectable(2)
+ .setPositionUnselectable(3);
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithOnItemSelectedAction.java b/core/tests/coretests/src/android/widget/listview/ListWithOnItemSelectedAction.java
new file mode 100644
index 0000000..26e1d5d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithOnItemSelectedAction.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.widget.TextView;
+import android.util.ListScenario;
+
+/**
+ * The header text view echos the value of the selected item by using (indirectly)
+ * the {@link android.widget.AdapterView.OnItemSelectedListener}.
+ */
+public class ListWithOnItemSelectedAction extends ListScenario {
+ protected void init(Params params) {
+ params.setNumItems(8)
+ .setItemScreenSizeFactor(0.2)
+ .includeHeaderAboveList(true);
+
+ }
+
+ @Override
+ protected void positionSelected(int positon) {
+ if (positon != getListView().getSelectedItemPosition()) {
+ throw new IllegalStateException("something is fishy... the selected postion does not " +
+ "match what the list reports.");
+ }
+ setHeaderValue(
+ ((TextView) getListView().getSelectedView()).getText().toString());
+
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithScreenOfNoSelectables.java b/core/tests/coretests/src/android/widget/listview/ListWithScreenOfNoSelectables.java
new file mode 100644
index 0000000..108ac4d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithScreenOfNoSelectables.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+public class ListWithScreenOfNoSelectables extends ListScenario {
+
+ protected void init(Params params) {
+ params.setNumItems(10)
+ .setItemScreenSizeFactor(0.2)
+ .setPositionsUnselectable(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/ListWithSeparators.java b/core/tests/coretests/src/android/widget/listview/ListWithSeparators.java
new file mode 100644
index 0000000..0f4f2d8
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/ListWithSeparators.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview;
+
+import android.util.ListScenario;
+
+/**
+ * Basic separator scenario, nothing fancy.
+ */
+public class ListWithSeparators extends ListScenario {
+
+ protected void init(Params params) {
+ params.setItemsFocusable(false)
+ .setNumItems(5)
+ .setItemScreenSizeFactor(0.22)
+ .setPositionUnselectable(0)
+ .setPositionUnselectable(2);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java
new file mode 100644
index 0000000..6238dab
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import android.widget.listview.ListInterleaveFocusables;
+import android.util.ListUtil;
+
+public class ListInterleaveFocusablesTest extends ActivityInstrumentationTestCase<ListInterleaveFocusables> {
+ private ListView mListView;
+ private ListUtil mListUtil;
+
+ public ListInterleaveFocusablesTest() {
+ super("com.android.frameworks.coretests", ListInterleaveFocusables.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ mListUtil = new ListUtil(mListView, getInstrumentation());
+ }
+
+ @LargeTest
+ public void testPreconditions() {
+ assertEquals(7, mListView.getChildCount());
+ assertTrue(mListView.getChildAt(1).isFocusable());
+ assertTrue(mListView.getChildAt(3).isFocusable());
+ assertTrue(mListView.getChildAt(6).isFocusable());
+ }
+
+ @MediumTest
+ public void testGoingFromUnFocusableSelectedToFocusable() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals("selected item position", 1, mListView.getSelectedItemPosition());
+ assertSelectedViewFocus(true);
+ }
+
+ // go down from an item that isn't focusable, make sure it finds the focusable
+ // below (instead of above). this exposes a (now fixed) bug where the focus search
+ // was not starting from the right spot
+ @MediumTest
+ public void testGoingDownFromUnFocusableSelectedToFocusableWithOtherFocusableAbove() {
+ mListUtil.setSelectedPosition(2);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("selected item position", 3, mListView.getSelectedItemPosition());
+ assertSelectedViewFocus(true);
+ }
+
+ // same, but going up
+ @MediumTest
+ public void testGoingUpFromUnFocusableSelectedToFocusableWithOtherFocusableAbove() {
+ mListUtil.setSelectedPosition(2);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("selected item position", 1, mListView.getSelectedItemPosition());
+ assertSelectedViewFocus(true);
+ }
+
+ /**
+ * Go down from a focusable when there is a focusable below, but it is more than
+ * one item away; make sure it won't give that item focus because it is too far away.
+ */
+ @MediumTest
+ public void testGoingDownFromFocusableToUnfocusableWhenFocusableIsBelow() {
+ mListUtil.setSelectedPosition(3);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("selected item position", 4, mListView.getSelectedItemPosition());
+ assertSelectedViewFocus(false);
+ }
+
+ // same but going up
+ @MediumTest
+ public void testGoingUpFromFocusableToUnfocusableWhenFocusableIsBelow() {
+ mListUtil.setSelectedPosition(6);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("selected item position", 5, mListView.getSelectedItemPosition());
+ assertSelectedViewFocus(false);
+ }
+
+ public void assertSelectedViewFocus(boolean isFocused) {
+ final View view = mListView.getSelectedView();
+ assertEquals("selected view focused", isFocused, view.isFocused());
+ assertEquals("selected position's isSelected should be the inverse "
+ + "of it having focus", !isFocused, view.isSelected());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusableAboveUnfocusableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusableAboveUnfocusableTest.java
new file mode 100644
index 0000000..82f4880
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusableAboveUnfocusableTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.widget.listview.ListItemFocusableAboveUnfocusable;
+
+public class ListItemFocusableAboveUnfocusableTest extends ActivityInstrumentationTestCase<ListItemFocusableAboveUnfocusable> {
+ private ListView mListView;
+
+ public ListItemFocusableAboveUnfocusableTest() {
+ super("com.android.frameworks.coretests", ListItemFocusableAboveUnfocusable.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ }
+
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals("selected position", 0, mListView.getSelectedItemPosition());
+ assertTrue(mListView.getChildAt(0).isFocused());
+ assertFalse(mListView.getChildAt(1).isFocusable());
+ }
+
+ @MediumTest
+ public void testMovingToUnFocusableTakesFocusAway() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertFalse("focused item should have lost focus",
+ mListView.getChildAt(0).isFocused());
+ assertEquals("selected position", 1, mListView.getSelectedItemPosition());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesCloseTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesCloseTest.java
new file mode 100644
index 0000000..3b30ebe
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesCloseTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.listview.ListItemFocusablesClose;
+
+public class ListItemFocusablesCloseTest extends ActivityInstrumentationTestCase<ListItemFocusablesClose> {
+ private ListView mListView;
+ private int mListTop;
+ private int mListBottom;
+
+ public ListItemFocusablesCloseTest() {
+ super("com.android.frameworks.coretests", ListItemFocusablesClose.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception{
+ super.setUp();
+ mListView = getActivity().getListView();
+ mListTop = mListView.getListPaddingTop();
+ mListBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertTrue(mListView.getAdapter().areAllItemsEnabled());
+ assertTrue(mListView.getItemsCanFocus());
+ assertEquals(0, mListView.getSelectedItemPosition());
+ final LinearLayout first = (LinearLayout) mListView.getSelectedView();
+ getInstrumentation().waitForIdleSync();
+ assertEquals("first item should be at top of screen",
+ mListView.getListPaddingTop(),
+ first.getTop());
+ assertTrue("first button of first list item should have focus",
+ first.getChildAt(0).isFocused());
+ assertTrue("item should be shorter than list for this test to make sense",
+ first.getHeight() < mListView.getHeight());
+ assertEquals("two items should be on screen",
+ 2, mListView.getChildCount());
+ assertTrue("first button of second item should be on screen",
+ getActivity().getChildOfItem(1, 0).getBottom() < mListBottom);
+ }
+
+
+ @MediumTest
+ public void testChangeFocusWithinItem() {
+ final LinearLayout first = (LinearLayout) mListView.getSelectedView();
+ final int topOfFirstItemBefore = first.getTop();
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue("focus should have moved to second button of first item",
+ first.getChildAt(2).isFocused());
+ assertEquals("selection should not have changed",
+ 0, mListView.getSelectedItemPosition());
+ assertEquals("list item should not have been shifted",
+ topOfFirstItemBefore, first.getTop());
+
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertTrue("focus should have moved back to first button of first item",
+ first.getChildAt(0).isFocused());
+ assertEquals("list item should not have been shifted",
+ topOfFirstItemBefore, first.getTop());
+ }
+
+ @MediumTest
+ public void testMoveDownToButtonInDifferentSelection() {
+ final LinearLayout first = (LinearLayout) mListView.getSelectedView();
+ final int topOfFirstItemBefore = first.getTop();
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals("selection should have moved to second item",
+ 1, mListView.getSelectedItemPosition());
+ final LinearLayout selectedItem = (LinearLayout) mListView.getSelectedView();
+ assertTrue("first button of second item should have focus",
+ selectedItem.getChildAt(0).isFocused());
+ assertEquals("list item should not have been shifted",
+ topOfFirstItemBefore, first.getTop());
+ }
+
+ @MediumTest
+ public void testMoveUpToButtonInDifferentSelection() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(1, mListView.getSelectedItemPosition());
+ assertTrue("first button of second item should have focus",
+ getActivity().getChildOfItem(1, 0).hasFocus());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("first list item should have selection", 0,
+ mListView.getSelectedItemPosition());
+ assertTrue("second button of first item should have focus",
+ getActivity().getChildOfItem(0, 2).hasFocus());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesFarApartTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesFarApartTest.java
new file mode 100644
index 0000000..475930d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemFocusablesFarApartTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.listview.ListItemFocusablesFarApart;
+
+public class ListItemFocusablesFarApartTest extends ActivityInstrumentationTestCase<ListItemFocusablesFarApart> {
+ private ListView mListView;
+ private int mListTop;
+ private int mListBottom;
+
+ public ListItemFocusablesFarApartTest() {
+ super("com.android.frameworks.coretests", ListItemFocusablesFarApart.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ mListTop = mListView.getListPaddingTop();
+ mListBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ }
+
+ /**
+ * Get the child of a list item.
+ * @param listIndex The index of the currently visible items
+ * @param index The index of the child.
+ */
+ public View getChildOfItem(int listIndex, int index) {
+ return ((ViewGroup) mListView.getChildAt(listIndex)).getChildAt(index);
+
+ }
+
+ public int getTopOfChildOfItem(int listIndex, int index) {
+ ViewGroup listItem = (ViewGroup) mListView.getChildAt(listIndex);
+ View child = listItem.getChildAt(index);
+ return child.getTop() + listItem.getTop();
+ }
+
+ public int getBottomOfChildOfItem(int listIndex, int index) {
+ ViewGroup listItem = (ViewGroup) mListView.getChildAt(listIndex);
+ View child = listItem.getChildAt(index);
+ return child.getBottom() + listItem.getTop();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertEquals("should only be one visible list item",
+ 1, mListView.getChildCount());
+ int topOfFirstButton = getTopOfChildOfItem(0, 0);
+ int topOfSecondButton = getTopOfChildOfItem(0, 2);
+ assertTrue("second button should be more than max scroll away from first",
+ topOfSecondButton - topOfFirstButton > mListView.getMaxScrollAmount());
+ }
+
+
+ @MediumTest
+ public void testPanWhenNextFocusableTooFarDown() {
+
+ int expectedTop = mListView.getChildAt(0).getTop();
+
+ final Button topButton = (Button) getChildOfItem(0, 0);
+
+ int counter = 0;
+ while(getTopOfChildOfItem(0, 2) > mListBottom) {
+ // just to make sure we never end up with an infinite loop
+ if (counter > 5) fail("couldn't reach next button within " + counter + " downs");
+
+ if (getBottomOfChildOfItem(0, 0) < mListTop) {
+ assertFalse("after " + counter + " downs, top button not visible, should not have focus",
+ topButton.isFocused());
+ assertFalse("after " + counter + " downs, neither top button nor botom button visible, nothng within first list " +
+ "item should have focus", mListView.getChildAt(0).hasFocus());
+ } else {
+ assertTrue("after " + counter + " downs, top button still visible, should have focus",
+ topButton.isFocused());
+ }
+
+ assertEquals("after " + counter + " downs, " +
+ "should have panned by max scroll amount",
+ expectedTop, mListView.getChildAt(0).getTop());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ expectedTop -= mListView.getMaxScrollAmount();
+ counter++;
+ }
+
+ // at this point, the second button is visible on screen.
+ // it should have focus
+ assertTrue("second button should have focus",
+ getChildOfItem(0, 2).isFocused());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemsExpandOnSelectionTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemsExpandOnSelectionTest.java
new file mode 100644
index 0000000..e4b5c18
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListItemsExpandOnSelectionTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.widget.listview.ListItemsExpandOnSelection;
+
+public class ListItemsExpandOnSelectionTest extends ActivityInstrumentationTestCase<ListItemsExpandOnSelection> {
+ private ListView mListView;
+ private int mListTop;
+ private int mListBottom;
+ private int mExpandedHeight;
+ private int mNormalHeight;
+
+ public ListItemsExpandOnSelectionTest() {
+ super("com.android.frameworks.coretests",
+ ListItemsExpandOnSelection.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ mListTop = mListView.getListPaddingTop();
+ mListBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ mExpandedHeight = mListView.getChildAt(0).getHeight();
+ mNormalHeight = mListView.getChildAt(1).getHeight();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals(0, mListView.getSelectedItemPosition());
+ assertEquals("selected item should be 1.5 times taller than the others",
+ mExpandedHeight, (int) (mNormalHeight * 1.5));
+ }
+
+ @MediumTest
+ public void testMoveSelectionDownNotRequiringScroll() {
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals(1, mListView.getSelectedItemPosition());
+ assertEquals("first item's top should not have shifted",
+ mListTop, mListView.getChildAt(0).getTop());
+
+ }
+
+ @MediumTest
+ public void testMoveSelectionUpNotRequiringScroll() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals(1, mListView.getSelectedItemPosition());
+ final int oldBottom = mListView.getSelectedView().getBottom();
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ assertEquals("bottom of 2nd itme should have stayed the same when " +
+ "moving back up",
+ oldBottom,
+ mListView.getChildAt(1).getBottom());
+ }
+
+ @MediumTest
+ public void testMoveSelectionDownRequiringScroll() {
+ int lastItemIndex = mListView.getChildCount() - 1;
+
+ for(int i = 0; i < lastItemIndex; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals("list position", lastItemIndex, mListView.getSelectedItemPosition());
+ assertEquals("expanded height", mExpandedHeight, mListView.getSelectedView().getHeight());
+ assertEquals("should be above bottom fading edge",
+ mListBottom - mListView.getVerticalFadingEdgeLength(),
+ mListView.getSelectedView().getBottom());
+ }
+
+ @LargeTest
+ public void testMoveSelectionUpRequiringScroll() {
+ int childrenPerScreen = mListView.getChildCount();
+
+ // go down past last child on screen
+ for(int i = 0; i < childrenPerScreen; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ // go back up to second to last
+ for(int i = 0; i < childrenPerScreen - 1; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals("list position", 1, mListView.getSelectedItemPosition());
+ assertEquals("expanded height", mExpandedHeight, mListView.getSelectedView().getHeight());
+ assertEquals("should be below top fading edge",
+ mListTop + mListView.getVerticalFadingEdgeLength(),
+ mListView.getSelectedView().getTop());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListLastItemPartiallyVisibleTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListLastItemPartiallyVisibleTest.java
new file mode 100644
index 0000000..5bc121a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListLastItemPartiallyVisibleTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.View;
+import android.view.KeyEvent;
+import android.widget.listview.ListLastItemPartiallyVisible;
+
+public class ListLastItemPartiallyVisibleTest extends ActivityInstrumentationTestCase<ListLastItemPartiallyVisible> {
+ private ListView mListView;
+ private int mListBottom;
+
+
+ public ListLastItemPartiallyVisibleTest() {
+ super("com.android.frameworks.coretests", ListLastItemPartiallyVisible.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ mListBottom = mListView.getHeight() - mListView.getPaddingBottom();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals("number of elements visible should be the same as number of items " +
+ "in adapter", mListView.getCount(), mListView.getChildCount());
+
+ final View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+ assertTrue("last item should be partially off screen",
+ lastChild.getBottom() > mListBottom);
+ assertEquals("selected position", 0, mListView.getSelectedItemPosition());
+ }
+
+ // reproduce bug 998094
+ @MediumTest
+ public void testMovingDownToFullyVisibleNoScroll() {
+ final View firstChild = mListView.getChildAt(0);
+ final int firstBottom = firstChild.getBottom();
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("shouldn't have scrolled: bottom of first child changed.",
+ firstBottom, firstChild.getBottom());
+ }
+
+ // reproduce bug 998094
+ @MediumTest
+ public void testMovingUpToFullyVisibleNoScroll() {
+ int numMovesToLast = mListView.getCount() - 1;
+ for (int i = 0; i < numMovesToLast; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("should have moved to last position",
+ mListView.getChildCount() - 1, mListView.getSelectedItemPosition());
+
+ final View lastChild = mListView.getSelectedView();
+ final int lastTop = lastChild.getTop();
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("shouldn't have scrolled: top of last child changed.",
+ lastTop, lastChild.getTop());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsShorterThanScreenTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsShorterThanScreenTest.java
new file mode 100644
index 0000000..eacde5b
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsShorterThanScreenTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.listview.ListOfItemsShorterThanScreen;
+
+public class ListOfItemsShorterThanScreenTest
+ extends ActivityInstrumentationTestCase<ListOfItemsShorterThanScreen> {
+ private ListView mListView;
+ private ListOfItemsShorterThanScreen mActivity;
+
+
+ public ListOfItemsShorterThanScreenTest() {
+ super("com.android.frameworks.coretests", ListOfItemsShorterThanScreen.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals(0, mListView.getSelectedItemPosition());
+ assertTrue(mListView.getChildAt(0).isSelected());
+ assertEquals(mListView.getListPaddingTop(), mListView.getSelectedView().getTop());
+ }
+
+ @MediumTest
+ public void testMoveDownToOnScreenNextItem() {
+ final View next = mListView.getChildAt(1);
+ assertFalse(next.isSelected());
+ final int secondPosition = mListView.getSelectedView().getBottom();
+ assertEquals("second item should be positioned item height pixels from top.",
+ secondPosition,
+ next.getTop());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(1, mListView.getSelectedItemPosition());
+ assertTrue(next.isSelected());
+ assertEquals("next selected item shouldn't have moved",
+ secondPosition,
+ next.getTop());
+ }
+
+ @MediumTest
+ public void testMoveUpToOnScreenItem() {
+ // move down one, then back up
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testMoveDownToItemRequiringScrolling() {
+ final int lastOnScreenItemIndex = mListView.getChildCount() - 1;
+ final View lastItem = mListView.getChildAt(lastOnScreenItemIndex);
+ assertTrue("last item should be partially off screen",
+ lastItem.getBottom() > mListView.getBottom());
+ assertEquals(mListView.getListPaddingTop(), mListView.getSelectedView().getTop());
+
+ for (int i = 0; i < lastOnScreenItemIndex; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ assertEquals(lastOnScreenItemIndex, mListView.getSelectedItemPosition());
+ assertEquals(
+ getTopOfBottomFadingEdge(),
+ mListView.getSelectedView().getBottom());
+
+ // there should be a peeking view
+
+ // the current view isn't the last anymore...
+ assertEquals(mListView.getSelectedView(), mListView.getChildAt(mListView.getChildCount() - 2));
+
+ // peeking view is now last
+ final TextView view = (TextView) mListView.getChildAt(mListView.getChildCount() - 1);
+ assertEquals(mActivity.getValueAtPosition(lastOnScreenItemIndex + 1), view.getText());
+ assertFalse(view.isSelected());
+ }
+
+ @MediumTest
+ public void testMoveUpToItemRequiringScrolling() {
+ // go down to one past last item, then back up to the second item. this will
+ // require scrolling to get it back on screen, and will need a peeking edge
+
+ int numItemsOnScreen = mListView.getChildCount();
+ for (int i = 0; i < numItemsOnScreen; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ for (int i = 0; i < numItemsOnScreen - 1; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+
+ assertEquals(1, mListView.getSelectedItemPosition());
+ assertEquals("top should be just below vertical fading edge",
+ mListView.getVerticalFadingEdgeLength() + mListView.getListPaddingTop(),
+ mListView.getSelectedView().getTop());
+ }
+
+ @MediumTest
+ public void testPressUpWhenAlreadyAtTop() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testPressDownWhenAlreadyAtBottom() {
+ final int lastItemPosition = mListView.getAdapter().getCount() - 1;
+ for (int i = 0; i < lastItemPosition; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals(lastItemPosition, mListView.getSelectedItemPosition());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(lastItemPosition, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testNoVerticalFadingEdgeWhenMovingToBottom() {
+ final int lastItemPosition = mListView.getAdapter().getCount() - 1;
+ for (int i = 0; i < lastItemPosition; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals(lastItemPosition, mListView.getSelectedItemPosition());
+
+ assertEquals("bottom of last item should be just above padding; no fading edge.",
+ mListView.getHeight() - mListView.getListPaddingBottom(),
+ mListView.getSelectedView().getBottom());
+
+ }
+
+ // the top of the bottom fading edge takes into account the list padding at the bottom,
+ // and the fading edge size
+ private int getTopOfBottomFadingEdge() {
+ return mListView.getHeight() - (mListView.getVerticalFadingEdgeLength() + mListView.getListPaddingBottom());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java
new file mode 100644
index 0000000..5960942
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.View;
+import android.view.KeyEvent;
+import android.widget.listview.ListOfItemsTallerThanScreen;
+
+public class ListOfItemsTallerThanScreenTest
+ extends ActivityInstrumentationTestCase<ListOfItemsTallerThanScreen> {
+
+ private ListView mListView;
+ private ListOfItemsTallerThanScreen mActivity;
+
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ public ListOfItemsTallerThanScreenTest() {
+ super("com.android.frameworks.coretests", ListOfItemsTallerThanScreen.class);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertEquals("should only be one visible child", 1, mListView.getChildCount());
+ final int amountOffScreen = mListView.getChildAt(0).getBottom() - (mListView.getBottom() - mListView.getListPaddingBottom());
+ assertTrue("must be more than max scroll off screen for this test to work",
+ amountOffScreen > mListView.getMaxScrollAmount());
+ }
+
+ @MediumTest
+ public void testScrollDownAcrossItem() {
+ final View view = mListView.getSelectedView();
+ assertTrue(view.isSelected());
+
+ assertEquals(mListView.getListPaddingTop(),
+ view.getTop());
+
+ assertTrue("view must be taller than screen for this test to be worth anything",
+ view.getBottom() > mListView.getBottom());
+
+ // scroll down until next view is peeking ahead
+ int numScrollsUntilNextViewVisible = getNumDownPressesToScrollDownAcrossSelected();
+
+ for (int i = 0; i < numScrollsUntilNextViewVisible; i++) {
+ assertEquals("after " + i + " down scrolls across tall item",
+ mListView.getListPaddingTop() - mListView.getMaxScrollAmount() * i,
+ view.getTop());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ // at this point, next view should be on screen peeking ahead, but we haven't given
+ // it selection yet
+ assertEquals("child count", 2, mListView.getChildCount());
+ assertEquals("selected position", 0, mListView.getSelectedItemPosition());
+ assertTrue("same view should be selected", view.isSelected());
+ final View peekingView = mListView.getChildAt(1);
+ assertEquals(view.getBottom(), peekingView.getTop());
+ }
+
+ @MediumTest
+ public void testScrollDownToNextItem() {
+ final int numPresses = getNumDownPressesToScrollDownAcrossSelected();
+ assertEquals(1, mListView.getChildCount());
+
+ for (int i = 0; i < numPresses; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ // remember top of peeking child
+ final int topOfPeekingNext = mListView.getChildAt(1).getTop();
+
+ // next view is peeking, now press one more time
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ // old view should not have selection
+ assertFalse(mListView.getChildAt(0).isSelected());
+
+ // next view should now have selection, and be scrolled another a third of the list
+ // height
+ assertEquals(2, mListView.getChildCount());
+ final View next = mListView.getChildAt(1);
+ assertTrue("has selection", next.isSelected());
+ assertEquals(topOfPeekingNext - (mListView.getMaxScrollAmount()), next.getTop());
+ }
+
+ @MediumTest
+ public void testScrollFirstItemOffScreen() {
+ int numDownsToGetFirstItemOffScreen =
+ (mListView.getSelectedView().getHeight() / mListView.getMaxScrollAmount()) + 1;
+
+ for (int i = 0; i < numDownsToGetFirstItemOffScreen; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals("should be at next item",
+ 1, mListView.getSelectedItemPosition());
+
+ final int listTop = mListView.getTop() + mListView.getListPaddingTop();
+ assertTrue("top of selected view should be above top of list",
+ mListView.getSelectedView().getTop() < listTop);
+
+ assertEquals("off screen item shouldn't be a child of list view",
+ 1, mListView.getChildCount());
+ }
+
+ @LargeTest
+ public void testScrollDownToLastItem() {
+ final int numItems = mListView.getAdapter().getCount();
+
+ int maxDowns = 20;
+ while (mListView.getSelectedItemPosition() < (numItems - 1)) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ if (--maxDowns <= 0) {
+ fail("couldn't get to last item within 20 down arrows");
+ }
+ }
+ getInstrumentation().waitForIdleSync();
+
+ // press down enough times to get to bottom of last item
+ final int numDownsLeft = getNumDownPressesToScrollDownAcrossSelected();
+ assertTrue(numDownsLeft > 0);
+ for (int i = 0; i < numDownsLeft; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ // one more time to get across last item
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ assertEquals(numItems - 1, mListView.getSelectedItemPosition());
+ final int realBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ assertEquals(realBottom, mListView.getSelectedView().getBottom());
+
+ assertEquals("views scrolled off screen should be removed from view group",
+ 1, mListView.getChildCount());
+ }
+
+ @MediumTest
+ public void testScrollUpAcrossFirstItem() {
+ final int listTop = mListView.getListPaddingTop();
+ assertEquals(listTop, mListView.getSelectedView().getTop());
+ final int numPresses = getNumDownPressesToScrollDownAcrossSelected();
+ for (int i = 0; i < numPresses; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals(2, mListView.getChildCount());
+ for (int i = 0; i < numPresses; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals(1, mListView.getChildCount());
+ }
+ assertEquals(listTop, mListView.getSelectedView().getTop());
+ }
+
+ /**
+ * Assuming the selected view is overlapping the bottom edge, how many times
+ * do I have to press down to get beyond it so that either:
+ * a) the next view is peeking in
+ * b) the selected view is the last item in the list, and we are scrolled to the bottom
+ * @return
+ */
+ private int getNumDownPressesToScrollDownAcrossSelected() {
+ View selected = mListView.getSelectedView();
+ int realBottom = mListView.getBottom() - mListView.getListPaddingBottom();
+ assertTrue("view should be overlapping bottom",
+ selected.getBottom() > realBottom);
+ assertTrue("view should be overlapping bottom",
+ selected.getTop() < realBottom);
+
+ int pixelsOffScreen = selected.getBottom() - realBottom;
+ return (pixelsOffScreen / mListView.getMaxScrollAmount()) + 1;
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java
new file mode 100644
index 0000000..a5d4906
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.listview.ListOfShortShortTallShortShort;
+import android.util.ListUtil;
+
+public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationTestCase<ListOfShortShortTallShortShort> {
+ private ListView mListView;
+ private ListUtil mListUtil;
+
+ public ListOfShortShortTallShortShortTest() {
+ super("com.android.frameworks.coretests", ListOfShortShortTallShortShort.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ mListUtil = new ListUtil(mListView, getInstrumentation());
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals("list item count", 5, mListView.getCount());
+ assertEquals("list visible child count", 3, mListView.getChildCount());
+ int firstTwoHeight = mListView.getChildAt(0).getHeight() + mListView.getChildAt(1).getHeight();
+ assertTrue("first two items should fit within fading edge",
+ firstTwoHeight <= mListView.getVerticalFadingEdgeLength());
+ assertTrue("first two items should fit within list max scroll",
+ firstTwoHeight <= mListView.getMaxScrollAmount());
+ }
+
+ @MediumTest
+ public void testFadeTopTwoItemsOut() {
+ // put 2nd item selected
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ // one more to get two items scrolled off
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals("selected item position", 2, mListView.getSelectedItemPosition());
+ assertTrue("selected item top should be above list top",
+ mListView.getSelectedView().getTop() < mListUtil.getListTop());
+ assertTrue("selected item bottom should be below list bottom",
+ mListView.getSelectedView().getBottom() > mListUtil.getListBottom());
+ assertEquals("should only be 1 child of list (2 should have been scrolled off and removed",
+ 1, mListView.getChildCount());
+ }
+
+ @LargeTest
+ public void testFadeInTwoBottomItems() {
+ // put 2nd item selected
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ // one more to get two items scrolled off
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("number of list children", 1, mListView.getChildCount());
+
+ // last down brings bottom two items into view
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("should have scrolled two extra views onto screen",
+ 3, mListView.getChildCount());
+ assertEquals("new view position", 3, mListView.getChildAt(1).getId());
+ assertEquals("new view position", 4, mListView.getChildAt(2).getId());
+
+ assertTrue("bottom most view shouldn't be above list bottom",
+ mListView.getChildAt(2).getBottom() >= mListUtil.getListBottom());
+ }
+
+ @LargeTest
+ public void testFadeOutBottomTwoItems() throws Exception {
+ mListUtil.arrowScrollToSelectedPosition(4);
+
+ // go up to tall item
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ // one more time to scroll off bottom two items
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+
+ assertEquals("selected item position", 2, mListView.getSelectedItemPosition());
+ assertTrue("selected item top should be at or above list top",
+ mListView.getSelectedView().getTop() <= mListUtil.getListTop());
+ assertTrue("selected item bottom should be below list bottom",
+ mListView.getSelectedView().getBottom() > mListUtil.getListBottom());
+ assertEquals("should only be 1 child of list (2 should have been scrolled off and removed",
+ 1, mListView.getChildCount());
+ }
+
+ @LargeTest
+ public void testFadeInTopTwoItems() throws Exception {
+ mListUtil.arrowScrollToSelectedPosition(4);
+
+ // put 2nd item selected
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ // one more to get two items scrolled off
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("number of list children", 1, mListView.getChildCount());
+
+ // last down brings top two items into view
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("should have scrolled two extra views onto screen",
+ 3, mListView.getChildCount());
+ assertEquals("new view position", 0, mListView.getChildAt(0).getId());
+ assertEquals("new view position", 1, mListView.getChildAt(1).getId());
+
+ assertTrue("top most view shouldn't be above list top",
+ mListView.getChildAt(0).getTop() <= mListUtil.getListTop());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortTallShortTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortTallShortTest.java
new file mode 100644
index 0000000..c958591
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortTallShortTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.widget.listview.ListOfShortTallShort;
+
+public class ListOfShortTallShortTest extends ActivityInstrumentationTestCase<ListOfShortTallShort> {
+ private ListView mListView;
+
+ public ListOfShortTallShortTest() {
+ super("com.android.frameworks.coretests", ListOfShortTallShort.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("second item should be taller than screen",
+ mListView.getChildAt(1).getHeight() > mListView.getHeight());
+ }
+
+ @MediumTest
+ public void testGoDownFromShortToTall() {
+ int topBeforeMove = mListView.getChildAt(1).getTop();
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals("selection should have moved to tall item below",
+ 1, mListView.getSelectedItemPosition());
+ assertEquals("should not have scrolled; top should be the same.",
+ topBeforeMove,
+ mListView.getSelectedView().getTop());
+ }
+
+ @MediumTest
+ public void testGoUpFromShortToTall() {
+ int maxMoves = 8;
+ while (mListView.getSelectedItemPosition() != 2 && maxMoves > 0) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("couldn't get to 3rd item",
+ 2,
+ mListView.getSelectedItemPosition());
+
+ assertEquals("should only be two items on screen",
+ 2, mListView.getChildCount());
+ assertEquals("selected item should be last item on screen",
+ mListView.getChildAt(1), mListView.getSelectedView());
+
+ final int bottomBeforeMove = mListView.getChildAt(0).getBottom();
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("should have moved selection to tall item above",
+ 1, mListView.getSelectedItemPosition());
+ assertEquals("should not have scrolled, top should be the same",
+ bottomBeforeMove,
+ mListView.getChildAt(0).getBottom());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
new file mode 100644
index 0000000..17c1e03
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.listview.ListOfThinItems;
+
+public class ListOfThinItemsTest extends ActivityInstrumentationTestCase<ListOfThinItems> {
+ private ListView mListView;
+
+ public ListOfThinItemsTest() {
+ super("com.android.frameworks.coretests", ListOfThinItems.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception{
+ super.setUp();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertTrue("need item height less than fading edge length",
+ mListView.getSelectedView().getHeight() < mListView.getVerticalFadingEdgeLength());
+ assertTrue("need items off screen",
+ mListView.getChildCount() < mListView.getAdapter().getCount());
+ }
+
+ @LargeTest
+ public void testScrollToBottom() {
+ final int numItems = mListView.getAdapter().getCount();
+ final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ for (int i = 0; i < numItems; i++) {
+ assertEquals("wrong selection at position " + i,
+ i, mListView.getSelectedItemPosition());
+ final int bottomFadingEdge = listBottom - mListView.getVerticalFadingEdgeLength();
+ final View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+ final int lastVisiblePosition = lastChild.getId();
+
+
+ int bottomThreshold = (lastVisiblePosition < mListView.getAdapter().getCount() - 1) ?
+ bottomFadingEdge : listBottom;
+
+ String prefix = "after " + i + " down presses, ";
+
+ assertTrue(prefix + "selected item is below bottom threshold (fading edge or bottom as " +
+ "appropriate)",
+ mListView.getSelectedView().getBottom() <= bottomThreshold);
+ assertTrue(prefix + "first item in list must be at very top or just above",
+ mListView.getChildAt(0).getTop() <= 0);
+ assertTrue(prefix + "last item in list should be at very bottom or just below",
+ lastChild.getBottom() >= listBottom);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ }
+
+ @LargeTest
+ public void testScrollToTop() {
+ final int numItems = mListView.getAdapter().getCount();
+
+ for (int i = 0; i < numItems - 1; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("should have moved to last position",
+ numItems - 1, mListView.getSelectedItemPosition());
+
+ int listTop = mListView.getListPaddingTop();
+ final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+
+ for (int i = 0; i < numItems; i++) {
+ int expectedPostion = numItems - (i + 1);
+ assertEquals("wrong selection at position " + expectedPostion,
+ expectedPostion, mListView.getSelectedItemPosition());
+ final int topFadingEdge = listTop + mListView.getVerticalFadingEdgeLength();
+ final View firstChild = mListView.getChildAt(0);
+ final View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+ final int firstVisiblePosition = firstChild.getId();
+
+
+ int topThreshold = (firstVisiblePosition > 0) ?
+ topFadingEdge : listTop;
+
+ String prefix = "after " + i + " up presses, ";
+
+ assertTrue(prefix + "selected item is above top threshold (fading edge or top as " +
+ "appropriate)",
+ mListView.getSelectedView().getTop() >= topThreshold);
+ assertTrue(prefix + "first item in list must be at very top or just above",
+ firstChild.getTop() <= 0);
+ assertTrue(prefix + "last item in list should be at very bottom or just below",
+ lastChild.getBottom() >= listBottom);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
new file mode 100644
index 0000000..400fd7d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.listview.arrowscroll;
+
+import android.widget.listview.ListWithFirstScreenUnSelectable;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.AdapterView;
+
+public class ListWithFirstScreenUnSelectableTest
+ extends ActivityInstrumentationTestCase2<ListWithFirstScreenUnSelectable> {
+ private ListView mListView;
+
+ public ListWithFirstScreenUnSelectableTest() {
+ super("com.android.frameworks.coretests", ListWithFirstScreenUnSelectable.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ setActivityInitialTouchMode(true);
+
+ mListView = getActivity().getListView();
+ }
+
+ public void testPreconditions() {
+ assertTrue(mListView.isInTouchMode());
+ assertEquals(1, mListView.getChildCount());
+ assertFalse(mListView.getAdapter().isEnabled(0));
+ assertEquals(AdapterView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ }
+
+ public void testRessurectSelection() {
+ sendKeys(KeyEvent.KEYCODE_SPACE);
+ assertEquals(AdapterView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ }
+
+ public void testScrollUpDoesNothing() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals(AdapterView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ assertEquals(1, mListView.getChildCount());
+ assertEquals(0, mListView.getFirstVisiblePosition());
+ }
+
+ public void testScrollDownPansNextItemOn() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(2, mListView.getChildCount());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
new file mode 100644
index 0000000..957be01
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.widget.listview.ListWithNoFadingEdge;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+
+public class ListWithNoFadingEdgeTest extends ActivityInstrumentationTestCase<ListWithNoFadingEdge> {
+
+ private ListView mListView;
+
+ public ListWithNoFadingEdgeTest() {
+ super("com.android.frameworks.coretests", ListWithNoFadingEdge.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertEquals("listview vertical fading edge", 0, mListView.getVerticalFadingEdgeLength());
+ assertTrue("expecting that not all views fit on screen",
+ mListView.getChildCount() < mListView.getCount());
+ }
+
+ @MediumTest
+ public void testScrollDownToBottom() {
+ final int numItems = mListView.getCount();
+
+ for (int i = 0; i < numItems; i++) {
+ assertEquals("selected position", i, mListView.getSelectedItemPosition());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("selected position", numItems - 1, mListView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testScrollFromBottomToTop() {
+ final int numItems = mListView.getCount();
+
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setSelection(numItems - 1);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ for (int i = numItems - 1; i >=0; i--) {
+ assertEquals(i, mListView.getSelectedItemPosition());
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+
+ assertEquals("selected position", 0, mListView.getSelectedItemPosition());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOffScreenNextSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOffScreenNextSelectableTest.java
new file mode 100644
index 0000000..610b890
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOffScreenNextSelectableTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.listview.ListWithOffScreenNextSelectable;
+
+public class ListWithOffScreenNextSelectableTest
+ extends ActivityInstrumentationTestCase<ListWithOffScreenNextSelectable> {
+ private ListView mListView;
+
+ public ListWithOffScreenNextSelectableTest() {
+ super("com.android.frameworks.coretests", ListWithOffScreenNextSelectable.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mListView);
+ assertEquals(5, mListView.getAdapter().getCount());
+ assertFalse(mListView.getAdapter().areAllItemsEnabled());
+ assertFalse(mListView.getAdapter().isEnabled(1));
+ assertFalse(mListView.getAdapter().isEnabled(2));
+ assertFalse(mListView.getAdapter().isEnabled(3));
+ assertEquals("only 4 children should be on screen (so that next selectable is off " +
+ "screen) for this test to be meaningful.",
+ 4, mListView.getChildCount());
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ // when the next items on screen are not selectable, we pan until the next selectable item
+ // is (partially visible), then we jump to it
+ @MediumTest
+ public void testGoDownToOffScreenSelectable() {
+
+ final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+
+ final View lastVisibleView = mListView.getChildAt(mListView.getChildCount() - 1);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("expecting view to be panned to just above fading edge",
+ listBottom - mListView.getVerticalFadingEdgeLength(), lastVisibleView.getBottom());
+ assertEquals("selection should not have moved yet",
+ 0, mListView.getSelectedItemPosition());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("selection should have moved",
+ 4, mListView.getSelectedItemPosition());
+ assertEquals("wrong view created when scrolling",
+ getActivity().getValueAtPosition(4), ((TextView) mListView.getSelectedView()).getText());
+ assertEquals(listBottom, mListView.getSelectedView().getBottom());
+ }
+
+ @MediumTest
+ public void testGoUpToOffScreenSelectable() {
+ final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
+ final int listTop = mListView.getListPaddingTop();
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals(4, mListView.getSelectedItemPosition());
+ assertEquals(listBottom, mListView.getSelectedView().getBottom());
+
+ // now we have the reverse situation: the next selectable position upward is off screen
+ final View firstVisibleView = mListView.getChildAt(0);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("should have panned top view just below vertical fading edge",
+ listTop + mListView.getVerticalFadingEdgeLength(), firstVisibleView.getTop());
+ assertEquals("selection should not have moved yet",
+ 4, mListView.getSelectedItemPosition());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("selection should have moved",
+ 0, mListView.getSelectedItemPosition());
+ assertEquals(getActivity().getValueAtPosition(0),((TextView) mListView.getSelectedView()).getText());
+ assertEquals(listTop, mListView.getSelectedView().getTop());
+
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOnItemSelectedActionTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOnItemSelectedActionTest.java
new file mode 100644
index 0000000..feea9b2
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithOnItemSelectedActionTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.view.KeyEvent;
+import android.widget.listview.ListWithOnItemSelectedAction;
+
+public class ListWithOnItemSelectedActionTest extends ActivityInstrumentationTestCase<ListWithOnItemSelectedAction> {
+ private ListView mListView;
+
+ public ListWithOnItemSelectedActionTest() {
+ super("com.android.frameworks.coretests", ListWithOnItemSelectedAction.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ }
+
+ private String getValueOfSelectedTextView() {
+ return ((TextView) mListView.getSelectedView()).getText().toString();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals(0, mListView.getSelectedItemPosition());
+ assertEquals("header text field should be echoing contents of selected item",
+ getValueOfSelectedTextView(),
+ getActivity().getHeaderValue());
+ }
+
+ @MediumTest
+ public void testHeaderEchoesSelectionAfterMove() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals(1, mListView.getSelectedItemPosition());
+ assertEquals("header text field should be echoing contents of selected item",
+ getValueOfSelectedTextView(),
+ getActivity().getHeaderValue());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java
new file mode 100644
index 0000000..8071650
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.widget.listview.ListWithScreenOfNoSelectables;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.ListView;
+
+public class ListWithScreenOfNoSelectablesTest extends ActivityInstrumentationTestCase<ListWithScreenOfNoSelectables> {
+
+ private ListView mListView;
+
+ public ListWithScreenOfNoSelectablesTest() {
+ super("com.android.frameworks.coretests", ListWithScreenOfNoSelectables.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("expecting first position to be selectable",
+ mListView.getAdapter().isEnabled(0));
+ final int numItems = mListView.getCount();
+ for (int i = 1; i < numItems; i++) {
+ assertFalse("expecting item to be unselectable (index " + i +")",
+ mListView.getAdapter().isEnabled(i));
+ }
+ assertTrue("expecting that not all views fit on screen",
+ mListView.getChildCount() < mListView.getCount());
+ }
+
+
+ @MediumTest
+ public void testGoFromSelectedViewExistsToNoSelectedViewExists() {
+
+ // go down untile first (and only selectable) item is off screen
+ View first = mListView.getChildAt(0);
+ while (first.getParent() != null) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ // nothing should be selected
+ assertEquals("selected position", ListView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ assertNull("selected view", mListView.getSelectedView());
+ }
+
+ @LargeTest
+ public void testPanDownAcrossUnselectableChildrenToBottom() {
+ final int lastPosition = mListView.getCount() - 1;
+ final int maxDowns = 20;
+ for(int count = 0; count < maxDowns && mListView.getLastVisiblePosition() <= lastPosition; count++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ assertEquals("last visible position not the last position in the list even "
+ + "after " + maxDowns + " downs", lastPosition, mListView.getLastVisiblePosition());
+ }
+
+ @MediumTest
+ public void testGoFromNoSelectionToSelectionExists() {
+ // go down untile first (and only selectable) item is off screen
+ View first = mListView.getChildAt(0);
+ while (first.getParent() != null) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ // nothing should be selected
+ assertEquals("selected position", ListView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ assertNull("selected view", mListView.getSelectedView());
+
+ // go up once to bring the selectable back on screen
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("first visible position", 0, mListView.getFirstVisiblePosition());
+ assertEquals("selected position", ListView.INVALID_POSITION, mListView.getSelectedItemPosition());
+
+
+ // up once more should give it selection
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("selected position", 0, mListView.getSelectedItemPosition());
+
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithSeparatorsTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithSeparatorsTest.java
new file mode 100644
index 0000000..42058f0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithSeparatorsTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.arrowscroll;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ListView;
+import android.view.KeyEvent;
+import android.widget.listview.ListWithSeparators;
+
+public class ListWithSeparatorsTest extends ActivityInstrumentationTestCase<ListWithSeparators> {
+ private ListWithSeparators mActivity;
+ private ListView mListView;
+
+ public ListWithSeparatorsTest() {
+ super("com.android.frameworks.coretests", ListWithSeparators.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ assertFalse(mListView.getAdapter().areAllItemsEnabled());
+ assertFalse(mListView.getAdapter().isEnabled(0));
+ assertFalse(mListView.getAdapter().isEnabled(2));
+ assertEquals(1, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testGoingUpDoesnNotHitUnselectableItem() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals("selected position should remain the same",
+ 1, mListView.getSelectedItemPosition());
+
+ assertEquals("seperator should be scrolled flush with top",
+ mListView.getListPaddingTop(), mListView.getChildAt(0).getTop());
+ }
+
+ @MediumTest
+ public void testGoingDownSkipsOverUnselectable() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("should have skipped to next selectable ",
+ 3,
+ mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testGoingUpSkippingOverUnselectable() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertEquals(1, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/focus/AdjacentListsWithAdjacentISVsInsideTest.java b/core/tests/coretests/src/android/widget/listview/focus/AdjacentListsWithAdjacentISVsInsideTest.java
new file mode 100644
index 0000000..461f83d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/focus/AdjacentListsWithAdjacentISVsInsideTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.focus;
+
+import android.widget.listview.AdjacentListsWithAdjacentISVsInside;
+import android.util.InternalSelectionView;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ListView;
+
+public class AdjacentListsWithAdjacentISVsInsideTest extends ActivityInstrumentationTestCase<AdjacentListsWithAdjacentISVsInside> {
+
+ private ListView mLeftListView;
+ private InternalSelectionView mLeftIsv;
+ private InternalSelectionView mLeftMiddleIsv;
+ private ListView mRightListView;
+ private InternalSelectionView mRightMiddleIsv;
+ private InternalSelectionView mRightIsv;
+
+ public AdjacentListsWithAdjacentISVsInsideTest() {
+ super("com.android.frameworks.coretests", AdjacentListsWithAdjacentISVsInside.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final AdjacentListsWithAdjacentISVsInside a = getActivity();
+ mLeftListView = a.getLeftListView();
+ mLeftIsv = a.getLeftIsv();
+ mLeftMiddleIsv = a.getLeftMiddleIsv();
+ mRightListView = a.getRightListView();
+ mRightMiddleIsv = a.getRightMiddleIsv();
+ mRightIsv = a.getRightIsv();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue(mLeftListView.hasFocus());
+ assertTrue(mLeftIsv.isFocused());
+ assertEquals(0, mLeftIsv.getSelectedRow());
+ }
+
+ /**
+ * rockinist test name to date!
+ */
+ @MediumTest
+ public void testFocusedRectAndFocusHintWorkWithinListItemHorizontal() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals(1, mLeftIsv.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertTrue(mLeftListView.hasFocus());
+ assertTrue(mLeftMiddleIsv.isFocused());
+ assertEquals("mLeftMiddleIsv.getSelectedRow()", 1, mLeftMiddleIsv.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ assertTrue(mLeftIsv.isFocused());
+ assertEquals("mLeftIsv.getSelectedRow()", 1, mLeftIsv.getSelectedRow());
+ }
+
+ @MediumTest
+ public void testFocusTransfersOutsideOfListWhenNoCandidateInsideHorizontal() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
+
+ assertTrue(mLeftListView.hasFocus());
+ assertTrue(mLeftMiddleIsv.isFocused());
+ assertEquals(2, mLeftMiddleIsv.getSelectedRow());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertTrue("mRightListView.hasFocus()", mRightListView.hasFocus());
+ assertTrue("mRightMiddleIsv.isFocused()", mRightMiddleIsv.isFocused());
+ assertEquals("mRightMiddleIsv.getSelectedRow()", 2, mRightMiddleIsv.getSelectedRow());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListButtonsDiagonalAcrossItemsTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListButtonsDiagonalAcrossItemsTest.java
new file mode 100644
index 0000000..5540d65
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListButtonsDiagonalAcrossItemsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.focus;
+
+import android.widget.listview.ListButtonsDiagonalAcrossItems;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ListView;
+
+/**
+ * Test that ListView will override default behavior of focus searching to
+ * make sure going right and left doesn't change selection
+ */
+public class ListButtonsDiagonalAcrossItemsTest extends ActivityInstrumentationTestCase<ListButtonsDiagonalAcrossItems> {
+
+ private Button mLeftButton;
+ private Button mCenterButton;
+ private Button mRightButton;
+ private ListView mListView;
+
+ public ListButtonsDiagonalAcrossItemsTest() {
+ super("com.android.frameworks.coretests", ListButtonsDiagonalAcrossItems.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mLeftButton = getActivity().getLeftButton();
+ mCenterButton = getActivity().getCenterButton();
+ mRightButton = getActivity().getRightButton();
+
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ final ListView lv = mListView;
+ assertEquals("num children", 3, lv.getChildCount());
+
+ assertEquals("selected position", 0, lv.getSelectedItemPosition());
+ assertTrue("left button focused", mLeftButton.isFocused());
+
+ assertTrue("left left of center",
+ mLeftButton.getRight()
+ < mCenterButton.getLeft());
+
+ assertTrue("center left of right",
+ mCenterButton.getRight()
+ < mRightButton.getLeft());
+
+ assertEquals("focus search right from left button should be center button",
+ mCenterButton,
+ FocusFinder.getInstance().findNextFocus(mListView, mLeftButton, View.FOCUS_RIGHT));
+ assertEquals("focus search right from center button should be right button",
+ mRightButton,
+ FocusFinder.getInstance().findNextFocus(mListView, mCenterButton, View.FOCUS_RIGHT));
+ assertEquals("focus search left from centr button should be left button",
+ mLeftButton,
+ FocusFinder.getInstance().findNextFocus(mListView, mCenterButton, View.FOCUS_LEFT));
+ }
+
+ @MediumTest
+ public void testGoingRightDoesNotChangeSelection() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+
+ assertEquals("selected position shouldn't have changed",
+ 0,
+ mListView.getSelectedItemPosition());
+ assertTrue("left should still be focused", mLeftButton.isFocused());
+ }
+
+ @MediumTest
+ public void testGoingLeftDoesNotChangeSelection() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("list view postion", 1, mListView.getSelectedItemPosition());
+ assertTrue("mCenterButton.isFocused()", mCenterButton.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ assertEquals("selected position shouldn't have changed",
+ 1,
+ mListView.getSelectedItemPosition());
+ assertTrue("center should still be focused", mCenterButton.isFocused());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
new file mode 100644
index 0000000..8f971bb
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.focus;
+
+import android.widget.listview.ListHorizontalFocusWithinItemWins;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ListView;
+
+public class ListHorizontalFocusWithinItemWinsTest extends ActivityInstrumentationTestCase<ListHorizontalFocusWithinItemWins> {
+
+ private ListView mListView;
+ private Button mTopLeftButton;
+ private Button mTopRightButton;
+ private Button mBottomMiddleButton;
+
+ public ListHorizontalFocusWithinItemWinsTest() {
+ super("com.android.frameworks.coretests", ListHorizontalFocusWithinItemWins.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mListView = getActivity().getListView();
+ mTopLeftButton = getActivity().getTopLeftButton();
+ mTopRightButton = getActivity().getTopRightButton();
+ mBottomMiddleButton = getActivity().getBottomMiddleButton();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertEquals("list position", 0, mListView.getSelectedItemPosition());
+ assertTrue("mTopLeftButton.isFocused()", mTopLeftButton.isFocused());
+ assertEquals("global focus search to right from top left is bottom middle",
+ mBottomMiddleButton,
+ FocusFinder.getInstance().findNextFocus(mListView, mTopLeftButton, View.FOCUS_RIGHT));
+ assertEquals("global focus search to left from top right is bottom middle",
+ mBottomMiddleButton,
+ FocusFinder.getInstance().findNextFocus(mListView, mTopRightButton, View.FOCUS_LEFT));
+ }
+
+ @MediumTest
+ public void testOptionWithinItemTrumpsGlobal() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+
+ assertEquals("list position", 0, mListView.getSelectedItemPosition());
+ assertTrue("mTopRightButton.isFocused()", mTopRightButton.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ assertEquals("list position", 0, mListView.getSelectedItemPosition());
+ assertTrue("mTopLeftButton.isFocused()", mTopLeftButton.isFocused());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListWithEditTextHeaderTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListWithEditTextHeaderTest.java
new file mode 100644
index 0000000..b9051e9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListWithEditTextHeaderTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.focus;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.FlakyTest;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.AbsListView;
+import android.widget.ListView;
+import android.widget.listview.ListWithEditTextHeader;
+
+public class ListWithEditTextHeaderTest extends ActivityInstrumentationTestCase2<ListWithEditTextHeader> {
+ private ListView mListView;
+
+ public ListWithEditTextHeaderTest() {
+ super(ListWithEditTextHeader.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("listview.getItemsCanFocus()", mListView.getItemsCanFocus());
+ assertFalse("out of touch-mode", mListView.isInTouchMode());
+ assertEquals("header view count", 1, mListView.getHeaderViewsCount());
+ assertTrue("header does not have focus", mListView.getChildAt(0).isFocused());
+ }
+
+ @FlakyTest(tolerance=2)
+ @LargeTest
+ public void testClickingHeaderKeepsFocus() {
+ TouchUtils.clickView(this, mListView.getChildAt(0));
+ assertTrue("header does not have focus", mListView.getChildAt(0).isFocused());
+ assertEquals("something is selected", AbsListView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testClickingHeaderWhenOtherItemHasFocusGivesHeaderFocus() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertEquals("selected position", 1, mListView.getSelectedItemPosition());
+ TouchUtils.clickView(this, mListView.getChildAt(0));
+ assertTrue("header does not have focus", mListView.getChildAt(0).isFocused());
+ assertEquals("something is selected", AbsListView.INVALID_POSITION, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListGetSelectedViewTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListGetSelectedViewTest.java
new file mode 100644
index 0000000..28f899e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListGetSelectedViewTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.widget.ListView;
+import android.view.View;
+
+import android.widget.listview.ListGetSelectedView;
+
+/**
+ * This test is made to check that getSelectedView() will return
+ * null in touch mode.
+ */
+public class ListGetSelectedViewTest extends ActivityInstrumentationTestCase<ListGetSelectedView> {
+ private ListGetSelectedView mActivity;
+ private ListView mListView;
+
+ public ListGetSelectedViewTest() {
+ super("com.android.frameworks.coretests", ListGetSelectedView.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testGetSelectedView() {
+ View last = mListView.getChildAt(1);
+ TouchUtils.clickView(this, last);
+
+ assertNull(mListView.getSelectedItem());
+ assertNull(mListView.getSelectedView());
+ assertEquals(-1, mListView.getSelectedItemPosition());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListOfTouchablesTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListOfTouchablesTest.java
new file mode 100644
index 0000000..ffa9a5e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListOfTouchablesTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ListView;
+
+import android.widget.listview.ListOfTouchables;
+import android.test.TouchUtils;
+
+/**
+ * Touch tests for a list where all of the items fit on the screen.
+ */
+public class ListOfTouchablesTest extends ActivityInstrumentationTestCase<ListOfTouchables> {
+ private ListOfTouchables mActivity;
+ private ListView mListView;
+
+ public ListOfTouchablesTest() {
+ super("com.android.frameworks.coretests", ListOfTouchables.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testShortScroll() {
+ View firstChild = mListView.getChildAt(0);
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int firstTop = firstChild.getTop();
+
+ TouchUtils.dragViewBy(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ 0, -(ViewConfiguration.getTouchSlop() + 1 + 10));
+
+ View newFirstChild = mListView.getChildAt(0);
+
+ assertEquals("View scrolled too early", firstTop, newFirstChild.getTop() + 10);
+ assertEquals("Wrong view in first position", 0, newFirstChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testLongScroll() {
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ int distance = TouchUtils.dragViewToY(this, lastChild,
+ Gravity.TOP | Gravity.LEFT, mListView.getTop());
+
+ assertEquals("View scrolled to wrong position",
+ lastTop - (distance - ViewConfiguration.getTouchSlop() - 1), lastChild.getTop());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListSetSelectionTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListSetSelectionTest.java
new file mode 100644
index 0000000..b7733d1
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListSetSelectionTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.View;
+import android.widget.ListView;
+
+import android.widget.listview.ListSimple;
+
+/**
+ * Tests setting the selection in touch mode
+ */
+public class ListSetSelectionTest extends ActivityInstrumentationTestCase<ListSimple> {
+ private ListSimple mActivity;
+ private ListView mListView;
+
+ public ListSetSelectionTest() {
+ super("com.android.frameworks.coretests", ListSimple.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+ }
+
+ @LargeTest
+ public void testSetSelection() {
+ TouchUtils.dragQuarterScreenDown(this);
+ TouchUtils.dragQuarterScreenUp(this);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ final int targetPosition = mListView.getAdapter().getCount() / 2;
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setSelection(targetPosition);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ boolean found = false;
+ int childCount = mListView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ View child = mListView.getChildAt(i);
+ if (child.getId() == targetPosition) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Selected item not visible in list", found);
+ }
+
+ @LargeTest
+ public void testSetSelectionFromTop() {
+ TouchUtils.dragQuarterScreenDown(this);
+ TouchUtils.dragQuarterScreenUp(this);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ final int targetPosition = mListView.getAdapter().getCount() / 2;
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setSelectionFromTop(targetPosition, 100);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ View target = null;
+ boolean found = false;
+ int childCount = mListView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ View child = mListView.getChildAt(i);
+ if (child.getId() == targetPosition) {
+ target = child;
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Selected item not visible in list", found);
+
+ if (target != null) {
+ assertEquals("Selection not at correct location", 100 + mListView.getPaddingTop(),
+ target.getTop());
+ }
+ }
+
+ @LargeTest
+ public void testSetSelection0() {
+ TouchUtils.dragQuarterScreenDown(this);
+ TouchUtils.dragQuarterScreenDown(this);
+ TouchUtils.dragQuarterScreenDown(this);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ mListView.setSelection(0);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+
+ boolean found = false;
+ int childCount = mListView.getChildCount();
+ for (int i=0; i<childCount; i++) {
+ View child = mListView.getChildAt(i);
+ if (child.getId() == 0 && i == 0) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue("Selected item not visible in list", found);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityManyTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityManyTest.java
new file mode 100644
index 0000000..7daf64e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityManyTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ListView;
+
+import android.widget.listview.ListBottomGravityMany;
+
+/**
+ * Touch tests for a list where all of the items do not fit on the screen, and the list
+ * stacks from the bottom.
+ */
+public class ListTouchBottomGravityManyTest extends ActivityInstrumentationTestCase<ListBottomGravityMany> {
+ private ListBottomGravityMany mActivity;
+ private ListView mListView;
+
+ public ListTouchBottomGravityManyTest() {
+ super("com.android.frameworks.coretests", ListBottomGravityMany.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // Last item should be selected
+ assertEquals(mListView.getAdapter().getCount() - 1, mListView.getSelectedItemPosition());
+ }
+
+ @LargeTest
+ public void testPullDown() {
+ int originalCount = mListView.getChildCount();
+
+ TouchUtils.scrollToTop(this, mListView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ View firstChild = mListView.getChildAt(0);
+
+ assertEquals("Item zero not the first child in the list", 0, firstChild.getId());
+
+ assertEquals("Item zero not at the top of the list", mListView.getListPaddingTop(),
+ firstChild.getTop());
+
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mListView.getChildCount(), originalCount + 1),
+ mListView.getChildCount() <= originalCount + 1);
+ }
+
+ @MediumTest
+ public void testPushUp() {
+ TouchUtils.scrollToBottom(this, mListView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ assertEquals("List is not scrolled to the bottom", mListView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ assertEquals("Last item is not touching the bottom edge",
+ mListView.getHeight() - mListView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+ @MediumTest
+ public void testNoScroll() {
+ View firstChild = mListView.getChildAt(0);
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ TouchUtils.dragViewBy(this, firstChild, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ 0, ViewConfiguration.getTouchSlop());
+
+ View newLastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ assertEquals("View scrolled too early", lastTop, newLastChild.getTop());
+ assertEquals("Wrong view in last position", mListView.getAdapter().getCount() - 1,
+ newLastChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testShortScroll() {
+ View firstChild = mListView.getChildAt(0);
+ if (firstChild.getTop() < this.mListView.getListPaddingTop()) {
+ firstChild = mListView.getChildAt(1);
+ }
+
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ TouchUtils.dragViewBy(this, firstChild, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL,
+ 0, ViewConfiguration.getTouchSlop() + 1 + 10);
+
+ View newLastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ assertEquals("View scrolled to wrong position", lastTop, newLastChild.getTop() - 10);
+ assertEquals("Wrong view in last position", mListView.getAdapter().getCount() - 1,
+ newLastChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testLongScroll() {
+ View firstChild = mListView.getChildAt(0);
+ if (firstChild.getTop() < mListView.getListPaddingTop()) {
+ firstChild = mListView.getChildAt(1);
+ }
+
+ int firstTop = firstChild.getTop();
+
+ int distance = TouchUtils.dragViewBy(this, firstChild,
+ Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0,
+ (int)(mActivity.getWindowManager().getDefaultDisplay().getHeight() * 0.75f));
+
+ assertEquals("View scrolled to wrong position", firstTop
+ + (distance - ViewConfiguration.getTouchSlop() - 1), firstChild.getTop());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityTest.java
new file mode 100644
index 0000000..4086cf0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListTouchBottomGravityTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.View;
+import android.widget.ListView;
+
+import android.widget.listview.ListBottomGravity;
+
+/**
+ * Touch tests for a list where all of the items fit on the screen, and the list
+ * stacks from the bottom.
+ */
+public class ListTouchBottomGravityTest extends ActivityInstrumentationTestCase<ListBottomGravity> {
+ private ListBottomGravity mActivity;
+ private ListView mListView;
+
+ public ListTouchBottomGravityTest() {
+ super("com.android.frameworks.coretests", ListBottomGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // First item should be selected
+ assertEquals(mListView.getAdapter().getCount() - 1, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testPullDown() {
+ View firstChild = mListView.getChildAt(0);
+
+ TouchUtils.dragViewToBottom(this, firstChild);
+
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ assertEquals("List is not scrolled to the bottom", mListView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ assertEquals("Last item is not touching the bottom edge",
+ mListView.getHeight() - mListView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+ @MediumTest
+ public void testPushUp() {
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ TouchUtils.dragViewToTop(this, lastChild);
+
+ lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ assertEquals("List is not scrolled to the bottom", mListView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ assertEquals("Last item is not touching the bottom edge",
+ mListView.getHeight() - mListView.getListPaddingBottom(), lastChild.getBottom());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListTouchManyTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListTouchManyTest.java
new file mode 100644
index 0000000..30d56ca
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListTouchManyTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.ListView;
+
+import android.widget.listview.ListTopGravityMany;
+
+/**
+ * Touch tests for a list where all of the items do not fit on the screen.
+ */
+public class ListTouchManyTest extends ActivityInstrumentationTestCase<ListTopGravityMany> {
+ private ListTopGravityMany mActivity;
+ private ListView mListView;
+
+ public ListTouchManyTest() {
+ super("com.android.frameworks.coretests", ListTopGravityMany.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // First item should be selected
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testPullDown() {
+ TouchUtils.scrollToTop(this, mListView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ View firstChild = mListView.getChildAt(0);
+
+ assertEquals("Item zero not the first child in the list", 0, firstChild.getId());
+
+ assertEquals("Item zero not at the top of the list", mListView.getListPaddingTop(),
+ firstChild.getTop());
+ }
+
+ @LargeTest
+ public void testPushUp() {
+ int originalCount = mListView.getChildCount();
+
+ TouchUtils.scrollToBottom(this, mListView);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ assertEquals("List is not scrolled to the bottom", mListView.getAdapter().getCount() - 1,
+ lastChild.getId());
+
+ assertEquals("Last item is not touching the bottom edge",
+ mListView.getHeight() - mListView.getListPaddingBottom(), lastChild.getBottom());
+
+ assertTrue(String.format("Too many children created: %d expected no more than %d",
+ mListView.getChildCount(), originalCount + 1),
+ mListView.getChildCount() <= originalCount + 1);
+ }
+
+ @LargeTest
+ public void testPress() {
+ int i;
+ int count = mListView.getChildCount();
+ mActivity.setClickedPosition(-1);
+ mActivity.setLongClickedPosition(-1);
+
+ for (i = 0; i < count; i++) {
+ View child = mListView.getChildAt(i);
+ if ((child.getTop() >= mListView.getListPaddingTop())
+ && (child.getBottom() <=
+ mListView.getHeight() - mListView.getListPaddingBottom())) {
+ TouchUtils.clickView(this, child);
+
+ assertEquals("Incorrect view position reported being clicked", i,
+ mActivity.getClickedPosition());
+ assertEquals("View falsely reported being long clicked", -1,
+ mActivity.getLongClickedPosition());
+ try {
+ Thread.sleep((long)(ViewConfiguration.getLongPressTimeout() * 1.25f));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ @LargeTest
+ public void testLongPress() {
+ int i;
+ int count = mListView.getChildCount();
+ mActivity.enableLongPress();
+ mActivity.setClickedPosition(-1);
+ mActivity.setLongClickedPosition(-1);
+
+ for (i = 0; i < count; i++) {
+ View child = mListView.getChildAt(i);
+ if ((child.getTop() >= mListView.getListPaddingTop())
+ && (child.getBottom() <=
+ mListView.getHeight() - mListView.getListPaddingBottom())) {
+ TouchUtils.longClickView(this, child);
+ assertEquals("Incorrect view position reported being long clicked", i,
+ mActivity.getLongClickedPosition());
+ assertEquals("View falsely reported being clicked", -1,
+ mActivity.getClickedPosition());
+ }
+ }
+ }
+
+ @MediumTest
+ public void testNoScroll() {
+ View firstChild = mListView.getChildAt(0);
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int firstTop = firstChild.getTop();
+
+ TouchUtils.dragViewBy(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ 0, -(ViewConfiguration.getTouchSlop()));
+
+ View newFirstChild = mListView.getChildAt(0);
+
+ assertEquals("View scrolled too early", firstTop, newFirstChild.getTop());
+ assertEquals("Wrong view in first position", 0, newFirstChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testShortScroll() {
+ View firstChild = mListView.getChildAt(0);
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int firstTop = firstChild.getTop();
+
+ TouchUtils.dragViewBy(this, lastChild, Gravity.TOP | Gravity.LEFT,
+ 0, -(ViewConfiguration.getTouchSlop() + 1 + 10));
+
+ View newFirstChild = mListView.getChildAt(0);
+
+ assertEquals("View scrolled too early", firstTop, newFirstChild.getTop() + 10);
+ assertEquals("Wrong view in first position", 0, newFirstChild.getId());
+ }
+
+ // TODO: needs to be adjusted to pass on non-HVGA displays
+ // @LargeTest
+ public void testLongScroll() {
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ int lastTop = lastChild.getTop();
+
+ int distance = TouchUtils.dragViewToY(this, lastChild,
+ Gravity.TOP | Gravity.LEFT, mListView.getTop());
+
+ assertEquals("View scrolled to wrong position",
+ lastTop - (distance - ViewConfiguration.getTouchSlop() - 1), lastChild.getTop());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/listview/touch/ListTouchTest.java b/core/tests/coretests/src/android/widget/listview/touch/ListTouchTest.java
new file mode 100644
index 0000000..5b064b3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/listview/touch/ListTouchTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.listview.touch;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.TouchUtils;
+import android.view.View;
+import android.widget.ListView;
+
+import android.widget.listview.ListTopGravity;
+
+/**
+ * Touch tests for a list where all of the items fit on the screen.
+ */
+public class ListTouchTest extends ActivityInstrumentationTestCase<ListTopGravity> {
+ private ListTopGravity mActivity;
+ private ListView mListView;
+
+ public ListTouchTest() {
+ super("com.android.frameworks.coretests", ListTopGravity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mListView = getActivity().getListView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mActivity);
+ assertNotNull(mListView);
+
+ // First item should be selected
+ assertEquals(0, mListView.getSelectedItemPosition());
+ }
+
+ @MediumTest
+ public void testPullDown() {
+ View firstChild = mListView.getChildAt(0);
+
+ TouchUtils.dragViewToBottom(this, firstChild);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ firstChild = mListView.getChildAt(0);
+
+ assertEquals("Item zero not the first child in the list", 0, firstChild.getId());
+
+ assertEquals("Item zero not at the top of the list", mListView.getListPaddingTop(),
+ firstChild.getTop());
+ }
+
+ @MediumTest
+ public void testPushUp() {
+ View lastChild = mListView.getChildAt(mListView.getChildCount() - 1);
+
+ TouchUtils.dragViewToTop(this, lastChild);
+
+ // Nothing should be selected
+ assertEquals("Selection still available after touch", -1,
+ mListView.getSelectedItemPosition());
+
+ View firstChild = mListView.getChildAt(0);
+
+ assertEquals("Item zero not the first child in the list", 0, firstChild.getId());
+
+ assertEquals("Item zero not at the top of the list", mListView.getListPaddingTop(),
+ firstChild.getTop());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionView.java b/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionView.java
new file mode 100644
index 0000000..a7f5c05
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionView.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.util.InternalSelectionView;
+import android.util.ScrollViewScenario;
+
+import android.widget.Button;
+
+/**
+ * A button above a tall internal selection view, wrapped in a scroll view.
+ */
+public class ButtonAboveTallInternalSelectionView extends ScrollViewScenario {
+
+ private final int mNumRowsInIsv = 5;
+
+
+ public Button getButtonAbove() {
+ return getContentChildAt(0);
+ }
+
+ public InternalSelectionView getIsv() {
+ return getContentChildAt(1);
+ }
+
+
+ protected void init(Params params) {
+ params.addButton("howdy", 0.1f)
+ .addInternalSelectionView(mNumRowsInIsv, 1.1f)
+ .addButton("below", 0.1f);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionViewTest.java b/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionViewTest.java
new file mode 100644
index 0000000..41123280
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ButtonAboveTallInternalSelectionViewTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.widget.scroll.ButtonAboveTallInternalSelectionView;
+import android.util.InternalSelectionView;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+
+public class ButtonAboveTallInternalSelectionViewTest extends
+ ActivityInstrumentationTestCase<ButtonAboveTallInternalSelectionView> {
+
+ public ButtonAboveTallInternalSelectionViewTest() {
+ super("com.android.frameworks.coretests", ButtonAboveTallInternalSelectionView.class);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("expecting the top button to have focus",
+ getActivity().getButtonAbove().isFocused());
+ assertEquals("scrollview scroll y",
+ 0,
+ getActivity().getScrollView().getScrollY());
+ assertTrue("internal selection view should be taller than screen",
+ getActivity().getIsv().getHeight() > getActivity().getScrollView().getHeight());
+
+ assertTrue("top of ISV should be on screen",
+ getActivity().getIsv().getTop() >
+ getActivity().getScrollView().getScrollY());
+
+ }
+
+ @MediumTest
+ public void testMovingFocusDownToItemTallerThanScreenStillOnScreen() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ final InternalSelectionView isv = getActivity().getIsv();
+ assertTrue("internal selection view should have taken focus",
+ isv.isFocused());
+ assertEquals("internal selection view selected row",
+ 0, isv.getSelectedRow());
+ assertTrue("top of ISV should still be on screen",
+ getActivity().getIsv().getTop() >
+ getActivity().getScrollView().getScrollY());
+ }
+
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ButtonsWithTallTextViewInBetween.java b/core/tests/coretests/src/android/widget/scroll/ButtonsWithTallTextViewInBetween.java
new file mode 100644
index 0000000..3d5f86d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ButtonsWithTallTextViewInBetween.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.util.ScrollViewScenario;
+
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * Two buttons sandwiching a tall text view (good for testing panning across
+ * before getting to next button).
+ */
+public class ButtonsWithTallTextViewInBetween extends ScrollViewScenario {
+
+ public Button getTopButton() {
+ return getContentChildAt(0);
+ }
+
+ public TextView getMiddleFiller() {
+ return getContentChildAt(1);
+ }
+
+ public Button getBottomButton() {
+ LinearLayout ll = getContentChildAt(2);
+ return (Button) ll.getChildAt(0);
+ }
+
+ protected void init(Params params) {
+
+ params.addButton("top button", 0.2f)
+ .addTextView("middle filler", 1.51f)
+ .addVerticalLLOfButtons("bottom", 1, 0.2f);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisible.java b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisible.java
new file mode 100644
index 0000000..9cc8544
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisible.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.graphics.Rect;
+import android.view.View;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+/**
+ * A screen with some scenarios that exercise {@link ScrollView}'s implementation
+ * of {@link android.view.ViewGroup#requestChildRectangleOnScreen}:
+ * <li>Scrolling to something off screen (from top and from bottom)
+ * <li>Scrolling to bring something that is larger than the screen on screen
+ * (from top and from bottom).
+ */
+public class RequestRectangleVisible extends Activity {
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.scroll_to_rectangle);
+
+ final Rect rect = new Rect();
+ final View childToMakeVisible = findViewById(R.id.childToMakeVisible);
+
+ final TextView topBlob = (TextView) findViewById(R.id.topBlob);
+ final TextView bottomBlob = (TextView) findViewById(R.id.bottomBlob);
+
+ // estimate to get blobs larger than screen
+ int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
+ int numLinesForScreen = screenHeight / 18;
+
+ for (int i = 0; i < numLinesForScreen; i++) {
+ topBlob.append(i + " another line in the blob\n");
+ bottomBlob.append(i + " another line in the blob\n");
+ }
+
+ findViewById(R.id.scrollToRectFromTop).setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ rect.set(0, 0, childToMakeVisible.getLeft(), childToMakeVisible.getHeight());
+ childToMakeVisible.requestRectangleOnScreen(rect, true);
+ }
+ });
+
+ findViewById(R.id.scrollToRectFromTop2).setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ rect.set(0, 0, topBlob.getWidth(), topBlob.getHeight());
+ topBlob.requestRectangleOnScreen(rect, true);
+ }
+ });
+
+ findViewById(R.id.scrollToRectFromBottom).setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ rect.set(0, 0, childToMakeVisible.getLeft(), childToMakeVisible.getHeight());
+ childToMakeVisible.requestRectangleOnScreen(rect, true);
+ }
+ });
+
+ findViewById(R.id.scrollToRectFromBottom2).setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ rect.set(0, 0, bottomBlob.getWidth(), bottomBlob.getHeight());
+ bottomBlob.requestRectangleOnScreen(rect, true);
+ }
+ });
+
+ }
+
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleTest.java b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleTest.java
new file mode 100644
index 0000000..95fb00b
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.widget.scroll.RequestRectangleVisible;
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.ViewAsserts;
+import android.widget.Button;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.view.View;
+import android.view.KeyEvent;
+
+/**
+ * {@link RequestRectangleVisible} is set up to exercise the cases of moving a
+ * rectangle that is either off screen or not entirely on the screen onto the screen.
+ */
+public class RequestRectangleVisibleTest extends ActivityInstrumentationTestCase<RequestRectangleVisible> {
+
+ private ScrollView mScrollView;
+
+ private Button mClickToScrollFromAbove;
+ private Button mClickToScrollToUpperBlob;
+ private TextView mTopBlob;
+
+ private View mChildToScrollTo;
+
+ private TextView mBottomBlob;
+ private Button mClickToScrollToBlobLowerBlob;
+ private Button mClickToScrollFromBelow;
+
+ public RequestRectangleVisibleTest() {
+ super("com.android.frameworks.coretests", RequestRectangleVisible.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ RequestRectangleVisible a = getActivity();
+
+ mScrollView = (ScrollView) a.findViewById(R.id.scrollView);
+ mClickToScrollFromAbove = (Button) a.findViewById(R.id.scrollToRectFromTop);
+ mClickToScrollToUpperBlob = (Button) a.findViewById(R.id.scrollToRectFromTop2);
+ mTopBlob = (TextView) a.findViewById(R.id.topBlob);
+ mChildToScrollTo = a.findViewById(R.id.childToMakeVisible);
+ mBottomBlob = (TextView) a.findViewById(R.id.bottomBlob);
+ mClickToScrollToBlobLowerBlob = (Button) a.findViewById(R.id.scrollToRectFromBottom2);
+ mClickToScrollFromBelow = (Button) a.findViewById(R.id.scrollToRectFromBottom);
+
+
+ }
+
+
+ @MediumTest
+ public void testPreconditions() {
+ assertNotNull(mScrollView);
+ assertNotNull(mClickToScrollFromAbove);
+ assertNotNull(mClickToScrollToUpperBlob);
+ assertNotNull(mTopBlob);
+ assertNotNull(mChildToScrollTo);
+ assertNotNull(mBottomBlob);
+ assertNotNull(mClickToScrollToBlobLowerBlob);
+ assertNotNull(mClickToScrollFromBelow);
+
+ assertTrue("top blob needs to be taller than the screen for many of the "
+ + "tests below to work.",
+ mTopBlob.getHeight() > mScrollView.getHeight());
+
+ assertTrue("bottom blob needs to be taller than the screen for many of the "
+ + "tests below to work.",
+ mBottomBlob.getHeight() > mScrollView.getHeight());
+
+ assertTrue("top blob needs to be lower than the fading edge region",
+ mTopBlob.getTop() > mScrollView.getVerticalFadingEdgeLength());
+ }
+
+ @MediumTest
+ public void testScrollToOffScreenRectangleFromTop() {
+ // view is off screen
+ assertTrue(mClickToScrollFromAbove.hasFocus());
+ ViewAsserts.assertOffScreenBelow(mScrollView, mChildToScrollTo);
+
+ // click
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync(); // wait for scrolling to finish
+
+ // should be on screen, positioned at the bottom (with room for
+ // fading edge)
+ ViewAsserts.assertOnScreen(mScrollView, mChildToScrollTo);
+ ViewAsserts.assertHasScreenCoordinates(
+ mScrollView, mChildToScrollTo,
+ 0,
+ mScrollView.getHeight()
+ - mChildToScrollTo.getHeight()
+ - mScrollView.getVerticalFadingEdgeLength());
+ }
+
+ @MediumTest
+ public void testScrollToPartiallyOffScreenRectFromTop() {
+ pressDownUntilViewInFocus(mClickToScrollToUpperBlob, 4);
+
+ // make sure the blob is indeed partially on screen below
+ assertOnBottomEdgeOfScreen(mScrollView, mTopBlob);
+
+ // click
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync(); // wait for scrolling to finish
+
+ // blob should have moved so top of it is at top of screen (with
+ // room for the vertical fading edge
+ ViewAsserts.assertHasScreenCoordinates(
+ mScrollView, mTopBlob, 0, mScrollView.getVerticalFadingEdgeLength());
+ }
+
+ @LargeTest
+ public void testScrollToOffScreenRectangleFromBottom() {
+ // go to bottom button
+ pressDownUntilViewInFocus(mClickToScrollFromBelow, 10);
+
+ // view is off screen above
+ assertTrue(mClickToScrollFromBelow.hasFocus());
+ ViewAsserts.assertOffScreenAbove(mScrollView, mChildToScrollTo);
+
+ // click
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync(); // wait for scrolling to finish
+
+ // on screen, positioned at top (with room for fading edge)
+ ViewAsserts.assertOnScreen(mScrollView, mChildToScrollTo);
+ ViewAsserts.assertHasScreenCoordinates(
+ mScrollView, mChildToScrollTo, 0, mScrollView.getVerticalFadingEdgeLength());
+ }
+
+
+ @LargeTest
+ public void testScrollToPartiallyOffScreenRectFromBottom() {
+ pressDownUntilViewInFocus(mClickToScrollToBlobLowerBlob, 10);
+
+ // make sure the blob is indeed partially on screen above
+ assertOnTopEdgeOfScreen(mScrollView, mBottomBlob);
+
+ // click
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync(); // wait for scrolling to finish
+
+ // blob should have moved so bottom of it is at bottom of screen
+ // with room for vertical fading edge
+ ViewAsserts.assertHasScreenCoordinates(
+ mScrollView, mBottomBlob,
+ 0,
+ mScrollView.getHeight() - mBottomBlob.getHeight()
+ - mScrollView.getVerticalFadingEdgeLength());
+ }
+
+
+ /**
+ * Press the down key until a particular view is in focus
+ * @param view The view to get in focus.
+ * @param maxKeyPress The maximum times to press down before failing.
+ */
+ private void pressDownUntilViewInFocus(View view, int maxKeyPress) {
+ int count = 0;
+ while(!view.hasFocus()) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ // just in case...
+ if (++count > maxKeyPress) {
+ fail("couldn't move down to bottom button within "
+ + maxKeyPress + " key presses.");
+ }
+ }
+ }
+
+ /**
+ * Assert that view overlaps the bottom edge of the screen
+ * @param origin The root view of the screen.
+ * @param view The view
+ */
+ static public void assertOnBottomEdgeOfScreen(View origin, View view) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ int bottom = xy[1] + view.getHeight();
+ int bottomOfRoot = xyRoot[1] + origin.getHeight();
+
+ assertTrue(bottom > bottomOfRoot);
+
+ assertTrue(xy[1] < bottomOfRoot);
+ assertTrue(bottom > bottomOfRoot);
+ }
+
+ /**
+ * Assert that view overlaps the bottom edge of the screen
+ * @param origin The root view of the screen.
+ * @param view The view
+ */
+ static public void assertOnTopEdgeOfScreen(View origin, View view) {
+ int[] xy = new int[2];
+ view.getLocationOnScreen(xy);
+
+ int[] xyRoot = new int[2];
+ origin.getLocationOnScreen(xyRoot);
+
+ int bottom = xy[1] + view.getHeight();
+ int bottomOfRoot = xyRoot[1] + origin.getHeight();
+
+ assertTrue(bottom < bottomOfRoot);
+ assertTrue(bottom > xyRoot[1]);
+
+ assertTrue(xy[1] < xyRoot[1]);
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScroll.java b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScroll.java
new file mode 100644
index 0000000..0e2586d
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScroll.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Button;
+import android.view.View;
+import android.graphics.Rect;
+
+public class RequestRectangleVisibleWithInternalScroll extends Activity {
+
+ private int scrollYofBlob = 52;
+
+ private TextView mTextBlob;
+ private Button mScrollToBlob;
+
+
+ public int getScrollYofBlob() {
+ return scrollYofBlob;
+ }
+
+
+ public TextView getTextBlob() {
+ return mTextBlob;
+ }
+
+
+ public Button getScrollToBlob() {
+ return mScrollToBlob;
+ }
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ setContentView(R.layout.scroll_to_rect_with_internal_scroll);
+
+ mTextBlob = (TextView) findViewById(R.id.blob);
+ mTextBlob.scrollBy(0, scrollYofBlob);
+
+
+ mScrollToBlob = (Button) findViewById(R.id.scrollToBlob);
+ mScrollToBlob.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+
+ // the rect we want to make visible is offset to match
+ // the internal scroll
+ Rect rect = new Rect();
+ rect.set(0, 0, 0, mTextBlob.getHeight());
+ rect.offset(0, mTextBlob.getScrollY());
+ mTextBlob.requestRectangleOnScreen(rect);
+ }
+ });
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScrollTest.java b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScrollTest.java
new file mode 100644
index 0000000..5e9b520
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/RequestRectangleVisibleWithInternalScrollTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import com.android.frameworks.coretests.R;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.ViewAsserts;
+import android.test.suitebuilder.annotation.Suppress;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+/**
+ * This is suppressed because {@link TextView#scrollBy} isn't working.
+ */
+@Suppress
+public class RequestRectangleVisibleWithInternalScrollTest
+ extends ActivityInstrumentationTestCase<RequestRectangleVisibleWithInternalScroll> {
+
+ private TextView mTextBlob;
+ private Button mScrollToBlob;
+
+ private ScrollView mScrollView;
+
+
+ public RequestRectangleVisibleWithInternalScrollTest() {
+ super("com.android.frameworks.coretests",
+ RequestRectangleVisibleWithInternalScroll.class);
+ }
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTextBlob = getActivity().getTextBlob();
+ mScrollToBlob = getActivity().getScrollToBlob();
+
+ mScrollView = (ScrollView) getActivity().findViewById(R.id.scrollView);
+ }
+
+ public void testPreconditions() {
+ assertNotNull(mTextBlob);
+ assertNotNull(mScrollToBlob);
+ assertEquals(getActivity().getScrollYofBlob(), mTextBlob.getScrollY());
+ }
+
+ public void testMoveToChildWithScrollYBelow() {
+ assertTrue(mScrollToBlob.hasFocus());
+
+ ViewAsserts.assertOffScreenBelow(mScrollView, mTextBlob);
+
+ // click
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ getInstrumentation().waitForIdleSync(); // wait for scrolling to finish
+
+ // should be on screen, positioned at the bottom (with enough room for
+ // fading edge)
+ ViewAsserts.assertOnScreen(mScrollView, mTextBlob);
+ ViewAsserts.assertHasScreenCoordinates(
+ mScrollView, mTextBlob,
+ 0,
+ mScrollView.getHeight()
+ - mTextBlob.getHeight()
+ - mScrollView.getVerticalFadingEdgeLength());
+
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabels.java b/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabels.java
new file mode 100644
index 0000000..4d0892c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabels.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import com.android.frameworks.coretests.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Button;
+
+
+/**
+ * Basic scroll view example
+ */
+public class ScrollViewButtonsAndLabels extends Activity {
+
+ private ScrollView mScrollView;
+ private LinearLayout mLinearLayout;
+
+ private int mNumGroups = 10;
+
+
+ public ScrollView getScrollView() {
+ return mScrollView;
+ }
+
+ public LinearLayout getLinearLayout() {
+ return mLinearLayout;
+ }
+
+ public int getNumButtons() {
+ return mNumGroups;
+ }
+
+ public Button getButton(int groupNum) {
+ if (groupNum > mNumGroups) {
+ throw new IllegalArgumentException("groupNum > " + mNumGroups);
+ }
+ return (Button) mLinearLayout.getChildAt(2*groupNum);
+ }
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.scrollview_linear_layout);
+
+
+ // estimated ratio to get enough buttons so a couple are off screen
+ int screenHeight = getWindowManager().getDefaultDisplay().getHeight();
+ mNumGroups = screenHeight / 30;
+
+ mScrollView = (ScrollView) findViewById(R.id.scrollView);
+ mLinearLayout = (LinearLayout) findViewById(R.id.layout);
+
+ LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ );
+
+ for (int i = 0; i < mNumGroups; i++) {
+ // want button to be first and last
+ if (i > 0) {
+ TextView textView = new TextView(this);
+ textView.setText("Text View " + i);
+ mLinearLayout.addView(textView, p);
+ }
+
+ Button button = new Button(this);
+ button.setText("Button " + (i + 1));
+ mLinearLayout.addView(button, p);
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabelsTest.java b/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabelsTest.java
new file mode 100644
index 0000000..7efb9aa
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ScrollViewButtonsAndLabelsTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.widget.scroll.ScrollViewButtonsAndLabels;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.view.KeyEvent;
+
+
+public class ScrollViewButtonsAndLabelsTest
+ extends ActivityInstrumentationTestCase<ScrollViewButtonsAndLabels> {
+
+ private ScrollView mScrollView;
+ private LinearLayout mLinearLayout;
+ private int mScreenBottom;
+ private int mScreenTop;
+
+ public ScrollViewButtonsAndLabelsTest() {
+ super("com.android.frameworks.coretests",
+ ScrollViewButtonsAndLabels.class);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mScrollView = getActivity().getScrollView();
+ mLinearLayout = getActivity().getLinearLayout();
+
+ int origin[] = {0, 0};
+ mScrollView.getLocationOnScreen(origin);
+ mScreenTop = origin[1];
+ mScreenBottom = origin[1] + mScrollView.getHeight();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("vertical fading edge width needs to be non-zero for this "
+ + "test to be worth anything",
+ mScrollView.getVerticalFadingEdgeLength() > 0);
+ }
+
+ // moving down to something off screen should move the element
+ // onto the screen just above the vertical fading edge
+ @LargeTest
+ public void testArrowScrollDownOffScreenVerticalFadingEdge() {
+
+ int offScreenIndex = findFirstButtonOffScreenTop2Bottom();
+ Button firstButtonOffScreen = getActivity().getButton(offScreenIndex);
+
+ for (int i = 0; i < offScreenIndex; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertTrue(firstButtonOffScreen.hasFocus());
+
+ assertTrue("the button we've moved to off screen must not be the last "
+ + "button in the scroll view for this test to work (since we "
+ + "are expecting the fading edge to be there).",
+ offScreenIndex < getActivity().getNumButtons());
+
+ // now we are at the first button off screen
+ int buttonLoc[] = {0, 0};
+ firstButtonOffScreen.getLocationOnScreen(buttonLoc);
+ int buttonBottom = buttonLoc[1] + firstButtonOffScreen.getHeight();
+
+ int verticalFadingEdgeLength = mScrollView
+ .getVerticalFadingEdgeLength();
+ assertEquals("bottom of button should be verticalFadingEdgeLength "
+ + "above the bottom of the screen",
+ buttonBottom, mScreenBottom - verticalFadingEdgeLength);
+ }
+
+ // there should be no offset for vertical fading edge
+ // if the item is the last one on screen
+ @LargeTest
+ public void testArrowScrollDownToBottomElementOnScreen() {
+
+ int numGroups = getActivity().getNumButtons();
+ Button lastButton = getActivity().getButton(numGroups - 1);
+
+ assertEquals("button needs to be at the very bottom of the layout for "
+ + "this test to work",
+ mLinearLayout.getHeight(), lastButton.getBottom());
+
+ // move down to last button
+ for (int i = 0; i < numGroups; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertTrue("last button should have focus", lastButton.hasFocus());
+
+ int buttonLoc[] = {0, 0};
+ lastButton.getLocationOnScreen(buttonLoc);
+ int buttonBottom = buttonLoc[1] + lastButton.getHeight();
+ assertEquals("button should be at very bottom of screen",
+ mScreenBottom, buttonBottom);
+ }
+
+ @LargeTest
+ public void testArrowScrollUpOffScreenVerticalFadingEdge() {
+ // get to bottom button
+ int numGroups = goToBottomButton();
+
+ // go up to first off screen button
+ int offScreenIndex = findFirstButtonOffScreenBottom2Top();
+ Button offScreenButton = getActivity().getButton(offScreenIndex);
+ int clicksToOffScreenIndex = numGroups - offScreenIndex - 1;
+ for (int i = 0; i < clicksToOffScreenIndex; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertTrue("we want to be at offScreenButton", offScreenButton.hasFocus());
+
+ // top should take into account fading edge
+ int buttonLoc[] = {0, 0};
+ offScreenButton.getLocationOnScreen(buttonLoc);
+ assertEquals("top should take into account fading edge",
+ mScreenTop + mScrollView.getVerticalFadingEdgeLength(), buttonLoc[1]);
+ }
+
+
+ @LargeTest
+ public void testArrowScrollUpToTopElementOnScreen() {
+ // get to bottom button
+ int numButtons = goToBottomButton();
+
+ // go back to the top
+ for (int i = 0; i < numButtons; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+ getInstrumentation().waitForIdleSync();
+
+ Button topButton = getActivity().getButton(0);
+ assertTrue("should be back at top button", topButton.hasFocus());
+
+
+ int buttonLoc[] = {0, 0};
+ topButton.getLocationOnScreen(buttonLoc);
+ assertEquals("top of top button should be at top of screen; no need to take"
+ + " into account vertical fading edge.",
+ mScreenTop, buttonLoc[1]);
+ }
+
+ private int goToBottomButton() {
+ int numButtons = getActivity().getNumButtons();
+ Button lastButton = getActivity().getButton(numButtons - 1);
+
+ for (int i = 0; i < numButtons; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+ getInstrumentation().waitForIdleSync();
+ assertTrue("we want to be at the last button", lastButton.hasFocus());
+ return numButtons;
+ }
+
+ // search from top to bottom for the first button off screen
+ private int findFirstButtonOffScreenTop2Bottom() {
+ int origin[] = {0, 0};
+ mScrollView.getLocationOnScreen(origin);
+ int screenHeight = mScrollView.getHeight();
+
+ for (int i = 0; i < getActivity().getNumButtons(); i++) {
+
+ int buttonLoc[] = {0, 0};
+ Button button = getActivity().getButton(i);
+ button.getLocationOnScreen(buttonLoc);
+
+ if (buttonLoc[1] - origin[1] > screenHeight) {
+ return i;
+ }
+ }
+ fail("couldn't find first button off screen");
+ return -1; // this won't execute, but the compiler needs it
+ }
+
+ private int findFirstButtonOffScreenBottom2Top() {
+ int origin[] = {0, 0};
+ mScrollView.getLocationOnScreen(origin);
+
+ for (int i = getActivity().getNumButtons() - 1; i >= 0; i--) {
+
+ int buttonLoc[] = {0, 0};
+ Button button = getActivity().getButton(i);
+ button.getLocationOnScreen(buttonLoc);
+
+ if (buttonLoc[1] < 0) {
+ return i;
+ }
+ }
+ fail("couldn't find first button off screen");
+ return -1; // this won't execute, but the compiler needs it
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/ShortButtons.java b/core/tests/coretests/src/android/widget/scroll/ShortButtons.java
new file mode 100644
index 0000000..3a0f29a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/ShortButtons.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.util.ScrollViewScenario;
+
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+/**
+ * A series of short buttons, some of which are embedded within another
+ * layout.
+ */
+public class ShortButtons extends ScrollViewScenario {
+
+ private final int mNumButtons = 10;
+ protected final float mButtonHeightFactor = 0.2f;
+
+ public int getNumButtons() {
+ return mNumButtons;
+ }
+
+ public Button getButtonAt(int index) {
+ if (index < 3) {
+ return getContentChildAt(index);
+ } else {
+ LinearLayout ll = getContentChildAt(3);
+ return (Button) ll.getChildAt(index - 3);
+ }
+ }
+
+ @Override
+ protected void init(Params params) {
+ final int numButtonsInSubLayout = getNumButtons() - 3;
+ params.addButtons(3, "top-level", mButtonHeightFactor)
+ .addVerticalLLOfButtons("embedded",
+ numButtonsInSubLayout,
+ numButtonsInSubLayout * mButtonHeightFactor);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/TallTextAboveButton.java b/core/tests/coretests/src/android/widget/scroll/TallTextAboveButton.java
new file mode 100644
index 0000000..4096fe9
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/TallTextAboveButton.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll;
+
+import android.util.ScrollViewScenario;
+
+/**
+ * An (unfocusable) text view that takes up more than the height
+ * of the screen followed by a button.
+ */
+public class TallTextAboveButton extends ScrollViewScenario {
+
+ protected void init(Params params) {
+ params.addTextView("top tall", 1.1f)
+ .addButton("button", 0.2f);
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/arrowscroll/ButtonsWithTallTextViewInBetweenTest.java b/core/tests/coretests/src/android/widget/scroll/arrowscroll/ButtonsWithTallTextViewInBetweenTest.java
new file mode 100644
index 0000000..56d7ed2
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/arrowscroll/ButtonsWithTallTextViewInBetweenTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll.arrowscroll;
+
+import android.widget.scroll.ButtonsWithTallTextViewInBetween;
+
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+public class ButtonsWithTallTextViewInBetweenTest
+ extends ActivityInstrumentationTestCase<ButtonsWithTallTextViewInBetween> {
+
+ private ScrollView mScrollView;
+ private Button mTopButton;
+ private TextView mMiddleFiller;
+ private TextView mBottomButton;
+
+ public ButtonsWithTallTextViewInBetweenTest() {
+ super("com.android.frameworks.coretests", ButtonsWithTallTextViewInBetween.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mScrollView = getActivity().getScrollView();
+ mTopButton = getActivity().getTopButton();
+ mMiddleFiller = getActivity().getMiddleFiller();
+ mBottomButton = getActivity().getBottomButton();
+ }
+
+ private Rect mTempRect = new Rect();
+
+ private int getTopWithinScrollView(View descendant) {
+ descendant.getDrawingRect(mTempRect);
+ mScrollView.offsetDescendantRectToMyCoords(descendant, mTempRect);
+ return mTempRect.top;
+ }
+
+ private int getBottomWithinScrollView(View descendant) {
+ descendant.getDrawingRect(mTempRect);
+ mScrollView.offsetDescendantRectToMyCoords(descendant, mTempRect);
+ return mTempRect.bottom;
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("top button should be shorter than max scroll amount",
+ mTopButton.getHeight() <
+ mScrollView.getMaxScrollAmount());
+ assertTrue("bottom button should be further than max scroll amount off screen",
+ getTopWithinScrollView(mBottomButton)- mScrollView.getBottom() > mScrollView.getMaxScrollAmount());
+ }
+
+ @MediumTest
+ public void testPanTopButtonOffScreenLosesFocus() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertEquals("scroll view should be scrolled by the max amount for one "
+ + "arrow navigation",
+ mScrollView.getMaxScrollAmount(),
+ mScrollView.getScrollY());
+
+ assertTrue("top button should be off screen",
+ getBottomWithinScrollView(mTopButton) < mScrollView.getScrollY());
+
+ assertFalse("top button should have lost focus",
+ mTopButton.isFocused());
+
+ assertTrue("scroll view should be focused", mScrollView.isFocused());
+ }
+
+ @MediumTest
+ public void testScrollDownToBottomButton() throws Exception {
+ final int screenBottom = mScrollView.getScrollY() + mScrollView.getHeight();
+ final int numDownsToButtonButton =
+ ((getBottomWithinScrollView(mBottomButton) - screenBottom)) / mScrollView.getMaxScrollAmount() + 1;
+
+ for (int i = 0; i < numDownsToButtonButton; i++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ assertTrue("bottombutton.isFocused", mBottomButton.isFocused());
+
+ assertEquals("should be fully scrolled to bottom",
+ getActivity().getLinearLayout().getHeight() - mScrollView.getHeight(),
+ mScrollView.getScrollY());
+ }
+
+ @MediumTest
+ public void testPanBottomButtonOffScreenLosesFocus() throws Exception {
+ mBottomButton.post(new Runnable() {
+ public void run() {
+ mBottomButton.requestFocus();
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+
+ assertTrue("bottombutton.isFocused", mBottomButton.isFocused());
+ final int maxScroll = getActivity().getLinearLayout().getHeight()
+ - mScrollView.getHeight();
+ assertEquals("should be fully scrolled to bottom",
+ maxScroll,
+ mScrollView.getScrollY());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+
+ assertEquals("scroll view should have scrolled by the max amount for one "
+ + "arrow navigation",
+ maxScroll - mScrollView.getMaxScrollAmount(),
+ mScrollView.getScrollY());
+
+ assertTrue("bottom button should be off screen",
+ getTopWithinScrollView(mBottomButton) > mScrollView.getScrollY() + mScrollView.getHeight());
+
+ assertFalse("bottom button should have lost focus",
+ mBottomButton.isFocused());
+
+ assertTrue("scroll view should be focused", mScrollView.isFocused());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/arrowscroll/ShortButtonsTest.java b/core/tests/coretests/src/android/widget/scroll/arrowscroll/ShortButtonsTest.java
new file mode 100644
index 0000000..267d8ee
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/arrowscroll/ShortButtonsTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll.arrowscroll;
+
+import android.widget.scroll.ShortButtons;
+
+import android.graphics.Rect;
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.ScrollView;
+
+public class ShortButtonsTest extends ActivityInstrumentationTestCase<ShortButtons> {
+
+ private ScrollView mScrollView;
+
+ public ShortButtonsTest() {
+ super("com.android.frameworks.coretests", ShortButtons.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mScrollView = getActivity().getScrollView();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("buttons should be shorter than screen",
+ getActivity().getButtonAt(0).getHeight()
+ < mScrollView.getHeight());
+
+ assertTrue("should be enough buttons to have some scrolled off screen",
+ getActivity().getLinearLayout().getHeight()
+ > getActivity().getScrollView().getHeight());
+ }
+
+ @LargeTest
+ public void testScrollDownToBottomThroughButtons() throws Exception {
+ final int numButtons = getActivity().getNumButtons();
+
+ for (int i = 0; i < numButtons; i++) {
+ String prefix = "after " + i + " downs expected button " + i;
+ final Button button = getActivity().getButtonAt(i);
+ assertTrue(prefix + " to have focus", button.isFocused());
+ assertTrue(prefix + " to be on screen", isButtonOnScreen(button));
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ }
+
+ assertEquals("should be fully scrolled to bottom",
+ getActivity().getLinearLayout().getHeight() - mScrollView.getHeight(),
+ mScrollView.getScrollY());
+ }
+
+ @LargeTest
+ public void testScrollFromBottomToTopThroughButtons() throws Exception {
+ final int numButtons = getActivity().getNumButtons();
+
+ final Button lastButton = getActivity().getButtonAt(numButtons - 1);
+
+ lastButton.post(new Runnable() {
+ public void run() {
+ lastButton.requestFocus();
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+
+ assertTrue("lastButton.isFocused()", lastButton.isFocused());
+
+ for (int i = numButtons - 1; i >= 0; i--) {
+ String prefix = "after " + i + " ups expected button " + i;
+ final Button button = getActivity().getButtonAt(i);
+ assertTrue(prefix + " to have focus", button.isFocused());
+ assertTrue(prefix + " to be on screen", isButtonOnScreen(button));
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ }
+
+ assertEquals("should be fully scrolled to top",
+ 0,
+ mScrollView.getScrollY());
+ }
+
+ private Rect mTempRect = new Rect();
+ protected boolean isButtonOnScreen(Button b) {
+ b.getDrawingRect(mTempRect);
+ mScrollView.offsetDescendantRectToMyCoords(b, mTempRect);
+ return mTempRect.bottom >= mScrollView.getScrollY()
+ && mTempRect.top <= (mScrollView.getScrollY() + mScrollView.getHeight());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/scroll/arrowscroll/TallTextAboveButtonTest.java b/core/tests/coretests/src/android/widget/scroll/arrowscroll/TallTextAboveButtonTest.java
new file mode 100644
index 0000000..5351839
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/scroll/arrowscroll/TallTextAboveButtonTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.scroll.arrowscroll;
+
+import android.widget.scroll.TallTextAboveButton;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+public class TallTextAboveButtonTest extends ActivityInstrumentationTestCase<TallTextAboveButton> {
+ private ScrollView mScrollView;
+ private TextView mTopText;
+ private TextView mBottomButton;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mScrollView = getActivity().getScrollView();
+ mTopText = getActivity().getContentChildAt(0);
+ mBottomButton = getActivity().getContentChildAt(1);
+ }
+
+ public TallTextAboveButtonTest() {
+ super("com.android.frameworks.coretests", TallTextAboveButton.class);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("top text should be larger than screen",
+ mTopText.getHeight() > mScrollView.getHeight());
+ assertTrue("scroll view should have focus (because nothing else focusable "
+ + "is on screen), but " + getActivity().getScrollView().findFocus() + " does instead",
+ getActivity().getScrollView().isFocused());
+ }
+
+ @MediumTest
+ public void testGainFocusAsScrolledOntoScreen() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue("button should have scrolled onto screen",
+ mBottomButton.getBottom() >= mScrollView.getBottom());
+ assertTrue("button should have gained focus as it was scrolled completely "
+ + "into view", mBottomButton.isFocused());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertTrue("scroll view should have focus, but " + getActivity().getScrollView().findFocus() + " does instead",
+ getActivity().getScrollView().isFocused());
+ }
+
+ @MediumTest
+ public void testScrollingButtonOffScreenLosesFocus() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue("button should have focus", mBottomButton.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ assertTrue("scroll view should have focus, but " + getActivity().getScrollView().findFocus() + " does instead",
+ getActivity().getScrollView().isFocused());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/touchmode/ChangeTouchModeTest.java b/core/tests/coretests/src/android/widget/touchmode/ChangeTouchModeTest.java
new file mode 100644
index 0000000..449c95c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/touchmode/ChangeTouchModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.touchmode;
+
+import android.widget.layout.linear.LLOfButtons1;
+import android.widget.layout.linear.LLOfButtons2;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
+import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+
+/**
+ * Tests that the touch mode changes from various events, and that the state
+ * persists across activities.
+ */
+public class ChangeTouchModeTest extends ActivityInstrumentationTestCase<LLOfButtons1> {
+
+ public ChangeTouchModeTest() {
+ super("com.android.frameworks.coretests", LLOfButtons1.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @MediumTest
+ public void testPreconditions() throws Exception {
+ assertFalse("touch mode", getActivity().isInTouchMode());
+ }
+
+ @MediumTest
+ public void testTouchingScreenEntersTouchMode() throws Exception {
+ assertInTouchModeAfterTap(this, getActivity().getFirstButton());
+ assertTrue("touch mode", getActivity().isInTouchMode());
+ }
+
+ // TODO: reenable when more reliable
+ public void DISABLE_testDpadDirectionLeavesTouchMode() throws Exception {
+ assertInTouchModeAfterClick(this, getActivity().getFirstButton());
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ assertNotInTouchModeAfterKey(this, KeyEvent.KEYCODE_DPAD_RIGHT, getActivity().getFirstButton());
+ assertFalse("touch mode", getActivity().isInTouchMode());
+ }
+
+ public void TODO_touchTrackBallMovementLeavesTouchMode() throws Exception {
+
+ }
+
+ @MediumTest
+ public void testTouchModeFalseAcrossActivites() throws Exception {
+
+ getInstrumentation().waitForIdleSync();
+
+ LLOfButtons2 otherActivity = null;
+ try {
+ otherActivity =
+ launchActivity("com.android.frameworks.coretests", LLOfButtons2.class, null);
+ assertNotNull(otherActivity);
+ assertFalse(otherActivity.isInTouchMode());
+ } finally {
+ if (otherActivity != null) {
+ otherActivity.finish();
+ }
+ }
+ }
+
+ @LargeTest
+ public void testTouchModeTrueAcrossActivites() throws Exception {
+ assertInTouchModeAfterClick(this, getActivity().getFirstButton());
+ LLOfButtons2 otherActivity = null;
+ try {
+ otherActivity =
+ launchActivity("com.android.frameworks.coretests", LLOfButtons2.class, null);
+ assertNotNull(otherActivity);
+ assertTrue(otherActivity.isInTouchMode());
+ } finally {
+ if (otherActivity != null) {
+ otherActivity.finish();
+ }
+ }
+ }
+
+ @LargeTest
+ public void testTouchModeChangedInOtherActivity() throws Exception {
+
+ assertFalse("touch mode", getActivity().isInTouchMode());
+
+ LLOfButtons2 otherActivity = null;
+ try {
+ otherActivity =
+ launchActivity("com.android.frameworks.coretests", LLOfButtons2.class, null);
+ assertNotNull(otherActivity);
+ assertFalse(otherActivity.isInTouchMode());
+ assertInTouchModeAfterClick(this, otherActivity.getFirstButton());
+ assertTrue(otherActivity.isInTouchMode());
+ } finally {
+ if (otherActivity != null) {
+ otherActivity.finish();
+ }
+ }
+
+ // need to wait for async update back to window to occur
+ Thread.sleep(200);
+
+ assertTrue("touch mode", getActivity().isInTouchMode());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/touchmode/FocusableInTouchModeClickTest.java b/core/tests/coretests/src/android/widget/touchmode/FocusableInTouchModeClickTest.java
new file mode 100644
index 0000000..691b25a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/touchmode/FocusableInTouchModeClickTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.touchmode;
+
+import android.widget.layout.linear.LLOfTwoFocusableInTouchMode;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+
+public class FocusableInTouchModeClickTest extends ActivityInstrumentationTestCase2<LLOfTwoFocusableInTouchMode> {
+
+ public FocusableInTouchModeClickTest() {
+ super("com.android.frameworks.coretests", LLOfTwoFocusableInTouchMode.class);
+ }
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ setActivityInitialTouchMode(true);
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("should start in touch mode", getActivity().getButton1().isInTouchMode());
+ assertTrue(getActivity().getButton1().isFocused());
+ }
+
+ @LargeTest
+ public void testClickGivesFocusNoClickFired() {
+ TouchUtils.clickView(this, getActivity().getButton2());
+ assertTrue("click should give focusable in touch mode focus",
+ getActivity().getButton2().isFocused());
+ assertFalse("getting focus should result in no on click",
+ getActivity().isB2Fired());
+
+ TouchUtils.clickView(this, getActivity().getButton2());
+ assertTrue("subsequent click while focused should fire on click",
+ getActivity().isB2Fired());
+ }
+
+ @MediumTest
+ public void testTapGivesFocusNoClickFired() {
+ TouchUtils.touchAndCancelView(this, getActivity().getButton2());
+ assertFalse("button shouldn't have fired click", getActivity().isB2Fired());
+ assertFalse("button shouldn't have focus", getActivity().getButton2().isFocused());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/touchmode/StartInTouchWithViewInFocusTest.java b/core/tests/coretests/src/android/widget/touchmode/StartInTouchWithViewInFocusTest.java
new file mode 100644
index 0000000..5339188
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/touchmode/StartInTouchWithViewInFocusTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.touchmode;
+
+import android.widget.layout.linear.LLEditTextThenButton;
+import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.EditText;
+
+public class StartInTouchWithViewInFocusTest extends
+ ActivityInstrumentationTestCase2<LLEditTextThenButton> {
+
+ private EditText mEditText;
+
+ private Button mButton;
+
+ public StartInTouchWithViewInFocusTest() {
+ super("com.android.frameworks.coretests", LLEditTextThenButton.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.setActivityInitialTouchMode(true);
+ mEditText = getActivity().getEditText();
+ mButton = getActivity().getButton();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertTrue("should start in touch mode", mEditText.isInTouchMode());
+ assertTrue("edit text is focusable in touch mode, should have focus", mEditText.isFocused());
+ }
+
+ // TODO: reenable when more reliable
+ public void DISABLE_testKeyDownLeavesTouchModeAndGoesToNextView() {
+ assertNotInTouchModeAfterKey(this, KeyEvent.KEYCODE_DPAD_DOWN, mEditText);
+ assertFalse("should have left touch mode", mEditText.isInTouchMode());
+ assertTrue("should have given focus to next view", mButton.isFocused());
+ }
+
+ // TODO: reenable when more reliable
+ public void DISABLE_testNonDirectionalKeyExitsTouchMode() {
+ assertNotInTouchModeAfterKey(this, KeyEvent.KEYCODE_A, mEditText);
+ assertFalse("should have left touch mode", mEditText.isInTouchMode());
+ assertTrue("edit text should still have focus", mEditText.isFocused());
+ }
+
+}
diff --git a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
new file mode 100644
index 0000000..bd6977e
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.touchmode;
+
+import android.widget.layout.linear.LLOfButtons1;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
+import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+
+/**
+ * Make sure focus isn't kept by buttons when entering touch mode.
+ *
+ * When in touch mode and hitting the d-pad, we should leave touch mode and the
+ * top most focusable gets focus.
+ */
+public class TouchModeFocusChangeTest extends ActivityInstrumentationTestCase<LLOfButtons1> {
+ private LLOfButtons1 mActivity;
+ private Button mFirstButton;
+
+ public TouchModeFocusChangeTest() {
+ super("com.android.frameworks.coretests", LLOfButtons1.class);
+ }
+
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mActivity = getActivity();
+ mFirstButton = mActivity.getFirstButton();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertFalse("we should not be in touch mode", mActivity.isInTouchMode());
+ assertTrue("top button should have focus", mFirstButton.isFocused());
+ }
+
+ @MediumTest
+ public void testTouchButtonNotTakeFocus() {
+ assertInTouchModeAfterTap(this, mFirstButton);
+
+ assertTrue("should be in touch mode", mActivity.isInTouchMode());
+ assertFalse("button.isFocused",
+ mFirstButton.isFocused());
+ assertFalse("button.hasFocus",
+ mFirstButton.hasFocus());
+ assertNull("activity shouldn't have focus", mActivity.getCurrentFocus());
+ assertFalse("linear layout should not have focus",
+ mActivity.getLayout().hasFocus());
+
+ assertTrue("button's onClickListener should have fired",
+ mActivity.buttonClickListenerFired());
+ }
+
+ // TODO: reenable when more reliable
+ public void DISABLE_testLeaveTouchModeWithDpadEvent() {
+ assertInTouchModeAfterClick(this, mFirstButton);
+
+ assertTrue("should be in touch mode", mActivity.isInTouchMode());
+ assertFalse("button should not have focus when touched",
+ mFirstButton.isFocused());
+
+ assertNotInTouchModeAfterKey(this, KeyEvent.KEYCODE_DPAD_RIGHT, mFirstButton);
+ assertFalse("should be out of touch mode", mActivity.isInTouchMode());
+ assertTrue("first button (the top most focusable) should have gained focus",
+ mFirstButton.isFocused());
+ }
+
+
+}
diff --git a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusableTest.java b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusableTest.java
new file mode 100644
index 0000000..dd07a08
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusableTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.touchmode;
+
+import android.widget.layout.linear.LLEditTextThenButton;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
+import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
+
+import android.test.ActivityInstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.widget.EditText;
+
+/**
+ * Some views, like edit texts, can keep and gain focus even when in touch mode.
+ */
+public class TouchModeFocusableTest extends ActivityInstrumentationTestCase<LLEditTextThenButton> {
+ private EditText mEditText;
+ private Button mButton;
+
+
+ public TouchModeFocusableTest() {
+ super("com.android.frameworks.coretests", LLEditTextThenButton.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mEditText = getActivity().getEditText();
+ mButton = getActivity().getButton();
+ }
+
+ @MediumTest
+ public void testPreconditions() {
+ assertFalse("should not be in touch mode to start off", mButton.isInTouchMode());
+ assertTrue("edit text should have focus", mEditText.isFocused());
+ assertTrue("edit text should be focusable in touch mode", mEditText.isFocusableInTouchMode());
+ }
+
+ @MediumTest
+ public void testClickButtonEditTextKeepsFocus() {
+ assertInTouchModeAfterTap(this, mButton);
+ assertTrue("should be in touch mode", mButton.isInTouchMode());
+ assertTrue("edit text should still have focus", mEditText.isFocused());
+ }
+
+ @LargeTest
+ public void testClickEditTextGivesItFocus() {
+ // go down to button
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue("button should have focus", mButton.isFocused());
+
+ assertInTouchModeAfterClick(this, mEditText);
+ assertTrue("clicking edit text should have entered touch mode", mButton.isInTouchMode());
+ assertTrue("clicking edit text should have given it focus", mEditText.isFocused());
+ }
+
+
+ // entering touch mode takes focus away from the currently focused item if it
+ // isn't focusable in touch mode.
+ @LargeTest
+ public void testEnterTouchModeGivesFocusBackToFocusableInTouchMode() {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+
+ assertTrue("button should have focus",
+ mButton.isFocused());
+
+ assertInTouchModeAfterClick(this, mButton);
+ assertTrue("should be in touch mode", mButton.isInTouchMode());
+ assertNull("nothing should have focus", getActivity().getCurrentFocus());
+ assertFalse("layout should not have focus",
+ getActivity().getLayout().hasFocus());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/DNParserTest.java b/core/tests/coretests/src/com/android/internal/net/DNParserTest.java
new file mode 100644
index 0000000..9b490a3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/net/DNParserTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.net;
+
+import com.android.internal.net.DNParser;
+
+import javax.security.auth.x500.X500Principal;
+
+import junit.framework.TestCase;
+
+public class DNParserTest extends TestCase {
+ public void testFind() {
+ checkFind("", "cn", null);
+ checkFind("ou=xxx", "cn", null);
+ checkFind("ou=xxx,cn=xxx", "cn", "xxx");
+ checkFind("ou=xxx+cn=yyy,cn=zzz+cn=abc", "cn", "yyy");
+ checkFind("2.5.4.3=a,ou=xxx", "cn", "a"); // OID
+ checkFind("cn=a,cn=b", "cn", "a");
+ checkFind("ou=Cc,ou=Bb,ou=Aa", "ou", "Cc");
+ checkFind("cn=imap.gmail.com", "cn", "imap.gmail.com");
+
+ // Quoted string (see http://www.ietf.org/rfc/rfc2253.txt)
+ checkFind("o=\"\\\" a ,=<>#;\"", "o", "\" a ,=<>#;");
+ checkFind("o=abc\\,def", "o", "abc,def");
+
+ // UTF-8 (example in rfc 2253)
+ checkFind("cn=Lu\\C4\\8Di\\C4\\87", "cn", "\u004c\u0075\u010d\u0069\u0107");
+
+ // whitespaces
+ checkFind("ou=a, o= a b ,cn=x", "o", "a b");
+ checkFind("o=\" a b \" ,cn=x", "o", " a b ");
+ }
+
+ private void checkFind(String dn, String attrType, String expected) {
+ String actual = new DNParser(new X500Principal(dn)).find(attrType);
+ assertEquals("dn:" + dn + " attr:" + attrType, expected, actual);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/net/DomainNameValidatorTest.java b/core/tests/coretests/src/com/android/internal/net/DomainNameValidatorTest.java
new file mode 100644
index 0000000..f0d581e
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/net/DomainNameValidatorTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.net;
+
+import com.android.internal.net.DomainNameValidator;
+import com.android.frameworks.coretests.R;
+import com.android.frameworks.coretests.R.raw;
+
+import android.test.AndroidTestCase;
+
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+public class DomainNameValidatorTest extends AndroidTestCase {
+ private static final int ALT_UNKNOWN = 0;
+ private static final int ALT_DNS_NAME = 2;
+ private static final int ALT_IPA_NAME = 7;
+
+ /**
+ * Tests {@link DomainNameValidator#match}, using a simple {@link X509Certificate}
+ * implementation.
+ */
+ public void testMatch() {
+ checkMatch("11", new StubX509Certificate("cn=imap.g.com"), "imap.g.com", true);
+ checkMatch("12", new StubX509Certificate("cn=imap2.g.com"), "imap.g.com", false);
+ checkMatch("13", new StubX509Certificate("cn=sub.imap.g.com"), "imap.g.com", false);
+
+ // If a subjectAltName extension of type dNSName is present, that MUST
+ // be used as the identity
+ checkMatch("21", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+ , "imap.g.com", false);
+ checkMatch("22", new StubX509Certificate("cn=imap.g.com") // This cn should be ignored
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.y.com")
+ , "imap.g.com", false);
+ checkMatch("23", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ , "imap.g.com", true);
+
+ // With wildcards
+ checkMatch("24", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.g.com")
+ , "imap.g.com", true);
+
+ // host name is ip address
+ checkMatch("31", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ , "1.2.3.4", true);
+ checkMatch("32", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ , "1.2.3.5", false);
+ checkMatch("32", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "1.2.3.4")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "192.168.100.1")
+ , "192.168.100.1", true);
+
+ // Has unknown subject alternative names
+ checkMatch("41", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "imap.g.com", true);
+
+ checkMatch("42", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "2.33.44.55", true);
+
+ checkMatch("43", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "g.com", false);
+
+ checkMatch("44", new StubX509Certificate("")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 1")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 2")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "a.b.c.d")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "*.google.com")
+ .addSubjectAlternativeName(ALT_DNS_NAME, "imap.g.com")
+ .addSubjectAlternativeName(ALT_IPA_NAME, "2.33.44.55")
+ .addSubjectAlternativeName(ALT_UNKNOWN, "random string 3")
+ , "2.33.44.1", false);
+ }
+
+ private void checkMatch(String message, X509Certificate certificate, String thisDomain,
+ boolean expected) {
+ Boolean actual = DomainNameValidator.match(certificate, thisDomain);
+ assertEquals(message, (Object) expected, (Object) actual);
+ }
+
+ /**
+ * Tests {@link DomainNameValidator#matchDns}
+ */
+ public void testMatchDns() {
+ checkMatchDns("11", "a.b.c.d", "a.b.c.d", true);
+ checkMatchDns("12", "a.b.c.d", "*.b.c.d", true);
+ checkMatchDns("13", "b.c.d", "*.b.c.d", true);
+ checkMatchDns("14", "b.c.d", "b*.c.d", true);
+
+ checkMatchDns("15", "a.b.c.d", "*.*.c.d", false);
+ checkMatchDns("16", "a.b.c.d", "*.c.d", false);
+
+ checkMatchDns("21", "imap.google.com", "imap.google.com", true);
+ checkMatchDns("22", "imap2.google.com", "imap.google.com", false);
+ checkMatchDns("23", "imap.google.com", "*.google.com", true);
+ checkMatchDns("24", "imap2.google.com", "*.google.com", true);
+ checkMatchDns("25", "imap.google.com", "*.googl.com", false);
+ checkMatchDns("26", "imap2.google2.com", "*.google3.com", false);
+ checkMatchDns("27", "imap.google.com", "ima*.google.com", true);
+ checkMatchDns("28", "imap.google.com", "imap*.google.com", true);
+ checkMatchDns("29", "imap.google.com", "*.imap.google.com", true);
+
+ checkMatchDns("41", "imap.google.com", "a*.google.com", false);
+ checkMatchDns("42", "imap.google.com", "ix*.google.com", false);
+
+ checkMatchDns("51", "imap.google.com", "iMap.Google.Com", true);
+ }
+
+ private void checkMatchDns(String message, String thisDomain, String thatDomain,
+ boolean expected) {
+ boolean actual = DomainNameValidator.matchDns(thisDomain, thatDomain);
+ assertEquals(message, expected, actual);
+ }
+
+ /**
+ * Test {@link DomainNameValidator#match} with actual certificates.
+ */
+ public void testWithActualCert() throws Exception {
+ // subject_only
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: n/a
+ checkWithActualCert("11", R.raw.subject_only, "www.example.com", true);
+ checkWithActualCert("12", R.raw.subject_only, "www2.example.com", false);
+
+ // subject_alt_only
+ //
+ // subject: C=JP (no CN)
+ // subject alt names: DNS:www.example.com
+ checkWithActualCert("21", R.raw.subject_alt_only, "www.example.com", true);
+ checkWithActualCert("22", R.raw.subject_alt_only, "www2.example.com", false);
+
+ // subject_with_alt_names
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: DNS:www2.example.com, DNS:www3.example.com
+ // * Subject should be ignored, because it has subject alt names.
+ checkWithActualCert("31", R.raw.subject_with_alt_names, "www.example.com", false);
+ checkWithActualCert("32", R.raw.subject_with_alt_names, "www2.example.com", true);
+ checkWithActualCert("33", R.raw.subject_with_alt_names, "www3.example.com", true);
+ checkWithActualCert("34", R.raw.subject_with_alt_names, "www4.example.com", false);
+
+ // subject_with_wild_alt_name
+ //
+ // subject: C=JP, CN=www.example.com
+ // subject alt names: DNS:*.example2.com
+ // * Subject should be ignored, because it has subject alt names.
+ checkWithActualCert("41", R.raw.subject_with_wild_alt_name, "www.example.com", false);
+ checkWithActualCert("42", R.raw.subject_with_wild_alt_name, "www2.example.com", false);
+ checkWithActualCert("43", R.raw.subject_with_wild_alt_name, "www.example2.com", true);
+ checkWithActualCert("44", R.raw.subject_with_wild_alt_name, "abc.example2.com", true);
+ checkWithActualCert("45", R.raw.subject_with_wild_alt_name, "www.example3.com", false);
+
+ // wild_alt_name_only
+ //
+ // subject: C=JP
+ // subject alt names: DNS:*.example.com
+ checkWithActualCert("51", R.raw.wild_alt_name_only, "www.example.com", true);
+ checkWithActualCert("52", R.raw.wild_alt_name_only, "www2.example.com", true);
+ checkWithActualCert("53", R.raw.wild_alt_name_only, "www.example2.com", false);
+
+ // wild_alt_name_only
+ //
+ // subject: C=JP
+ // subject alt names: IP Address:192.168.10.1
+ checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.1", true);
+ checkWithActualCert("61", R.raw.alt_ip_only, "192.168.10.2", false);
+ }
+
+ private void checkWithActualCert(String message, int resId, String domain,
+ boolean expected) throws Exception {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ InputStream certStream = getContext().getResources().openRawResource(resId);
+ X509Certificate certificate = (X509Certificate) factory.generateCertificate(certStream);
+
+ checkMatch(message, certificate, domain, expected);
+ }
+
+ /**
+ * Minimal {@link X509Certificate} implementation for {@link DomainNameValidator}.
+ */
+ private static class StubX509Certificate extends X509Certificate {
+ private final X500Principal subjectX500Principal;
+ private Collection<List<?>> subjectAlternativeNames;
+
+ public StubX509Certificate(String subjectDn) {
+ subjectX500Principal = new X500Principal(subjectDn);
+ subjectAlternativeNames = null;
+ }
+
+ public StubX509Certificate addSubjectAlternativeName(int type, String name) {
+ if (subjectAlternativeNames == null) {
+ subjectAlternativeNames = new ArrayList<List<?>>();
+ }
+ LinkedList<Object> entry = new LinkedList<Object>();
+ entry.add(type);
+ entry.add(name);
+ subjectAlternativeNames.add(entry);
+ return this;
+ }
+
+ @Override
+ public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException {
+ return subjectAlternativeNames;
+ }
+
+ @Override
+ public X500Principal getSubjectX500Principal() {
+ return subjectX500Principal;
+ }
+
+ @Override
+ public void checkValidity() throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void checkValidity(Date date) throws CertificateExpiredException,
+ CertificateNotYetValidException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public int getBasicConstraints() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Principal getIssuerDN() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getIssuerUniqueID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getKeyUsage() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Date getNotAfter() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Date getNotBefore() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public BigInteger getSerialNumber() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String getSigAlgName() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String getSigAlgOID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getSigAlgParams() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getSignature() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public Principal getSubjectDN() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public boolean[] getSubjectUniqueID() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public int getVersion() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public byte[] getEncoded() throws CertificateEncodingException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public String toString() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException,
+ InvalidKeyException, NoSuchProviderException, SignatureException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ @Override
+ public void verify(PublicKey key, String sigProvider) throws CertificateException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+ SignatureException {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public Set<String> getCriticalExtensionOIDs() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public byte[] getExtensionValue(String oid) {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public Set<String> getNonCriticalExtensionOIDs() {
+ throw new RuntimeException("Method not implemented");
+ }
+
+ public boolean hasUnsupportedCriticalExtension() {
+ throw new RuntimeException("Method not implemented");
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
new file mode 100644
index 0000000..a304b68
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.android.internal.util;
+
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+import com.android.internal.util.HexDump;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import android.util.Log;
+
+import java.util.Random;
+
+public class BitwiseStreamsTest extends AndroidTestCase {
+ private final static String LOG_TAG = "BitwiseStreamsTest";
+
+ @SmallTest
+ public void testOne() throws Exception {
+ int offset = 3;
+ byte[] inBuf = HexDump.hexStringToByteArray("FFDD");
+ BitwiseOutputStream outStream = new BitwiseOutputStream(30);
+ outStream.skip(offset);
+ for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]);
+ byte[] outBuf = outStream.toByteArray();
+ BitwiseInputStream inStream = new BitwiseInputStream(outBuf);
+ byte[] inBufDup = new byte[inBuf.length];
+ inStream.skip(offset);
+ for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8);
+ assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup));
+ }
+
+ @SmallTest
+ public void testTwo() throws Exception {
+ int offset = 3;
+ byte[] inBuf = HexDump.hexStringToByteArray("11d4f29c0e9ad3c36e72584e064d9b53");
+ BitwiseOutputStream outStream = new BitwiseOutputStream(30);
+ outStream.skip(offset);
+ for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]);
+ BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+ inStream.skip(offset);
+ byte[] inBufDup = new byte[inBuf.length];
+ for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8);
+ assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup));
+ }
+
+ @SmallTest
+ public void testThree() throws Exception {
+ int offset = 4;
+ byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0");
+ BitwiseOutputStream outStream = new BitwiseOutputStream(30);
+ outStream.skip(offset);
+ for (int i = 0; i < inBuf.length; i++) outStream.write(8, inBuf[i]);
+ BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+ inStream.skip(offset);
+ byte[] inBufDup = new byte[inBuf.length];
+ for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8);
+ assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup));
+ }
+
+ @SmallTest
+ public void testFour() throws Exception {
+ int offset = 7;
+ byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0");
+ BitwiseOutputStream outStream = new BitwiseOutputStream(30);
+ outStream.skip(offset);
+ for (int i = 0; i < inBuf.length; i++) {
+ outStream.write(5, inBuf[i] >>> 3);
+ outStream.write(3, inBuf[i] & 0x07);
+ }
+ BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+ inStream.skip(offset);
+ byte[] inBufDup = new byte[inBuf.length];
+ for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8);
+ assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup));
+ }
+
+ @SmallTest
+ public void testFive() throws Exception {
+ Random random = new Random();
+ int iterations = 10000;
+ int[] sizeArr = new int[iterations];
+ int[] valueArr = new int[iterations];
+ BitwiseOutputStream outStream = new BitwiseOutputStream(iterations * 4);
+ for (int i = 0; i < iterations; i++) {
+ int x = random.nextInt();
+ int size = (x & 0x07) + 1;
+ int value = x & (-1 >>> (32 - size));
+ sizeArr[i] = size;
+ valueArr[i] = value;
+ outStream.write(size, value);
+ }
+ BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+ for (int i = 0; i < iterations; i++) {
+ assertEquals(valueArr[i], inStream.read(sizeArr[i]));
+ }
+ }
+
+ @SmallTest
+ public void testSix() throws Exception {
+ int num_runs = 10;
+ long start = android.os.SystemClock.elapsedRealtime();
+ for (int run = 0; run < num_runs; run++) {
+ int offset = run % 8;
+ byte[] inBuf = HexDump.hexStringToByteArray("00031040900112488ea794e0");
+ BitwiseOutputStream outStream = new BitwiseOutputStream(30);
+ outStream.skip(offset);
+ for (int i = 0; i < inBuf.length; i++) {
+ outStream.write(5, inBuf[i] >>> 3);
+ outStream.write(3, inBuf[i] & 0x07);
+ }
+ BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+ inStream.skip(offset);
+ byte[] inBufDup = new byte[inBuf.length];
+ for (int i = 0; i < inBufDup.length; i++) inBufDup[i] = (byte)inStream.read(8);
+ assertEquals(HexDump.toHexString(inBuf), HexDump.toHexString(inBufDup));
+ }
+ long end = android.os.SystemClock.elapsedRealtime();
+ Log.d(LOG_TAG, "repeated encode-decode took " + (end - start) + " ms");
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java b/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java
new file mode 100644
index 0000000..55d186c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import com.android.internal.util.CharSequences;
+import static com.android.internal.util.CharSequences.forAsciiBytes;
+import junit.framework.TestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class CharSequencesTest extends TestCase {
+
+ @SmallTest
+ public void testCharSequences() {
+ String s = "Crazy Bob";
+ byte[] bytes = s.getBytes();
+
+ String copy = toString(forAsciiBytes(bytes));
+ assertTrue(s.equals(copy));
+
+ copy = toString(forAsciiBytes(bytes, 0, s.length()));
+ assertTrue(s.equals(copy));
+
+ String crazy = toString(forAsciiBytes(bytes, 0, 5));
+ assertTrue("Crazy".equals(crazy));
+
+ String a = toString(forAsciiBytes(bytes, 0, 3).subSequence(2, 3));
+ assertTrue("a".equals(a));
+
+ String empty = toString(forAsciiBytes(bytes, 0, 3).subSequence(3, 3));
+ assertTrue("".equals(empty));
+
+ assertTrue(CharSequences.equals("bob", "bob"));
+ assertFalse(CharSequences.equals("b", "bob"));
+ assertFalse(CharSequences.equals("", "bob"));
+ }
+
+ /**
+ * Converts a CharSequence to a string the slow way. Useful for testing
+ * a CharSequence implementation.
+ */
+ static String toString(CharSequence charSequence) {
+ return new StringBuilder().append(charSequence).toString();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/HanziToPinyinTest.java b/core/tests/coretests/src/com/android/internal/util/HanziToPinyinTest.java
new file mode 100644
index 0000000..36dee70
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/HanziToPinyinTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import com.android.internal.util.HanziToPinyin;
+import com.android.internal.util.HanziToPinyin.Token;
+
+import junit.framework.TestCase;
+
+public class HanziToPinyinTest extends TestCase {
+ private final static String ONE_HANZI = "\u675C";
+ private final static String TWO_HANZI = "\u675C\u9D51";
+ private final static String ASSIC = "test";
+ private final static String ONE_UNKNOWN = "\uFF71";
+ private final static String MISC = "test\u675C Test with space\uFF71\uFF71\u675C";
+
+ @SmallTest
+ public void testGetToken() throws Exception {
+ if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) {
+ return;
+ }
+ ArrayList<Token> tokens = HanziToPinyin.getInstance().get(ONE_HANZI);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.PINYIN);
+ assertTrue(tokens.get(0).target.equalsIgnoreCase("DU"));
+
+ tokens = HanziToPinyin.getInstance().get(TWO_HANZI);
+ assertEquals(tokens.size(), 2);
+ assertEquals(tokens.get(0).type, Token.PINYIN);
+ assertEquals(tokens.get(1).type, Token.PINYIN);
+ assertTrue(tokens.get(0).target.equalsIgnoreCase("DU"));
+ assertTrue(tokens.get(1).target.equalsIgnoreCase("JUAN"));
+
+ tokens = HanziToPinyin.getInstance().get(ASSIC);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.LATIN);
+
+ tokens = HanziToPinyin.getInstance().get(ONE_UNKNOWN);
+ assertEquals(tokens.size(), 1);
+ assertEquals(tokens.get(0).type, Token.UNKNOWN);
+
+ tokens = HanziToPinyin.getInstance().get(MISC);
+ assertEquals(tokens.size(), 7);
+ assertEquals(tokens.get(0).type, Token.LATIN);
+ assertEquals(tokens.get(1).type, Token.PINYIN);
+ assertEquals(tokens.get(2).type, Token.LATIN);
+ assertEquals(tokens.get(3).type, Token.LATIN);
+ assertEquals(tokens.get(4).type, Token.LATIN);
+ assertEquals(tokens.get(5).type, Token.UNKNOWN);
+ assertEquals(tokens.get(6).type, Token.PINYIN);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/PredicatesTest.java b/core/tests/coretests/src/com/android/internal/util/PredicatesTest.java
new file mode 100644
index 0000000..c46ff05
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/PredicatesTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class PredicatesTest extends TestCase {
+
+ private static final Predicate<Object> TRUE = new Predicate<Object>() {
+ public boolean apply(Object o) {
+ return true;
+ }
+ };
+
+ private static final Predicate<Object> FALSE = new Predicate<Object>() {
+ public boolean apply(Object o) {
+ return false;
+ }
+ };
+
+ public void testAndPredicate_AllConditionsTrue() throws Exception {
+ assertTrue(Predicates.and(newArrayList(TRUE)).apply(null));
+ assertTrue(Predicates.and(newArrayList(TRUE, TRUE)).apply(null));
+ }
+
+ public void testAndPredicate_AtLeastOneConditionIsFalse() throws Exception {
+ assertFalse(Predicates.and(newArrayList(FALSE, TRUE, TRUE)).apply(null));
+ assertFalse(Predicates.and(newArrayList(TRUE, FALSE, TRUE)).apply(null));
+ assertFalse(Predicates.and(newArrayList(TRUE, TRUE, FALSE)).apply(null));
+ }
+
+ public void testOrPredicate_AllConditionsTrue() throws Exception {
+ assertTrue(Predicates.or(newArrayList(TRUE, TRUE, TRUE)).apply(null));
+ }
+
+ public void testOrPredicate_AllConditionsFalse() throws Exception {
+ assertFalse(Predicates.or(newArrayList(FALSE, FALSE, FALSE)).apply(null));
+ }
+
+ public void testOrPredicate_AtLeastOneConditionIsTrue() throws Exception {
+ assertTrue(Predicates.or(newArrayList(TRUE, FALSE, FALSE)).apply(null));
+ assertTrue(Predicates.or(newArrayList(FALSE, TRUE, FALSE)).apply(null));
+ assertTrue(Predicates.or(newArrayList(FALSE, FALSE, TRUE)).apply(null));
+ }
+
+ public void testNotPredicate() throws Exception {
+ assertTrue(Predicates.not(FALSE).apply(null));
+ assertFalse(Predicates.not(TRUE).apply(null));
+ }
+
+ private static <E> ArrayList<E> newArrayList(E... elements) {
+ ArrayList<E> list = new ArrayList<E>();
+ Collections.addAll(list, elements);
+ return list;
+ }
+
+}
diff --git a/core/tests/hosttests/Android.mk b/core/tests/hosttests/Android.mk
new file mode 100644
index 0000000..0001201
--- /dev/null
+++ b/core/tests/hosttests/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+#LOCAL_TEST_TYPE := hostSideOnly
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := FrameworkCoreHostTests
+
+LOCAL_JAVA_LIBRARIES := hosttestlib ddmlib junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/hosttests/README b/core/tests/hosttests/README
new file mode 100644
index 0000000..d3bdb83
--- /dev/null
+++ b/core/tests/hosttests/README
@@ -0,0 +1,6 @@
+This dir contains tests which run on a host machine, and test aspects of
+package install etc via adb.
+
+To run, do:
+runtest framework-core-host
+
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
new file mode 100644
index 0000000..91cbe2f
--- /dev/null
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.SyncService;
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+import com.android.ddmlib.SyncService.SyncResult;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.Runtime;
+import java.lang.Process;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import junit.framework.Assert;
+import com.android.hosttest.DeviceTestCase;
+
+/**
+ * Set of tests that verify host side install cases
+ */
+public class PackageManagerHostTestUtils extends Assert {
+
+ private static final String LOG_TAG = "PackageManagerHostTests";
+ private IDevice mDevice = null;
+
+ // TODO: get this value from Android Environment instead of hardcoding
+ private static final String APP_PRIVATE_PATH = "/data/app-private/";
+ private static final String DEVICE_APP_PATH = "/data/app/";
+ private static final String SDCARD_APP_PATH = "/mnt/secure/asec/";
+
+ private static final int MAX_WAIT_FOR_DEVICE_TIME = 120 * 1000;
+ private static final int WAIT_FOR_DEVICE_POLL_TIME = 10 * 1000;
+ private static final int MAX_WAIT_FOR_APP_LAUNCH_TIME = 60 * 1000;
+ private static final int WAIT_FOR_APP_LAUNCH_POLL_TIME = 5 * 1000;
+
+ // Install preference on the device-side
+ public static enum InstallLocPreference {
+ AUTO,
+ INTERNAL,
+ EXTERNAL
+ }
+
+ // Actual install location
+ public static enum InstallLocation {
+ DEVICE,
+ SDCARD
+ }
+
+ /**
+ * Constructor takes the device to use
+ * @param the device to use when performing operations
+ */
+ public PackageManagerHostTestUtils(IDevice device)
+ {
+ mDevice = device;
+ }
+
+ /**
+ * Disable default constructor
+ */
+ private PackageManagerHostTestUtils() {}
+
+ /**
+ * Returns the path on the device of forward-locked apps.
+ *
+ * @return path of forward-locked apps on the device
+ */
+ public static String getAppPrivatePath() {
+ return APP_PRIVATE_PATH;
+ }
+
+ /**
+ * Returns the path on the device of normal apps.
+ *
+ * @return path of forward-locked apps on the device
+ */
+ public static String getDeviceAppPath() {
+ return DEVICE_APP_PATH;
+ }
+
+ /**
+ * Returns the path of apps installed on the SD card.
+ *
+ * @return path of forward-locked apps on the device
+ */
+ public static String getSDCardAppPath() {
+ return SDCARD_APP_PATH;
+ }
+
+ /**
+ * Helper method to run tests and return the listener that collected the results.
+ * @param pkgName Android application package for tests
+ * @return the {@link CollectingTestRunListener}
+ */
+ private CollectingTestRunListener doRunTests(String pkgName) throws IOException {
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ pkgName, mDevice);
+ CollectingTestRunListener listener = new CollectingTestRunListener();
+ testRunner.run(listener);
+ return listener;
+ }
+
+ /**
+ * Runs the specified packages tests, and returns whether all tests passed or not.
+ *
+ * @param pkgName Android application package for tests
+ * @return true if every test passed, false otherwise.
+ */
+ public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException {
+ CollectingTestRunListener listener = doRunTests(pkgName);
+ return listener.didAllTestsPass();
+ }
+
+ /**
+ * Helper method to push a file to device
+ * @param apkAppPrivatePath
+ * @throws IOException
+ */
+ public void pushFile(final String localFilePath, final String destFilePath)
+ throws IOException {
+ SyncResult result = mDevice.getSyncService().pushFile(
+ localFilePath, destFilePath, new NullSyncProgressMonitor());
+ assertEquals(SyncService.RESULT_OK, result.getCode());
+ }
+
+ /**
+ * Helper method to install a file
+ * @param localFilePath the absolute file system path to file on local host to install
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @throws IOException
+ */
+ public void installFile(final String localFilePath, final boolean replace) throws IOException {
+ String result = mDevice.installPackage(localFilePath, replace);
+ assertEquals(null, result);
+ }
+
+ /**
+ * Helper method to install a file that should not be install-able
+ * @param localFilePath the absolute file system path to file on local host to install
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @return the string output of the failed install attempt
+ * @throws IOException
+ */
+ public String installFileFail(final String localFilePath, final boolean replace)
+ throws IOException {
+ String result = mDevice.installPackage(localFilePath, replace);
+ assertNotNull(result);
+ return result;
+ }
+
+ /**
+ * Helper method to install a file to device as forward locked
+ * @param localFilePath the absolute file system path to file on local host to install
+ * @param reinstall set to <code>true</code> if re-install of app should be performed
+ * @throws IOException
+ */
+ public String installFileForwardLocked(final String localFilePath, final boolean replace)
+ throws IOException {
+ String remoteFilePath = mDevice.syncPackageToDevice(localFilePath);
+ InstallReceiver receiver = new InstallReceiver();
+ String cmd = String.format(replace ? "pm install -r -l \"%1$s\"" :
+ "pm install -l \"%1$s\"", remoteFilePath);
+ mDevice.executeShellCommand(cmd, receiver);
+ mDevice.removeRemotePackage(remoteFilePath);
+ return receiver.getErrorMessage();
+ }
+
+ /**
+ * Helper method to determine if file on device exists.
+ *
+ * @param destPath the absolute path of file on device to check
+ * @return <code>true</code> if file exists, <code>false</code> otherwise.
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesRemoteFileExist(String destPath) throws IOException {
+ String lsGrep = executeShellCommand(String.format("ls %s", destPath));
+ return !lsGrep.contains("No such file or directory");
+ }
+
+ /**
+ * Helper method to determine if file exists on the device containing a given string.
+ *
+ * @param destPath the absolute path of the file
+ * @return <code>true</code> if file exists containing given string,
+ * <code>false</code> otherwise.
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesRemoteFileExistContainingString(String destPath, String searchString)
+ throws IOException {
+ String lsResult = executeShellCommand(String.format("ls %s", destPath));
+ return lsResult.contains(searchString);
+ }
+
+ /**
+ * Helper method to determine if package on device exists.
+ *
+ * @param packageName the Android manifest package to check.
+ * @return <code>true</code> if package exists, <code>false</code> otherwise
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesPackageExist(String packageName) throws IOException {
+ String pkgGrep = executeShellCommand(String.format("pm path %s", packageName));
+ return pkgGrep.contains("package:");
+ }
+
+ /**
+ * Determines if app was installed on device.
+ *
+ * @param packageName package name to check for
+ * @return <code>true</code> if file exists, <code>false</code> otherwise.
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesAppExistOnDevice(String packageName) throws IOException {
+ return doesRemoteFileExistContainingString(DEVICE_APP_PATH, packageName);
+ }
+
+ /**
+ * Determines if app was installed on SD card.
+ *
+ * @param packageName package name to check for
+ * @return <code>true</code> if file exists, <code>false</code> otherwise.
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesAppExistOnSDCard(String packageName) throws IOException {
+ return doesRemoteFileExistContainingString(SDCARD_APP_PATH, packageName);
+ }
+
+ /**
+ * Helper method to determine if app was installed on SD card.
+ *
+ * @param packageName package name to check for
+ * @return <code>true</code> if file exists, <code>false</code> otherwise.
+ * @throws IOException if adb shell command failed
+ */
+ public boolean doesAppExistAsForwardLocked(String packageName) throws IOException {
+ return doesRemoteFileExistContainingString(APP_PRIVATE_PATH, packageName);
+ }
+
+ /**
+ * Waits for device's package manager to respond.
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void waitForPackageManager() throws InterruptedException, IOException {
+ Log.i(LOG_TAG, "waiting for device");
+ int currentWaitTime = 0;
+ // poll the package manager until it returns something for android
+ while (!doesPackageExist("android")) {
+ Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
+ currentWaitTime += WAIT_FOR_DEVICE_POLL_TIME;
+ if (currentWaitTime > MAX_WAIT_FOR_DEVICE_TIME) {
+ Log.e(LOG_TAG, "time out waiting for device");
+ throw new InterruptedException();
+ }
+ }
+ }
+
+ /**
+ * Helper to determine if the device is currently online and visible via ADB.
+ *
+ * @return true iff the device is currently available to ADB and online, false otherwise.
+ */
+ private boolean deviceIsOnline() {
+ AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
+ IDevice[] devices = bridge.getDevices();
+
+ for (IDevice device : devices) {
+ // only online if the device appears in the devices list, and its state is online
+ if ((mDevice != null) &&
+ mDevice.getSerialNumber().equals(device.getSerialNumber()) &&
+ device.isOnline()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Waits for device to be online (visible to ADB) before returning, or times out if we've
+ * waited too long. Note that this only means the device is visible via ADB, not that
+ * PackageManager is fully up and running yet.
+ *
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void waitForDeviceToComeOnline() throws InterruptedException, IOException {
+ Log.i(LOG_TAG, "waiting for device to be online");
+ int currentWaitTime = 0;
+
+ // poll ADB until we see the device is online
+ while (!deviceIsOnline()) {
+ Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
+ currentWaitTime += WAIT_FOR_DEVICE_POLL_TIME;
+ if (currentWaitTime > MAX_WAIT_FOR_DEVICE_TIME) {
+ Log.e(LOG_TAG, "time out waiting for device");
+ throw new InterruptedException();
+ }
+ }
+ // Note: if we try to access the device too quickly after it is "officially" online,
+ // there are sometimes strange issues where it's actually not quite ready yet,
+ // so we pause for a bit once more before actually returning.
+ Thread.sleep(WAIT_FOR_DEVICE_POLL_TIME);
+ }
+
+ /**
+ * Queries package manager and waits until a package is launched (or times out)
+ *
+ * @param packageName The name of the package to wait to load
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public void waitForApp(String packageName) throws InterruptedException, IOException {
+ Log.i(LOG_TAG, "waiting for app to launch");
+ int currentWaitTime = 0;
+ // poll the package manager until it returns something for the package we're looking for
+ while (!doesPackageExist(packageName)) {
+ Thread.sleep(WAIT_FOR_APP_LAUNCH_POLL_TIME);
+ currentWaitTime += WAIT_FOR_APP_LAUNCH_POLL_TIME;
+ if (currentWaitTime > MAX_WAIT_FOR_APP_LAUNCH_TIME) {
+ Log.e(LOG_TAG, "time out waiting for app to launch: " + packageName);
+ throw new InterruptedException();
+ }
+ }
+ }
+
+ /**
+ * Helper method which executes a adb shell command and returns output as a {@link String}
+ * @return the output of the command
+ * @throws IOException
+ */
+ public String executeShellCommand(String command) throws IOException {
+ Log.i(LOG_TAG, String.format("adb shell %s", command));
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ mDevice.executeShellCommand(command, receiver);
+ String output = receiver.getOutput();
+ Log.i(LOG_TAG, String.format("Result: %s", output));
+ return output;
+ }
+
+ /**
+ * Helper method ensures we are in root mode on the host side. It returns only after
+ * PackageManager is actually up and running.
+ * @throws IOException
+ */
+ public void runAdbRoot() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "adb root");
+ Runtime runtime = Runtime.getRuntime();
+ Process process = runtime.exec("adb root"); // adb should be in the path
+ BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ String nextLine = null;
+ while (null != (nextLine = output.readLine())) {
+ Log.i(LOG_TAG, nextLine);
+ }
+ process.waitFor();
+ waitForDeviceToComeOnline();
+ waitForPackageManager(); // now wait for package manager to actually load
+ }
+
+ /**
+ * Helper method which reboots the device and returns once the device is online again
+ * and package manager is up and running (note this function is synchronous to callers).
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public void rebootDevice() throws IOException, InterruptedException {
+ String command = "reboot"; // no need for -s since mDevice is already tied to a device
+ Log.i(LOG_TAG, command);
+ CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+ mDevice.executeShellCommand(command, receiver);
+ String output = receiver.getOutput();
+ Log.i(LOG_TAG, String.format("Result: %s", output));
+ waitForDeviceToComeOnline(); // wait for device to come online
+ runAdbRoot();
+ }
+
+ /**
+ * A {@link IShellOutputReceiver} which collects the whole shell output into one {@link String}
+ */
+ private class CollectingOutputReceiver extends MultiLineReceiver {
+
+ private StringBuffer mOutputBuffer = new StringBuffer();
+
+ public String getOutput() {
+ return mOutputBuffer.toString();
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line: lines) {
+ mOutputBuffer.append(line);
+ mOutputBuffer.append("\n");
+ }
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+ }
+
+ private class NullSyncProgressMonitor implements ISyncProgressMonitor {
+ public void advance(int work) {
+ // ignore
+ }
+
+ public boolean isCanceled() {
+ // ignore
+ return false;
+ }
+
+ public void start(int totalWork) {
+ // ignore
+
+ }
+
+ public void startSubTask(String name) {
+ // ignore
+ }
+
+ public void stop() {
+ // ignore
+ }
+ }
+
+ // For collecting results from running device tests
+ private static class CollectingTestRunListener implements ITestRunListener {
+
+ private boolean mAllTestsPassed = true;
+ private String mTestRunErrorMessage = null;
+
+ public void testEnded(TestIdentifier test) {
+ // ignore
+ }
+
+ public void testFailed(TestFailure status, TestIdentifier test,
+ String trace) {
+ Log.w(LOG_TAG, String.format("%s#%s failed: %s", test.getClassName(),
+ test.getTestName(), trace));
+ mAllTestsPassed = false;
+ }
+
+ public void testRunEnded(long elapsedTime) {
+ // ignore
+ }
+
+ public void testRunFailed(String errorMessage) {
+ Log.w(LOG_TAG, String.format("test run failed: %s", errorMessage));
+ mAllTestsPassed = false;
+ mTestRunErrorMessage = errorMessage;
+ }
+
+ public void testRunStarted(int testCount) {
+ // ignore
+ }
+
+ public void testRunStopped(long elapsedTime) {
+ // ignore
+ }
+
+ public void testStarted(TestIdentifier test) {
+ // ignore
+ }
+
+ boolean didAllTestsPass() {
+ return mAllTestsPassed;
+ }
+
+ /**
+ * Get the test run failure error message.
+ * @return the test run failure error message or <code>null</code> if test run completed.
+ */
+ String getTestRunErrorMessage() {
+ return mTestRunErrorMessage;
+ }
+ }
+
+ /**
+ * Output receiver for "pm install package.apk" command line.
+ *
+ */
+ private static final class InstallReceiver extends MultiLineReceiver {
+
+ private static final String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$
+ private static final Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$
+
+ private String mErrorMessage = null;
+
+ public InstallReceiver() {
+ }
+
+ @Override
+ public void processNewLines(String[] lines) {
+ for (String line : lines) {
+ if (line.length() > 0) {
+ if (line.startsWith(SUCCESS_OUTPUT)) {
+ mErrorMessage = null;
+ } else {
+ Matcher m = FAILURE_PATTERN.matcher(line);
+ if (m.matches()) {
+ mErrorMessage = m.group(1);
+ }
+ }
+ }
+ }
+ }
+
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+ }
+
+ /**
+ * Helper method for installing an app to wherever is specified in its manifest, and
+ * then verifying the app was installed onto SD Card.
+ *
+ * @param the path of the apk to install
+ * @param the name of the package
+ * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void installAppAndVerifyExistsOnSDCard(String apkPath, String pkgName, boolean overwrite)
+ throws IOException, InterruptedException {
+ // Start with a clean slate if we're not overwriting
+ if (!overwrite) {
+ // cleanup test app just in case it already exists
+ mDevice.uninstallPackage(pkgName);
+ // grep for package to make sure its not installed
+ assertFalse(doesPackageExist(pkgName));
+ }
+
+ installFile(apkPath, overwrite);
+ assertTrue(doesAppExistOnSDCard(pkgName));
+ assertFalse(doesAppExistOnDevice(pkgName));
+ waitForPackageManager();
+
+ // grep for package to make sure it is installed
+ assertTrue(doesPackageExist(pkgName));
+ }
+
+ /**
+ * Helper method for installing an app to wherever is specified in its manifest, and
+ * then verifying the app was installed onto device.
+ *
+ * @param the path of the apk to install
+ * @param the name of the package
+ * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void installAppAndVerifyExistsOnDevice(String apkPath, String pkgName, boolean overwrite)
+ throws IOException, InterruptedException {
+ // Start with a clean slate if we're not overwriting
+ if (!overwrite) {
+ // cleanup test app just in case it already exists
+ mDevice.uninstallPackage(pkgName);
+ // grep for package to make sure its not installed
+ assertFalse(doesPackageExist(pkgName));
+ }
+
+ installFile(apkPath, overwrite);
+ assertFalse(doesAppExistOnSDCard(pkgName));
+ assertTrue(doesAppExistOnDevice(pkgName));
+ waitForPackageManager();
+
+ // grep for package to make sure it is installed
+ assertTrue(doesPackageExist(pkgName));
+ }
+
+ /**
+ * Helper method for installing an app as forward-locked, and
+ * then verifying the app was installed in the proper forward-locked location.
+ *
+ * @param the path of the apk to install
+ * @param the name of the package
+ * @param <code>true</code> if the app should be overwritten, <code>false</code> otherwise
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void installFwdLockedAppAndVerifyExists(String apkPath,
+ String pkgName, boolean overwrite) throws IOException, InterruptedException {
+ // Start with a clean slate if we're not overwriting
+ if (!overwrite) {
+ // cleanup test app just in case it already exists
+ mDevice.uninstallPackage(pkgName);
+ // grep for package to make sure its not installed
+ assertFalse(doesPackageExist(pkgName));
+ }
+
+ String result = installFileForwardLocked(apkPath, overwrite);
+ assertEquals(null, result);
+ assertTrue(doesAppExistAsForwardLocked(pkgName));
+ assertFalse(doesAppExistOnSDCard(pkgName));
+ waitForPackageManager();
+
+ // grep for package to make sure it is installed
+ assertTrue(doesPackageExist(pkgName));
+ }
+
+ /**
+ * Helper method for uninstalling an app.
+ *
+ * @param pkgName package name to uninstall
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void uninstallApp(String pkgName) throws IOException, InterruptedException {
+ mDevice.uninstallPackage(pkgName);
+ // make sure its not installed anymore
+ assertFalse(doesPackageExist(pkgName));
+ }
+
+ /**
+ * Helper method for clearing any installed non-system apps.
+ * Useful ensuring no non-system apps are installed, and for cleaning up stale files that
+ * may be lingering on the system for whatever reason.
+ *
+ * @throws IOException if adb shell command failed
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void wipeNonSystemApps() throws IOException {
+ String allInstalledPackages = executeShellCommand("pm list packages -f");
+ BufferedReader outputReader = new BufferedReader(new StringReader(allInstalledPackages));
+
+ // First use Package Manager to uninstall all non-system apps
+ String currentLine = null;
+ while ((currentLine = outputReader.readLine()) != null) {
+ // Skip over any system apps...
+ if (currentLine.contains("/system/")) {
+ continue;
+ }
+ String packageName = currentLine.substring(currentLine.indexOf('=') + 1);
+ mDevice.uninstallPackage(packageName);
+ }
+ // Make sure there are no stale app files under these directories
+ executeShellCommand(String.format("rm %s*", SDCARD_APP_PATH, "*"));
+ executeShellCommand(String.format("rm %s*", DEVICE_APP_PATH, "*"));
+ executeShellCommand(String.format("rm %s*", APP_PRIVATE_PATH, "*"));
+ }
+
+ /**
+ * Sets the device's install location preference.
+ *
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void setDevicePreferredInstallLocation(InstallLocPreference pref) throws IOException {
+ String command = "pm setInstallLocation %d";
+ int locValue = 0;
+ switch (pref) {
+ case INTERNAL:
+ locValue = 1;
+ break;
+ case EXTERNAL:
+ locValue = 2;
+ break;
+ default: // AUTO
+ locValue = 0;
+ break;
+ }
+ executeShellCommand(String.format(command, locValue));
+ }
+
+ /**
+ * Gets the device's install location preference.
+ *
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public InstallLocPreference getDevicePreferredInstallLocation() throws IOException {
+ String result = executeShellCommand("pm getInstallLocation");
+ if (result.indexOf('0') != -1) {
+ return InstallLocPreference.AUTO;
+ }
+ else if (result.indexOf('1') != -1) {
+ return InstallLocPreference.INTERNAL;
+ }
+ else {
+ return InstallLocPreference.EXTERNAL;
+ }
+ }
+}
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTests.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTests.java
new file mode 100644
index 0000000..1b797d5
--- /dev/null
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTests.java
@@ -0,0 +1,988 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.SyncService;
+import com.android.ddmlib.SyncService.ISyncProgressMonitor;
+import com.android.ddmlib.SyncService.SyncResult;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.Test;
+
+/**
+ * Set of tests that verify host side install cases
+ */
+public class PackageManagerHostTests extends DeviceTestCase {
+
+ private static final String LOG_TAG = "PackageManagerHostTests";
+ private PackageManagerHostTestUtils mPMHostUtils = null;
+
+ private String appPrivatePath = null;
+ private String deviceAppPath = null;
+ private String sdcardAppPath = null;
+
+ // Various test files and their corresponding package names...
+
+ // testPushAppPrivate constants
+ // these constants must match values defined in test-apps/SimpleTestApp
+ private static final String SIMPLE_APK = "SimpleTestApp.apk";
+ private static final String SIMPLE_PKG = "com.android.framework.simpletestapp";
+
+ // Apk with install location set to auto
+ private static final String AUTO_LOC_APK = "AutoLocTestApp.apk";
+ private static final String AUTO_LOC_PKG = "com.android.framework.autoloctestapp";
+ // Apk with install location set to internalOnly
+ private static final String INTERNAL_LOC_APK = "InternalLocTestApp.apk";
+ private static final String INTERNAL_LOC_PKG = "com.android.framework.internalloctestapp";
+ // Apk with install location set to preferExternal
+ private static final String EXTERNAL_LOC_APK = "ExternalLocTestApp.apk";
+ private static final String EXTERNAL_LOC_PKG = "com.android.framework.externalloctestapp";
+ // Apk with install location set to auto (2 versions, for update testing)
+ private static final String AUTO_LOC_VERSION_V1_APK = "AutoLocVersionedTestApp_v1.apk";
+ private static final String AUTO_LOC_VERSION_V2_APK = "AutoLocVersionedTestApp_v2.apk";
+ private static final String AUTO_LOC_VERSION_PKG =
+ "com.android.framework.autolocversionedtestapp";
+ // Apk with install location set to preferExternal (2 versions, for update testing)
+ private static final String EXTERNAL_LOC_VERSION_V1_APK = "ExternalLocVersionedTestApp_v1.apk";
+ private static final String EXTERNAL_LOC_VERSION_V2_APK = "ExternalLocVersionedTestApp_v2.apk";
+ private static final String EXTERNAL_LOC_VERSION_PKG =
+ "com.android.framework.externallocversionedtestapp";
+ // Apk with install location set to auto (2 versions, for update testing)
+ private static final String NO_LOC_VERSION_V1_APK = "NoLocVersionedTestApp_v1.apk";
+ private static final String NO_LOC_VERSION_V2_APK = "NoLocVersionedTestApp_v2.apk";
+ private static final String NO_LOC_VERSION_PKG =
+ "com.android.framework.nolocversionedtestapp";
+ // Apk with no install location set
+ private static final String NO_LOC_APK = "NoLocTestApp.apk";
+ private static final String NO_LOC_PKG = "com.android.framework.noloctestapp";
+ // Apk with 2 different versions - v1 is set to external, v2 has no location setting
+ private static final String UPDATE_EXTERNAL_LOC_V1_EXT_APK
+ = "UpdateExternalLocTestApp_v1_ext.apk";
+ private static final String UPDATE_EXTERNAL_LOC_V2_NONE_APK
+ = "UpdateExternalLocTestApp_v2_none.apk";
+ private static final String UPDATE_EXTERNAL_LOC_PKG
+ = "com.android.framework.updateexternalloctestapp";
+ // Apk with 2 different versions - v1 is set to external, v2 is set to internalOnly
+ private static final String UPDATE_EXT_TO_INT_LOC_V1_EXT_APK
+ = "UpdateExtToIntLocTestApp_v1_ext.apk";
+ private static final String UPDATE_EXT_TO_INT_LOC_V2_INT_APK
+ = "UpdateExtToIntLocTestApp_v2_int.apk";
+ private static final String UPDATE_EXT_TO_INT_LOC_PKG
+ = "com.android.framework.updateexttointloctestapp";
+ // Apk set to preferExternal, with Access Fine Location permissions set in its manifest
+ private static final String FL_PERMS_APK = "ExternalLocPermsFLTestApp.apk";
+ private static final String FL_PERMS_PKG = "com.android.framework.externallocpermsfltestapp";
+ // Apk set to preferExternal, with all permissions set in manifest
+ private static final String ALL_PERMS_APK = "ExternalLocAllPermsTestApp.apk";
+ private static final String ALL_PERMS_PKG = "com.android.framework.externallocallpermstestapp";
+ // Apks with the same package name, but install location set to
+ // one of: Internal, External, Auto, or None
+ private static final String VERSATILE_LOC_PKG = "com.android.framework.versatiletestapp";
+ private static final String VERSATILE_LOC_INTERNAL_APK = "VersatileTestApp_Internal.apk";
+ private static final String VERSATILE_LOC_EXTERNAL_APK = "VersatileTestApp_External.apk";
+ private static final String VERSATILE_LOC_AUTO_APK = "VersatileTestApp_Auto.apk";
+ private static final String VERSATILE_LOC_NONE_APK = "VersatileTestApp_None.apk";
+ // Apks with shared UserID
+ private static final String SHARED_PERMS_APK = "ExternalSharedPermsTestApp.apk";
+ private static final String SHARED_PERMS_PKG
+ = "com.android.framework.externalsharedpermstestapp";
+ private static final String SHARED_PERMS_FL_APK = "ExternalSharedPermsFLTestApp.apk";
+ private static final String SHARED_PERMS_FL_PKG
+ = "com.android.framework.externalsharedpermsfltestapp";
+ private static final String SHARED_PERMS_BT_APK = "ExternalSharedPermsBTTestApp.apk";
+ private static final String SHARED_PERMS_BT_PKG
+ = "com.android.framework.externalsharedpermsbttestapp";
+ // Apk with shared UserID, but signed with a different cert (the media cert)
+ private static final String SHARED_PERMS_DIFF_KEY_APK = "ExternalSharedPermsDiffKeyTestApp.apk";
+ private static final String SHARED_PERMS_DIFF_KEY_PKG
+ = "com.android.framework.externalsharedpermsdiffkeytestapp";
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ // ensure apk path has been set before test is run
+ assertNotNull(getTestAppPath());
+
+ // setup the PackageManager host tests utilities class, and get various paths we'll need...
+ mPMHostUtils = new PackageManagerHostTestUtils(getDevice());
+ appPrivatePath = mPMHostUtils.getAppPrivatePath();
+ deviceAppPath = mPMHostUtils.getDeviceAppPath();
+ sdcardAppPath = mPMHostUtils.getSDCardAppPath();
+
+ // Ensure the default is set to let the system decide where to install apps
+ // (It's ok for individual tests to override and change this during their test, but should
+ // reset it back when they're done)
+ mPMHostUtils.setDevicePreferredInstallLocation(
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO);
+ }
+
+ /**
+ * Get the absolute file system location of test app with given filename
+ * @param fileName the file name of the test app apk
+ * @return {@link String} of absolute file path
+ */
+ public String getTestAppFilePath(String fileName) {
+ return String.format("%s%s%s", getTestAppPath(), File.separator, fileName);
+ }
+
+ public static Test suite() {
+ return new DeviceTestSuite(PackageManagerHostTests.class);
+ }
+
+ /**
+ * Regression test to verify that pushing an apk to the private app directory doesn't install
+ * the app, and otherwise cause the system to blow up.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testPushAppPrivate() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "testing pushing an apk to /data/app-private");
+ final String apkAppPrivatePath = appPrivatePath + SIMPLE_APK;
+
+ // cleanup test app just in case it was accidently installed
+ getDevice().uninstallPackage(SIMPLE_PKG);
+ mPMHostUtils.executeShellCommand("stop");
+ mPMHostUtils.pushFile(getTestAppFilePath(SIMPLE_APK), apkAppPrivatePath);
+
+ // sanity check to make sure file is there
+ assertTrue(mPMHostUtils.doesRemoteFileExist(apkAppPrivatePath));
+ mPMHostUtils.executeShellCommand("start");
+
+ mPMHostUtils.waitForPackageManager();
+
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(SIMPLE_PKG));
+ // ensure it has been deleted from app-private
+ assertFalse(mPMHostUtils.doesRemoteFileExist(apkAppPrivatePath));
+ }
+
+ /**
+ * Helper to do a standard install of an apk and verify it installed to the correct location.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @param apkName the file name of the test app apk
+ * @param pkgName the package name of the test app apk
+ * @param expectedLocation the file name of the test app apk
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ private void doStandardInstall(String apkName, String pkgName,
+ PackageManagerHostTestUtils.InstallLocation expectedLocation)
+ throws IOException, InterruptedException {
+
+ if (expectedLocation == PackageManagerHostTestUtils.InstallLocation.DEVICE) {
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(
+ getTestAppFilePath(apkName), pkgName, false);
+ }
+ else {
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(
+ getTestAppFilePath(apkName), pkgName, false);
+ }
+ }
+
+ /**
+ * Installs the Auto app using the preferred device install location specified,
+ * and verifies it was installed on the device.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @param preference the device's preferred location of where to install apps
+ * @param expectedLocation the expected location of where the apk was installed
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void installAppAutoLoc(PackageManagerHostTestUtils.InstallLocPreference preference,
+ PackageManagerHostTestUtils.InstallLocation expectedLocation)
+ throws IOException, InterruptedException {
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(preference);
+
+ doStandardInstall(AUTO_LOC_APK, AUTO_LOC_PKG, expectedLocation);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(AUTO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=auto
+ * will install the app to the device when device's preference is auto.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppAutoLocPrefIsAuto() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=auto, prefer=auto gets installed on device");
+ installAppAutoLoc(PackageManagerHostTestUtils.InstallLocPreference.AUTO,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=auto
+ * will install the app to the device when device's preference is internal.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppAutoLocPrefIsInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=auto, prefer=internal gets installed on device");
+ installAppAutoLoc(PackageManagerHostTestUtils.InstallLocPreference.INTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=auto
+ * will install the app to the SD card when device's preference is external.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppAutoLocPrefIsExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=auto, prefer=external gets installed on device");
+ installAppAutoLoc(PackageManagerHostTestUtils.InstallLocPreference.EXTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Installs the Internal app using the preferred device install location specified,
+ * and verifies it was installed to the location expected.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @param preference the device's preferred location of where to install apps
+ * @param expectedLocation the expected location of where the apk was installed
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void installAppInternalLoc(PackageManagerHostTestUtils.InstallLocPreference preference,
+ PackageManagerHostTestUtils.InstallLocation expectedLocation)
+ throws IOException, InterruptedException {
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(preference);
+
+ doStandardInstall(INTERNAL_LOC_APK, INTERNAL_LOC_PKG, expectedLocation);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(INTERNAL_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=internalOnly
+ * will install the app to the device when device's preference is auto.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppInternalLocPrefIsAuto() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=internal, prefer=auto gets installed on device");
+ installAppInternalLoc(PackageManagerHostTestUtils.InstallLocPreference.AUTO,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=internalOnly
+ * will install the app to the device when device's preference is internal.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppInternalLocPrefIsInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=internal, prefer=internal is installed on device");
+ installAppInternalLoc(PackageManagerHostTestUtils.InstallLocPreference.INTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=internalOnly
+ * will install the app to the device when device's preference is external.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppInternalLocPrefIsExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=internal, prefer=external is installed on device");
+ installAppInternalLoc(PackageManagerHostTestUtils.InstallLocPreference.EXTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.DEVICE);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=preferExternal
+ * will install the app to the SD card.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @param preference the device's preferred location of where to install apps
+ * @param expectedLocation the expected location of where the apk was installed
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void installAppExternalLoc(PackageManagerHostTestUtils.InstallLocPreference preference,
+ PackageManagerHostTestUtils.InstallLocation expectedLocation)
+ throws IOException, InterruptedException {
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(preference);
+
+ doStandardInstall(EXTERNAL_LOC_APK, EXTERNAL_LOC_PKG, expectedLocation);
+
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(EXTERNAL_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=preferExternal
+ * will install the app to the device when device's preference is auto.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppExternalLocPrefIsAuto() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=external, pref=auto gets installed on SD Card");
+ installAppExternalLoc(PackageManagerHostTestUtils.InstallLocPreference.AUTO,
+ PackageManagerHostTestUtils.InstallLocation.SDCARD);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=preferExternal
+ * will install the app to the device when device's preference is internal.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppExternalLocPrefIsInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=external, pref=internal gets installed on SD Card");
+ installAppExternalLoc(PackageManagerHostTestUtils.InstallLocPreference.INTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.SDCARD);
+ }
+
+ /**
+ * Regression test to verify that an app with its manifest set to installLocation=preferExternal
+ * will install the app to the device when device's preference is external.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppExternalLocPrefIsExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installLocation=external, pref=external gets installed on SD Card");
+ installAppExternalLoc(PackageManagerHostTestUtils.InstallLocPreference.EXTERNAL,
+ PackageManagerHostTestUtils.InstallLocation.SDCARD);
+ }
+
+ /**
+ * Regression test to verify that an app without installLocation in its manifest
+ * will install the app to the device by default when the system default pref is to let the
+ * system decide.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppNoLocPrefIsAuto() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with no installLocation gets installed on device");
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO);
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(
+ getTestAppFilePath(NO_LOC_APK), NO_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(NO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app without installLocation in its manifest
+ * will install the app to the device by default when the system default pref is to install
+ * external.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppNoLocPrefIsExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with no installLocation gets installed on SD card");
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(
+ PackageManagerHostTestUtils.InstallLocPreference.EXTERNAL);
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(
+ getTestAppFilePath(NO_LOC_APK), NO_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(NO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app without installLocation in its manifest
+ * will install the app to the device by default when the system default pref is to install
+ * internal.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAppNoLocPrefIsInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with no installLocation gets installed on device");
+
+ PackageManagerHostTestUtils.InstallLocPreference savedPref =
+ PackageManagerHostTestUtils.InstallLocPreference.AUTO;
+
+ try {
+ savedPref = mPMHostUtils.getDevicePreferredInstallLocation();
+ mPMHostUtils.setDevicePreferredInstallLocation(
+ PackageManagerHostTestUtils.InstallLocPreference.INTERNAL);
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(
+ getTestAppFilePath(NO_LOC_APK), NO_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.setDevicePreferredInstallLocation(savedPref);
+ mPMHostUtils.uninstallApp(NO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its installLocation set to internal that is
+ * forward-locked will get installed to the correct location.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallFwdLockedAppInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with installLoc set to Internal gets installed to app-private");
+
+ try {
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(
+ getTestAppFilePath(INTERNAL_LOC_APK), INTERNAL_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(INTERNAL_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its installLocation set to external that is
+ * forward-locked will get installed to the correct location.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallFwdLockedAppExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with installLoc set to Internal gets installed to app-private");
+
+ try {
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(
+ getTestAppFilePath(INTERNAL_LOC_APK), INTERNAL_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(INTERNAL_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with its installLocation set to external that is
+ * forward-locked will get installed to the correct location.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallFwdLockedAppAuto() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with installLoc set to Auto gets installed to app-private");
+
+ try {
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(
+ getTestAppFilePath(AUTO_LOC_APK), AUTO_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(AUTO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with no installLocation set and is
+ * forward-locked installed will get installed to the correct location.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallFwdLockedAppNone() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test an app with no installLoc set gets installed to app-private");
+
+ try {
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(
+ getTestAppFilePath(NO_LOC_APK), NO_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(NO_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that we can install an app onto the device,
+ * uninstall it, and reinstall it onto the SD card.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ // TODO: This currently relies on the app's manifest to switch from device to
+ // SD card install locations. We might want to make Device's installPackage()
+ // accept a installLocation flag so we can install a package to the
+ // destination of our choosing.
+ public void testReinstallInternalToExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installing an app first to the device, then to the SD Card");
+
+ try {
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(
+ getTestAppFilePath(VERSATILE_LOC_INTERNAL_APK), VERSATILE_LOC_PKG, false);
+ mPMHostUtils.uninstallApp(VERSATILE_LOC_PKG);
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(
+ getTestAppFilePath(VERSATILE_LOC_EXTERNAL_APK), VERSATILE_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(VERSATILE_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that we can install an app onto the SD Card,
+ * uninstall it, and reinstall it onto the device.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ // TODO: This currently relies on the app's manifest to switch from device to
+ // SD card install locations. We might want to make Device's installPackage()
+ // accept a installLocation flag so we can install a package to the
+ // destination of our choosing.
+ public void testReinstallExternalToInternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installing an app first to the SD Care, then to the device");
+
+ try {
+ // install the app externally
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(
+ getTestAppFilePath(VERSATILE_LOC_EXTERNAL_APK), VERSATILE_LOC_PKG, false);
+ mPMHostUtils.uninstallApp(VERSATILE_LOC_PKG);
+ // then replace the app with one marked for internalOnly
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(
+ getTestAppFilePath(VERSATILE_LOC_INTERNAL_APK), VERSATILE_LOC_PKG, false);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(VERSATILE_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that updating an app on the SD card will install
+ * the update onto the SD card as well when location is set to external for both versions
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testUpdateBothExternal() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating an app on the SD card stays on the SD card");
+
+ try {
+ // install the app externally
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ EXTERNAL_LOC_VERSION_V1_APK), EXTERNAL_LOC_VERSION_PKG, false);
+ // now replace the app with one where the location is still set to preferExternal
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ EXTERNAL_LOC_VERSION_V2_APK), EXTERNAL_LOC_VERSION_PKG, true);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(EXTERNAL_LOC_VERSION_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that updating an app on the SD card will install
+ * the update onto the SD card as well when location is not explicitly set in the
+ * updated apps' manifest file.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testUpdateToSDCard() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating an app on the SD card stays on the SD card");
+
+ try {
+ // install the app externally
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ UPDATE_EXTERNAL_LOC_V1_EXT_APK), UPDATE_EXTERNAL_LOC_PKG, false);
+ // now replace the app with one where the location is blank (app should stay external)
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ UPDATE_EXTERNAL_LOC_V2_NONE_APK), UPDATE_EXTERNAL_LOC_PKG, true);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(UPDATE_EXTERNAL_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that updating an app on the SD card will install
+ * the update onto the device if the manifest has changed to installLocation=internalOnly
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testUpdateSDCardToDevice() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating an app on the SD card to the Device through manifest change");
+
+ try {
+ // install the app externally
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ UPDATE_EXT_TO_INT_LOC_V1_EXT_APK), UPDATE_EXT_TO_INT_LOC_PKG, false);
+ // now replace the app with an update marked for internalOnly...(should move internal)
+ mPMHostUtils.installAppAndVerifyExistsOnDevice(getTestAppFilePath(
+ UPDATE_EXT_TO_INT_LOC_V2_INT_APK), UPDATE_EXT_TO_INT_LOC_PKG, true);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(UPDATE_EXT_TO_INT_LOC_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that installing and updating a forward-locked app will install
+ * the update onto the device's forward-locked location
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndUpdateExternalLocForwardLockedApp()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating a forward-locked app marked preferExternal");
+
+ try {
+ // first try to install the forward-locked app externally
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(getTestAppFilePath(
+ EXTERNAL_LOC_VERSION_V1_APK), EXTERNAL_LOC_VERSION_PKG, false);
+ // now replace the app with an update marked for internalOnly and as forward locked
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(getTestAppFilePath(
+ EXTERNAL_LOC_VERSION_V2_APK), EXTERNAL_LOC_VERSION_PKG, true);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(EXTERNAL_LOC_VERSION_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that updating a forward-locked app will install
+ * the update onto the device's forward-locked location
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndUpdateNoLocForwardLockedApp()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating a forward-locked app with no installLocation pref set");
+
+ try {
+ // install the app
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(getTestAppFilePath(
+ NO_LOC_VERSION_V1_APK), NO_LOC_VERSION_PKG, false);
+ // now replace the app with an update marked for internalOnly...
+ mPMHostUtils.installFwdLockedAppAndVerifyExists(getTestAppFilePath(
+ NO_LOC_VERSION_V2_APK), NO_LOC_VERSION_PKG, true);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(NO_LOC_VERSION_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with all permissions set can be installed on SD card
+ * and then launched without crashing.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchAllPermsAppOnSD()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with all perms set, installed on SD card");
+
+ try {
+ // install the app
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ ALL_PERMS_APK), ALL_PERMS_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(ALL_PERMS_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(ALL_PERMS_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with ACCESS_FINE_LOCATION (GPS) permissions can
+ * run without permissions errors.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchFLPermsAppOnSD()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with location perms set, installed on SD card");
+
+ try {
+ // install the app and verify we can launch it without permissions errors
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_FL_APK), SHARED_PERMS_FL_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_FL_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_FL_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with BLUE_TOOTH permissions can
+ * run without permissions errors.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchBTPermsAppOnSD()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with bluetooth perms set, installed on SD card");
+
+ try {
+ // install the app and verify we can launch it without permissions errors
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_BT_APK), SHARED_PERMS_BT_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_BT_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_BT_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that a shared app with no explicit permissions throws a
+ * SecurityException when launched if its other shared apps are not installed.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchSharedPermsAppOnSD_NoPerms()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with no explicit perms set, installed on SD card");
+
+ try {
+ // Make sure the 2 shared apps with needed permissions are not installed...
+ mPMHostUtils.uninstallApp(SHARED_PERMS_FL_PKG);
+ mPMHostUtils.uninstallApp(SHARED_PERMS_BT_PKG);
+
+ // now install the app and see if when we launch it we get a permissions error
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_APK), SHARED_PERMS_PKG, false);
+
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_PKG);
+ assertEquals("Shared perms app should fail to run", false, testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that a shared app with no explicit permissions can run if its other
+ * shared apps are installed.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchSharedPermsAppOnSD_GrantedPerms()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with no explicit perms set, installed on SD card");
+
+ try {
+ // install the 2 shared apps with needed permissions first
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_FL_APK), SHARED_PERMS_FL_PKG, false);
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_BT_APK), SHARED_PERMS_BT_PKG, false);
+
+ // now install the test app and see if we can launch it without errors
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_APK), SHARED_PERMS_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_PKG);
+ mPMHostUtils.uninstallApp(SHARED_PERMS_BT_PKG);
+ mPMHostUtils.uninstallApp(SHARED_PERMS_FL_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that an app with ACCESS_FINE_LOCATION (GPS) permissions can
+ * run without permissions errors even after a reboot
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchFLPermsAppOnSD_Reboot()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app with location perms set, installed on SD card");
+
+ try {
+ // install the app and verify we can launch it without permissions errors
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_FL_APK), SHARED_PERMS_FL_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_FL_PKG);
+ assert(testsPassed);
+
+ mPMHostUtils.rebootDevice();
+
+ testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_FL_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_FL_PKG);
+ }
+ }
+
+ /**
+ * Regression test to verify that a shared app with no explicit permissions can run if its other
+ * shared apps are installed, even after a reboot.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ * @throws IOException if adb shell command failed
+ * @throws InterruptedException if the thread was interrupted
+ */
+ public void testInstallAndLaunchSharedPermsAppOnSD_Reboot()
+ throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test launching an app on SD, with no explicit perms set after reboot");
+
+ try {
+ // install the 2 shared apps with needed permissions first
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_FL_APK), SHARED_PERMS_FL_PKG, false);
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_BT_APK), SHARED_PERMS_BT_PKG, false);
+
+ // now install the test app and see if we can launch it without errors
+ mPMHostUtils.installAppAndVerifyExistsOnSDCard(getTestAppFilePath(
+ SHARED_PERMS_APK), SHARED_PERMS_PKG, false);
+ boolean testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_PKG);
+ assert(testsPassed);
+
+ // reboot
+ mPMHostUtils.rebootDevice();
+
+ // Verify we can still launch the app
+ testsPassed = mPMHostUtils.runDeviceTestsDidAllTestsPass(SHARED_PERMS_PKG);
+ assert(testsPassed);
+ }
+ // cleanup test app
+ finally {
+ mPMHostUtils.uninstallApp(SHARED_PERMS_PKG);
+ mPMHostUtils.uninstallApp(SHARED_PERMS_BT_PKG);
+ mPMHostUtils.uninstallApp(SHARED_PERMS_FL_PKG);
+ }
+ }
+}
diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerStressHostTests.java b/core/tests/hosttests/src/android/content/pm/PackageManagerStressHostTests.java
new file mode 100644
index 0000000..715c55b
--- /dev/null
+++ b/core/tests/hosttests/src/android/content/pm/PackageManagerStressHostTests.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.Log;
+import com.android.hosttest.DeviceTestCase;
+import com.android.hosttest.DeviceTestSuite;
+
+import java.io.File;
+import java.io.IOException;
+
+import junit.framework.Test;
+
+/**
+ * Set of tests that verify host side stress scenarios (large apps, multiple upgrades, etc.)
+ */
+public class PackageManagerStressHostTests extends DeviceTestCase {
+
+ private static final String LOG_TAG = "PackageManagerStressHostTests";
+ private PackageManagerHostTestUtils mPMHostUtils = null;
+
+ // Path to the app repository and various subdirectories of it
+ // Note: These stress tests require large apks that cannot be checked into the tree.
+ // These variables define static locations that point to existing APKs (not built from
+ // the tree) which can be used by the the stress tests in this file.
+ private static final String LARGE_APPS_DIRECTORY_NAME = "largeApps";
+ private static final String MISC_APPS_DIRECTORY_NAME = "miscApps";
+ private static final String VERSIONED_APPS_DIRECTORY_NAME = "versionedApps";
+ private static final String MANY_APPS_DIRECTORY_NAME = "manyApps";
+
+ // Note: An external environment variable "ANDROID_TEST_APP_REPOSITORY" must be set
+ // which points to the root location of the app respository.
+ private static String AppRepositoryPath = null;
+
+ // Large apps (>1mb) - filenames and their corresponding package names:
+ private static enum APK {
+ FILENAME,
+ PACKAGENAME;
+ }
+ private static final String[][] LARGE_APPS = {
+ {"External1mb.apk", "com.appsonsd.mytests.External1mb"},
+ {"External2mb.apk", "com.appsonsd.mytests.External2mb"},
+ {"External3mb.apk", "com.appsonsd.mytests.External3mb"},
+ {"External4mb.apk", "com.appsonsd.mytests.External4mb"},
+ {"External5mb.apk", "com.appsonsd.mytests.External5mb"},
+ {"External6mb.apk", "com.appsonsd.mytests.External6mb"},
+ {"External7mb.apk", "com.appsonsd.mytests.External7mb"},
+ {"External8mb.apk", "com.appsonsd.mytests.External8mb"},
+ {"External9mb.apk", "com.appsonsd.mytests.External9mb"},
+ {"External10mb.apk", "com.appsonsd.mytests.External10mb"},
+ {"External16mb.apk", "com.appsonsd.mytests.External16mb"},
+ {"External28mb.apk", "com.appsonsd.mytests.External28mb"},
+ {"External34mb.apk", "com.appsonsd.mytests.External34mb"},
+ {"External46mb.apk", "com.appsonsd.mytests.External46mb"},
+ {"External58mb.apk", "com.appsonsd.mytests.External58mb"},
+ {"External65mb.apk", "com.appsonsd.mytests.External65mb"},
+ {"External72mb.apk", "com.appsonsd.mytests.External72mb"},
+ {"External79mb.apk", "com.appsonsd.mytests.External79mb"},
+ {"External86mb.apk", "com.appsonsd.mytests.External86mb"},
+ {"External93mb.apk", "com.appsonsd.mytests.External93mb"}};
+
+ // Various test files and their corresponding package names
+ private static final String AUTO_LOC_APK = "Auto241kb.apk";
+ private static final String AUTO_LOC_PKG = "com.appsonsd.mytests.Auto241kb";
+ private static final String INTERNAL_LOC_APK = "Internal781kb.apk";
+ private static final String INTERNAL_LOC_PKG = "com.appsonsd.mytests.Internal781kb";
+ private static final String EXTERNAL_LOC_APK = "External931kb.apk";
+ private static final String EXTERNAL_LOC_PKG = "com.appsonsd.mytests.External931kb";
+ private static final String NO_LOC_APK = "Internal751kb_EclairSDK.apk";
+ private static final String NO_LOC_PKG = "com.appsonsd.mytests.Internal751kb_EclairSDK";
+ // Versioned test apps
+ private static final String VERSIONED_APPS_FILENAME_PREFIX = "External455kb_v";
+ private static final String VERSIONED_APPS_PKG = "com.appsonsd.mytests.External455kb";
+ private static final int VERSIONED_APPS_START_VERSION = 1; // inclusive
+ private static final int VERSIONED_APPS_END_VERSION = 250; // inclusive
+ // Large number of app installs
+ // @TODO: increase the max when we can install more apps
+ private static final int MANY_APPS_START = 1;
+ private static final int MANY_APPS_END = 100;
+ private static final String MANY_APPS_PKG_PREFIX = "com.appsonsd.mytests.External49kb_";
+ private static final String MANY_APPS_APK_PREFIX = "External49kb_";
+
+ public static Test suite() {
+ return new DeviceTestSuite(PackageManagerStressHostTests.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // setup the PackageManager host tests utilities class, and get various paths we'll need...
+ mPMHostUtils = new PackageManagerHostTestUtils(getDevice());
+ AppRepositoryPath = System.getenv("ANDROID_TEST_APP_REPOSITORY");
+ assertNotNull(AppRepositoryPath);
+
+ // Make sure path ends with a separator
+ if (!AppRepositoryPath.endsWith(File.separator)) {
+ AppRepositoryPath += File.separator;
+ }
+ }
+
+ /**
+ * Get the absolute file system location of repository test app with given filename
+ * @param fileName the file name of the test app apk
+ * @return {@link String} of absolute file path
+ */
+ private String getRepositoryTestAppFilePath(String fileDirectory, String fileName) {
+ return String.format("%s%s%s%s", AppRepositoryPath, fileDirectory,
+ File.separator, fileName);
+ }
+
+ /**
+ * Get the absolute file system location of test app with given filename
+ * @param fileName the file name of the test app apk
+ * @return {@link String} of absolute file path
+ */
+ public String getTestAppFilePath(String fileName) {
+ return String.format("%s%s%s", getTestAppPath(), File.separator, fileName);
+ }
+
+ /**
+ * Stress test to verify that we can update an app multiple times on the SD card.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void testUpdateAppManyTimesOnSD() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating an app on SD numerous times");
+
+ // cleanup test app just in case it already exists
+ mPMHostUtils.uninstallApp(VERSIONED_APPS_PKG);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(VERSIONED_APPS_PKG));
+
+ try {
+ for (int i = VERSIONED_APPS_START_VERSION; i <= VERSIONED_APPS_END_VERSION; ++i) {
+ String currentApkName = String.format("%s%d.apk",
+ VERSIONED_APPS_FILENAME_PREFIX, i);
+
+ Log.i(LOG_TAG, "Installing app " + currentApkName);
+ mPMHostUtils.installFile(getRepositoryTestAppFilePath(VERSIONED_APPS_DIRECTORY_NAME,
+ currentApkName), true);
+ mPMHostUtils.waitForPackageManager();
+ assertTrue(mPMHostUtils.doesAppExistOnSDCard(VERSIONED_APPS_PKG));
+ assertTrue(mPMHostUtils.doesPackageExist(VERSIONED_APPS_PKG));
+ }
+ }
+ finally {
+ // cleanup test app
+ mPMHostUtils.uninstallApp(VERSIONED_APPS_PKG);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(VERSIONED_APPS_PKG));
+ }
+ }
+
+ /**
+ * Stress test to verify that an app can be installed, uninstalled, and
+ * reinstalled on SD many times.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void testUninstallReinstallAppOnSDManyTimes() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test updating an app on the SD card stays on the SD card");
+
+ // cleanup test app just in case it was already exists
+ mPMHostUtils.uninstallApp(EXTERNAL_LOC_PKG);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(EXTERNAL_LOC_PKG));
+
+ for (int i = 0; i <= 500; ++i) {
+ Log.i(LOG_TAG, "Installing app");
+
+ try {
+ // install the app
+ mPMHostUtils.installFile(getRepositoryTestAppFilePath(MISC_APPS_DIRECTORY_NAME,
+ EXTERNAL_LOC_APK), false);
+ mPMHostUtils.waitForPackageManager();
+ assertTrue(mPMHostUtils.doesAppExistOnSDCard(EXTERNAL_LOC_PKG));
+ assertTrue(mPMHostUtils.doesPackageExist(EXTERNAL_LOC_PKG));
+ }
+ finally {
+ // now uninstall the app
+ Log.i(LOG_TAG, "Uninstalling app");
+ mPMHostUtils.uninstallApp(EXTERNAL_LOC_PKG);
+ mPMHostUtils.waitForPackageManager();
+ assertFalse(mPMHostUtils.doesPackageExist(EXTERNAL_LOC_PKG));
+ }
+ }
+ }
+
+ /**
+ * Stress test to verify that we can install, 20 large apps (>1mb each)
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void testInstallManyLargeAppsOnSD() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installing 20 large apps onto the sd card");
+
+ try {
+ // Install all the large apps
+ for (int i=0; i < LARGE_APPS.length; ++i) {
+ String apkName = LARGE_APPS[i][APK.FILENAME.ordinal()];
+ String pkgName = LARGE_APPS[i][APK.PACKAGENAME.ordinal()];
+
+ // cleanup test app just in case it already exists
+ mPMHostUtils.uninstallApp(pkgName);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(pkgName));
+
+ Log.i(LOG_TAG, "Installing app " + apkName);
+ // install the app
+ mPMHostUtils.installFile(getRepositoryTestAppFilePath(LARGE_APPS_DIRECTORY_NAME,
+ apkName), false);
+ mPMHostUtils.waitForPackageManager();
+ assertTrue(mPMHostUtils.doesAppExistOnSDCard(pkgName));
+ assertTrue(mPMHostUtils.doesPackageExist(pkgName));
+ }
+ }
+ finally {
+ // Cleanup - ensure we uninstall all large apps if they were installed
+ for (int i=0; i < LARGE_APPS.length; ++i) {
+ String apkName = LARGE_APPS[i][APK.FILENAME.ordinal()];
+ String pkgName = LARGE_APPS[i][APK.PACKAGENAME.ordinal()];
+
+ Log.i(LOG_TAG, "Uninstalling app " + apkName);
+ // cleanup test app just in case it was accidently installed
+ mPMHostUtils.uninstallApp(pkgName);
+ // grep for package to make sure its not installed anymore
+ assertFalse(mPMHostUtils.doesPackageExist(pkgName));
+ assertFalse(mPMHostUtils.doesAppExistOnSDCard(pkgName));
+ }
+ }
+ }
+
+ /**
+ * Stress test to verify that we can install many small apps onto SD.
+ * <p/>
+ * Assumes adb is running as root in device under test.
+ */
+ public void testInstallManyAppsOnSD() throws IOException, InterruptedException {
+ Log.i(LOG_TAG, "Test installing 500 small apps onto SD");
+
+ try {
+ for (int i = MANY_APPS_START; i <= MANY_APPS_END; ++i) {
+ String currentPkgName = String.format("%s%d", MANY_APPS_PKG_PREFIX, i);
+
+ // cleanup test app just in case it already exists
+ mPMHostUtils.uninstallApp(currentPkgName);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(currentPkgName));
+
+ String currentApkName = String.format("%s%d.apk", MANY_APPS_APK_PREFIX, i);
+ Log.i(LOG_TAG, "Installing app " + currentApkName);
+ mPMHostUtils.installFile(getRepositoryTestAppFilePath(MANY_APPS_DIRECTORY_NAME,
+ currentApkName), true);
+ mPMHostUtils.waitForPackageManager();
+ assertTrue(mPMHostUtils.doesAppExistOnSDCard(currentPkgName));
+ assertTrue(mPMHostUtils.doesPackageExist(currentPkgName));
+ }
+ }
+ finally {
+ for (int i = MANY_APPS_START; i <= MANY_APPS_END; ++i) {
+ String currentPkgName = String.format("%s%d", MANY_APPS_PKG_PREFIX, i);
+
+ // cleanup test app
+ mPMHostUtils.uninstallApp(currentPkgName);
+ // grep for package to make sure its not installed
+ assertFalse(mPMHostUtils.doesPackageExist(currentPkgName));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/Android.mk b/core/tests/hosttests/test-apps/Android.mk
new file mode 100644
index 0000000..e25764f
--- /dev/null
+++ b/core/tests/hosttests/test-apps/Android.mk
@@ -0,0 +1,21 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Build the test APKs using their own makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/hosttests/test-apps/AutoLocTestApp/Android.mk b/core/tests/hosttests/test-apps/AutoLocTestApp/Android.mk
new file mode 100644
index 0000000..b3cab48
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := AutoLocTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/res/res/values-mcc204-nb/strings.xml b/core/tests/hosttests/test-apps/AutoLocTestApp/AndroidManifest.xml
index 94786f1..d8c806a 100644
--- a/core/res/res/values-mcc204-nb/strings.xml
+++ b/core/tests/hosttests/test-apps/AutoLocTestApp/AndroidManifest.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
@@ -13,7 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="2972154133076909542">"nl_nl"</string>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.autoloctestapp"
+ android:installLocation="auto">
+
+ <application android:label="AutoLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/AutoLocTestApp/src/com/android/framework/autoloctestapp/AutoLocTestAppActivity.java b/core/tests/hosttests/test-apps/AutoLocTestApp/src/com/android/framework/autoloctestapp/AutoLocTestAppActivity.java
new file mode 100644
index 0000000..fba5691
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocTestApp/src/com/android/framework/autoloctestapp/AutoLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.autoloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class AutoLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/Android.mk
new file mode 100644
index 0000000..a887bac
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := AutoLocVersionedTestApp_v1
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/AndroidManifest.xml
new file mode 100644
index 0000000..867871d
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.autolocversionedtestapp"
+ android:installLocation="auto"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="AutoLocVersionedTestApp_v1"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..49575b7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v1/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.autolocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class AutoLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/Android.mk
new file mode 100644
index 0000000..69084bf
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := AutoLocVersionedTestApp_v2
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/AndroidManifest.xml
new file mode 100644
index 0000000..98e5606
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.autolocversionedtestapp"
+ android:installLocation="auto"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+ <application android:label="AutoLocVersionedTestApp_v2"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..49575b7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/AutoLocVersionedTestApp_v2/src/com/android/framework/autolocversionedtestapp/AutoLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.autolocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class AutoLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
new file mode 100644
index 0000000..c70c1d3
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalLocAllPermsTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..0c502c0
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/AndroidManifest.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externallocallpermstestapp"
+ android:installLocation="preferExternal">
+
+ <uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
+ <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.ACCOUNT_MANAGER" />
+ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.BATTERY_STATS" />
+ <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+ <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BRICK" />
+ <uses-permission android:name="android.permission.BROADCAST_PACKAGE_REMOVED" />
+ <uses-permission android:name="android.permission.BROADCAST_SMS" />
+ <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+ <uses-permission android:name="android.permission.BROADCAST_WAP_PUSH" />
+ <uses-permission android:name="android.permission.CALL_PHONE" />
+ <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CLEAR_APP_CACHE" />
+ <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
+ <uses-permission android:name="android.permission.CONTROL_LOCATION_UPDATES" />
+ <uses-permission android:name="android.permission.DELETE_CACHE_FILES" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
+ <uses-permission android:name="android.permission.DEVICE_POWER" />
+ <uses-permission android:name="android.permission.DIAGNOSTIC" />
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+ <uses-permission android:name="android.permission.FACTORY_TEST" />
+ <uses-permission android:name="android.permission.FLASHLIGHT" />
+ <uses-permission android:name="android.permission.FORCE_BACK" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
+ <uses-permission android:name="android.permission.GET_TASKS" />
+ <uses-permission android:name="android.permission.GLOBAL_SEARCH" />
+ <uses-permission android:name="android.permission.HARDWARE_TEST" />
+ <uses-permission android:name="android.permission.INJECT_EVENTS" />
+ <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" />
+ <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+ <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+ <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+ <uses-permission android:name="android.permission.MASTER_CLEAR" />
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />
+ <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
+ <uses-permission android:name="android.permission.PERSISTENT_ACTIVITY" />
+ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
+ <uses-permission android:name="android.permission.READ_CALENDAR" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+ <uses-permission android:name="android.permission.READ_HISTORY_BOOKMARKS" />
+ <uses-permission android:name="android.permission.READ_INPUT_STATE" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <uses-permission android:name="android.permission.READ_OWNER_DATA" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_SMS" />
+ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+ <uses-permission android:name="android.permission.READ_SYNC_STATS" />
+ <uses-permission android:name="android.permission.REBOOT" />
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.RECEIVE_MMS" />
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.REORDER_TASKS" />
+ <uses-permission android:name="android.permission.RESTART_PACKAGES" />
+ <uses-permission android:name="android.permission.SEND_SMS" />
+ <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER" />
+ <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
+ <uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
+ <uses-permission android:name="android.permission.SET_DEBUG_APP" />
+ <uses-permission android:name="android.permission.SET_ORIENTATION" />
+ <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
+ <uses-permission android:name="android.permission.SET_PROCESS_LIMIT" />
+ <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER" />
+ <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
+ <uses-permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" />
+ <uses-permission android:name="android.permission.STATUS_BAR" />
+ <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_READ" />
+ <uses-permission android:name="android.permission.SUBSCRIBED_FEEDS_WRITE" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.VIBRATE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_GSERVICES" />
+ <uses-permission android:name="android.permission.WRITE_HISTORY_BOOKMARKS" />
+ <uses-permission android:name="android.permission.WRITE_OWNER_DATA" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_SMS" />
+ <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.framework.externallocallpermstestapp"
+ android:label="Test for instrumentation with an app granted all permissions" />
+</manifest>
diff --git a/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/src/com/android/framework/externallocallpermstestapp/ExternalLocAllPermsTest.java b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/src/com/android/framework/externallocallpermstestapp/ExternalLocAllPermsTest.java
new file mode 100644
index 0000000..8456255
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocAllPermsTestApp/src/com/android/framework/externallocallpermstestapp/ExternalLocAllPermsTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externallocallpermstestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import junit.framework.TestCase;
+
+
+public class ExternalLocAllPermsTest extends TestCase {
+ /**
+ * Test method that should get run. Doesn't need to actually do anything here,
+ * we just need to verify the test runs without errors.
+ */
+ public void testInstrumentationCanRun() {
+ }
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/Android.mk b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/Android.mk
new file mode 100644
index 0000000..9a05fa6
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalLocPermFLTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..f45c627
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externallocpermsfltestapp"
+ android:installLocation="preferExternal">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <application android:label="ExternalLocPermsFLTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/src/com/android/framework/externallocpermsfltestapp/ExternalLocPermsFLTestAppActivity.java b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/src/com/android/framework/externallocpermsfltestapp/ExternalLocPermsFLTestAppActivity.java
new file mode 100644
index 0000000..48fd744
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocPermsFLTestApp/src/com/android/framework/externallocpermsfltestapp/ExternalLocPermsFLTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externallocpermsfltestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class ExternalLocPermsFLTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocTestApp/Android.mk b/core/tests/hosttests/test-apps/ExternalLocTestApp/Android.mk
new file mode 100644
index 0000000..5aec78a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalLocTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalLocTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..a0c7b02
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocTestApp/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externalloctestapp"
+ android:installLocation="preferExternal">
+
+ <application android:label="ExternalLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/ExternalLocTestApp/src/com/android/framework/externalloctestapp/ExternalLocTestAppActivity.java b/core/tests/hosttests/test-apps/ExternalLocTestApp/src/com/android/framework/externalloctestapp/ExternalLocTestAppActivity.java
new file mode 100644
index 0000000..0a61628
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocTestApp/src/com/android/framework/externalloctestapp/ExternalLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externalloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class ExternalLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/Android.mk
new file mode 100644
index 0000000..05f62cd
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalLocVersionedTestApp_v1
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/AndroidManifest.xml
new file mode 100644
index 0000000..84bd5df
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externallocversionedtestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="ExternalLocVersionedTestApp_v1"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..a7487c2
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v1/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externallocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class ExternalLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/Android.mk
new file mode 100644
index 0000000..aa31759
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalLocVersionedTestApp_v2
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/AndroidManifest.xml
new file mode 100644
index 0000000..7acba15
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externallocversionedtestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+ <application android:label="ExternalLocVersionedTestApp_v2"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..a7487c2
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalLocVersionedTestApp_v2/src/com/android/framework/externallocversionedtestapp/ExternalLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externallocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class ExternalLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
new file mode 100644
index 0000000..7946e1a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalSharedPermsTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPerms/AndroidManifest.xml
new file mode 100644
index 0000000..263a0fb
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externalsharedpermstestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0"
+ android:sharedUserId="com.android.framework.externalsharedpermstestapp">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.framework.externalsharedpermstestapp"
+ android:label="Test for instrumentation with an external app with shared permissions (BT and FL)" />
+</manifest>
+
+
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPerms/src/com/android/framework/externalsharedpermstestapp/ExternalSharedPermsTest.java b/core/tests/hosttests/test-apps/ExternalSharedPerms/src/com/android/framework/externalsharedpermstestapp/ExternalSharedPermsTest.java
new file mode 100644
index 0000000..da9600c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPerms/src/com/android/framework/externalsharedpermstestapp/ExternalSharedPermsTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externalsharedpermstestapp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+
+import android.util.Log;
+
+import android.test.InstrumentationTestCase;
+
+public class ExternalSharedPermsTest extends InstrumentationTestCase
+{
+ private static final int REQUEST_ENABLE_BT = 2;
+
+ /** The use of location manager and bluetooth below are simply to simulate an app that
+ * tries to use them, so we can verify whether permissions are granted and accessible.
+ * */
+ public void testRunLocationAndBluetooth()
+ {
+ LocationManager locationManager = (LocationManager)getInstrumentation().getContext(
+ ).getSystemService(Context.LOCATION_SERVICE);
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
+ new LocationListener() {
+ public void onLocationChanged(Location location) {}
+ public void onProviderDisabled(String provider) {}
+ public void onProviderEnabled(String provider) {}
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ }
+ );
+ BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if ((mBluetoothAdapter != null) && (!mBluetoothAdapter.isEnabled())) {
+ mBluetoothAdapter.getName();
+ }
+ }
+}
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
new file mode 100644
index 0000000..657d0a4
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalSharedPermsBTTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml
new file mode 100644
index 0000000..98f7177
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externalsharedpermsbttestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0"
+ android:sharedUserId="com.android.framework.externalsharedpermstestapp">
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.framework.externalsharedpermsbttestapp"
+ android:label="Test for instrumentation with an external app granted BLUETOOTH permissions" />
+
+</manifest>
+
+
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsBT/src/com/android/framework/externalsharedpermsbttestapp/ExternalSharedPermsBTTest.java b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/src/com/android/framework/externalsharedpermsbttestapp/ExternalSharedPermsBTTest.java
new file mode 100644
index 0000000..c7bcdfc
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsBT/src/com/android/framework/externalsharedpermsbttestapp/ExternalSharedPermsBTTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externalsharedpermsbttestapp;
+
+import android.bluetooth.BluetoothAdapter;
+
+import android.test.InstrumentationTestCase;
+
+public class ExternalSharedPermsBTTest extends InstrumentationTestCase
+{
+ private static final int REQUEST_ENABLE_BT = 2;
+
+ /** The use of bluetooth below is simply to simulate an activity that tries to use bluetooth
+ * upon creation, so we can verify whether permissions are granted and accessible to the
+ * activity once it launches.
+ * */
+ public void testRunBluetooth()
+ {
+ BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if ((mBluetoothAdapter != null) && (!mBluetoothAdapter.isEnabled())) {
+ mBluetoothAdapter.getName();
+ }
+ }
+}
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
new file mode 100644
index 0000000..d4450dc
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalSharedPermsDiffKeyTestApp
+
+LOCAL_CERTIFICATE := media
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/AndroidManifest.xml
new file mode 100644
index 0000000..d0e8fb9
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externalsharedpermsdiffkeytestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0"
+ android:sharedUserId="com.android.framework.externalsharedpermstestapp">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.framework.externalsharedpermsdiffkeytestapp"
+ android:label="Test for instrumentation with an app with shared permissions but signed by different key" />
+</manifest>
+
+
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/src/com/android/framework/externalsharedpermsdiffkeytestapp/ExternalSharedPermsDiffKeyTest.java b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/src/com/android/framework/externalsharedpermsdiffkeytestapp/ExternalSharedPermsDiffKeyTest.java
new file mode 100644
index 0000000..adc906a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsDiffKey/src/com/android/framework/externalsharedpermsdiffkeytestapp/ExternalSharedPermsDiffKeyTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externalsharedpermsdiffkeytestapp;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.os.Bundle;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+
+import android.test.InstrumentationTestCase;
+
+public class ExternalSharedPermsDiffKeyTest extends InstrumentationTestCase
+{
+ private static final int REQUEST_ENABLE_BT = 2;
+
+ /** The use of location manager and bluetooth below are simply to simulate an app that
+ * tries to use them, so we can verify whether permissions are granted and accessible.
+ * */
+ public void testRunBluetoothAndFineLocation()
+ {
+ LocationManager locationManager = (LocationManager)getInstrumentation().getContext(
+ ).getSystemService(Context.LOCATION_SERVICE);
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
+ new LocationListener() {
+ public void onLocationChanged(Location location) {}
+ public void onProviderDisabled(String provider) {}
+ public void onProviderEnabled(String provider) {}
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ }
+ );
+ BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ if ((mBluetoothAdapter != null) && (!mBluetoothAdapter.isEnabled())) {
+ mBluetoothAdapter.getName();
+ }
+ fail("this app was signed by a different cert and should crash/fail to run by now");
+ }
+}
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
new file mode 100644
index 0000000..8154862
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := ExternalSharedPermsFLTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/AndroidManifest.xml b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/AndroidManifest.xml
new file mode 100644
index 0000000..15cc912
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.externalsharedpermsfltestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0"
+ android:sharedUserId="com.android.framework.externalsharedpermstestapp">
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.framework.externalsharedpermsfltestapp"
+ android:label="Test for instrumentation with an app granted FINE_LOCATION permissions" />
+</manifest>
+
+
diff --git a/core/tests/hosttests/test-apps/ExternalSharedPermsFL/src/com/android/framework/externalsharedpermsfltestapp/ExternalSharedPermsFLTest.java b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/src/com/android/framework/externalsharedpermsfltestapp/ExternalSharedPermsFLTest.java
new file mode 100644
index 0000000..307034e
--- /dev/null
+++ b/core/tests/hosttests/test-apps/ExternalSharedPermsFL/src/com/android/framework/externalsharedpermsfltestapp/ExternalSharedPermsFLTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.externalsharedpermsfltestapp;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+
+import android.test.InstrumentationTestCase;
+
+public class ExternalSharedPermsFLTest extends InstrumentationTestCase
+{
+ /** The use of location manager below is simply to simulate an app that
+ * tries to use it, so we can verify whether permissions are granted and accessible.
+ * */
+ public void testRunFineLocation()
+ {
+ LocationManager locationManager = (LocationManager)getInstrumentation().getContext(
+ ).getSystemService(Context.LOCATION_SERVICE);
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
+ new LocationListener() {
+ public void onLocationChanged(Location location) {}
+ public void onProviderDisabled(String provider) {}
+ public void onProviderEnabled(String provider) {}
+ public void onStatusChanged(String provider, int status, Bundle extras) {}
+ }
+ );
+ }
+}
diff --git a/core/tests/hosttests/test-apps/InternalLocTestApp/Android.mk b/core/tests/hosttests/test-apps/InternalLocTestApp/Android.mk
new file mode 100644
index 0000000..5b58e72
--- /dev/null
+++ b/core/tests/hosttests/test-apps/InternalLocTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := InternalLocTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/InternalLocTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/InternalLocTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..fae3ae0
--- /dev/null
+++ b/core/tests/hosttests/test-apps/InternalLocTestApp/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.internalloctestapp"
+ android:installLocation="internalOnly">
+
+ <application android:label="InternalLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/InternalLocTestApp/src/com/android/framework/internalloctestapp/InternalLocTestAppActivity.java b/core/tests/hosttests/test-apps/InternalLocTestApp/src/com/android/framework/internalloctestapp/InternalLocTestAppActivity.java
new file mode 100644
index 0000000..6934641
--- /dev/null
+++ b/core/tests/hosttests/test-apps/InternalLocTestApp/src/com/android/framework/internalloctestapp/InternalLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.internalloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class InternalLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/NoLocTestApp/Android.mk b/core/tests/hosttests/test-apps/NoLocTestApp/Android.mk
new file mode 100644
index 0000000..11916b0
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := NoLocTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/res/res/values-mcc234-nb/strings.xml b/core/tests/hosttests/test-apps/NoLocTestApp/AndroidManifest.xml
index bd391e1..bfdad37 100644
--- a/core/res/res/values-mcc234-nb/strings.xml
+++ b/core/tests/hosttests/test-apps/NoLocTestApp/AndroidManifest.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
@@ -13,7 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="233769627858930762">"en_gb"</string>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.noloctestapp">
+
+ <application android:label="NoLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/NoLocTestApp/src/com/android/framework/noloctestapp/NoLocTestAppActivity.java b/core/tests/hosttests/test-apps/NoLocTestApp/src/com/android/framework/noloctestapp/NoLocTestAppActivity.java
new file mode 100644
index 0000000..72c8d2b
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocTestApp/src/com/android/framework/noloctestapp/NoLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.noloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class NoLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/Android.mk
new file mode 100644
index 0000000..36413ee
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := NoLocVersionedTestApp_v1
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/AndroidManifest.xml b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/AndroidManifest.xml
new file mode 100644
index 0000000..c98f1c2
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.nolocversionedtestapp"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="NoLocVersionedTestApp_v1"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..0540e5a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v1/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.nolocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class NoLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/Android.mk
new file mode 100644
index 0000000..27d03b0
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := NoLocVersionedTestApp_v2
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/AndroidManifest.xml b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/AndroidManifest.xml
new file mode 100644
index 0000000..1af1e68
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.nolocversionedtestapp"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+ <application android:label="NoLocVersionedTestApp_v2"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java
new file mode 100644
index 0000000..0540e5a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/NoLocVersionedTestApp_v2/src/com/android/framework/nolocversionedtestapp/NoLocVersionedTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.nolocversionedtestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class NoLocVersionedTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/SimpleTestApp/Android.mk b/core/tests/hosttests/test-apps/SimpleTestApp/Android.mk
new file mode 100644
index 0000000..82543aa
--- /dev/null
+++ b/core/tests/hosttests/test-apps/SimpleTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := SimpleTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/SimpleTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/SimpleTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..ae36fe7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/SimpleTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.simpletestapp">
+
+ <!--
+ A simple empty app used to test various install scenarios/
+ -->
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/SimpleTestApp/src/com/android/framework/simpletestapp/SimpleAppActivity.java b/core/tests/hosttests/test-apps/SimpleTestApp/src/com/android/framework/simpletestapp/SimpleAppActivity.java
new file mode 100644
index 0000000..b7bbf94
--- /dev/null
+++ b/core/tests/hosttests/test-apps/SimpleTestApp/src/com/android/framework/simpletestapp/SimpleAppActivity.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.simpletestapp;
+
+import android.app.Activity;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class SimpleAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/Android.mk b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/Android.mk
new file mode 100644
index 0000000..f2baefe
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := UpdateExtToIntLocTestApp_v1_ext
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/AndroidManifest.xml b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/AndroidManifest.xml
new file mode 100644
index 0000000..b572e86
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.updateexttointloctestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="UpdateExtToIntLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java
new file mode 100644
index 0000000..5c51fb7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v1_ext/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.updateexttointloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class UpdateExtToIntLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/Android.mk b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/Android.mk
new file mode 100644
index 0000000..492c326
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := UpdateExtToIntLocTestApp_v2_int
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/AndroidManifest.xml b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/AndroidManifest.xml
new file mode 100644
index 0000000..c9437ae
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.updateexttointloctestapp"
+ android:installLocation="internalOnly"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+ <application android:label="UpdateExtToIntLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java
new file mode 100644
index 0000000..5c51fb7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExtToIntLocTestApp_v2_int/src/com/android/framework/updateexttointloctestapp/UpdateExtToIntLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.updateexttointloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class UpdateExtToIntLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/Android.mk b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/Android.mk
new file mode 100644
index 0000000..45867f7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := UpdateExternalLocTestApp_v1_ext
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/AndroidManifest.xml b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/AndroidManifest.xml
new file mode 100644
index 0000000..ec622ce
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.updateexternalloctestapp"
+ android:installLocation="preferExternal"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="UpdateExternalLocTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java
new file mode 100644
index 0000000..02ecb75
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v1_ext/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.updateexternalloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class UpdateExternalLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/Android.mk b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/Android.mk
new file mode 100644
index 0000000..780a9d7
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := UpdateExternalLocTestApp_v2_none
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/AndroidManifest.xml b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/AndroidManifest.xml
new file mode 100644
index 0000000..50fb104
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.updateexternalloctestapp"
+ android:versionCode="2"
+ android:versionName="2.0">
+
+ <application android:label="UpdateExternalLocTestApp"/>
+</manifest>
diff --git a/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java
new file mode 100644
index 0000000..02ecb75
--- /dev/null
+++ b/core/tests/hosttests/test-apps/UpdateExternalLocTestApp_v2_none/src/com/android/framework/updateexternalloctestapp/UpdateExternalLocTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.updateexternalloctestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class UpdateExternalLocTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_Auto/Android.mk b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/Android.mk
new file mode 100644
index 0000000..fc42bc4
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := VersatileTestApp_Auto
+
+include $(BUILD_PACKAGE)
diff --git a/core/res/res/values-mcc230-nb/strings.xml b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/AndroidManifest.xml
index 63ade62..f249250 100644
--- a/core/res/res/values-mcc230-nb/strings.xml
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/AndroidManifest.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
@@ -13,7 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="locale_replacement" msgid="9011721823833053909">"cs_cz"</string>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.versatiletestapp"
+ android:installLocation="auto">
+
+ <application android:label="VersatileTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_Auto/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
new file mode 100644
index 0000000..c201c7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Auto/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.versatiletestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class VersatileTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_External/Android.mk b/core/tests/hosttests/test-apps/VersatileTestApp_External/Android.mk
new file mode 100644
index 0000000..c72a92c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_External/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := VersatileTestApp_External
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_External/AndroidManifest.xml b/core/tests/hosttests/test-apps/VersatileTestApp_External/AndroidManifest.xml
new file mode 100644
index 0000000..0d17ac2
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_External/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.versatiletestapp"
+ android:installLocation="preferExternal">
+
+ <application android:label="VersatileTestApp"/>
+
+</manifest>
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_External/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java b/core/tests/hosttests/test-apps/VersatileTestApp_External/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
new file mode 100644
index 0000000..c201c7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_External/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.versatiletestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class VersatileTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_Internal/Android.mk b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/Android.mk
new file mode 100644
index 0000000..e477825
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := VersatileTestApp_Internal
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_Internal/AndroidManifest.xml b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/AndroidManifest.xml
new file mode 100644
index 0000000..e8cae6e
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.versatiletestapp"
+ android:installLocation="internalOnly">
+
+ <application android:label="VersatileTestApp"/>
+</manifest>
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_Internal/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
new file mode 100644
index 0000000..c201c7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_Internal/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.versatiletestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class VersatileTestAppActivity extends Activity {
+
+}
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_None/Android.mk b/core/tests/hosttests/test-apps/VersatileTestApp_None/Android.mk
new file mode 100644
index 0000000..1fc516c
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_None/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := VersatileTestApp_None
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_None/AndroidManifest.xml b/core/tests/hosttests/test-apps/VersatileTestApp_None/AndroidManifest.xml
new file mode 100644
index 0000000..1df40d4
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_None/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.framework.versatiletestapp">
+
+ <application android:label="VersatileTestApp"/>
+</manifest>
diff --git a/core/tests/hosttests/test-apps/VersatileTestApp_None/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java b/core/tests/hosttests/test-apps/VersatileTestApp_None/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
new file mode 100644
index 0000000..c201c7a
--- /dev/null
+++ b/core/tests/hosttests/test-apps/VersatileTestApp_None/src/com/android/framework/versatiletestapp/VersatileTestAppActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.framework.versatiletestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Empty activity, not needed for this test
+ */
+public class VersatileTestAppActivity extends Activity {
+
+}